| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Yamlet.Decode
Description
Conversion of nodes to Haskell values, with errors that point to the node that caused them.
Synopsis
- class FromYaml a where
- parseYaml :: Node -> Parser a
- parseYamlList :: Node -> Parser [a]
- parseYamlField :: Node -> Node -> Parser a
- data Parser a
- runParser :: (Node -> Parser a) -> Node -> Either (NonEmpty (Offset, String)) a
- parseNode :: (Node -> Parser a) -> Node -> Parser a
- failAt :: Node -> String -> Parser a
- typeMismatch :: String -> Node -> Parser a
- orElse :: Parser a -> Parser a -> Parser a
- data View
- = NullView
- | BoolView !Bool
- | IntView !Integer
- | FloatView !FloatValue
- | StringView !Text
- | SequenceView ![Node]
- | MappingView ![(Node, Node)]
- | AliasView !Text
- view :: Node -> View
- describeNode :: Node -> String
- withNull :: Parser a -> Node -> Parser a
- withBool :: (Bool -> Parser a) -> Node -> Parser a
- withInt :: (Integer -> Parser a) -> Node -> Parser a
- withFloat :: (Double -> Parser a) -> Node -> Parser a
- withScientific :: (Scientific -> Parser a) -> Node -> Parser a
- withText :: (Text -> Parser a) -> Node -> Parser a
- oneOf :: [(Text, a)] -> Node -> Parser a
- withSequence :: ([Node] -> Parser a) -> Node -> Parser a
- parseItems :: (Node -> Parser a) -> [Node] -> Parser [a]
- withMapping :: (Object -> Parser a) -> Node -> Parser a
- data Object
- objectNode :: Object -> Node
- objectEntries :: Object -> [(Node, Node)]
- objectKeys :: Object -> [Text]
- lookupKey :: Object -> Text -> Parser (Maybe Node)
- parseField :: FromYaml a => Object -> Text -> Parser a
- parseFieldMaybe :: FromYaml a => Object -> Text -> Parser (Maybe a)
- parseFieldIfPresent :: FromYaml a => Object -> Text -> Parser (Maybe a)
- parseFieldDefault :: FromYaml a => Object -> Text -> a -> Parser a
- parseFieldWith :: (Node -> Parser a) -> Object -> Text -> Parser a
- parseFieldMaybeWith :: (Node -> Parser a) -> Object -> Text -> Parser (Maybe a)
- parseFieldIfPresentWith :: (Node -> Parser a) -> Object -> Text -> Parser (Maybe a)
- parseFieldDefaultWith :: (Node -> Parser a) -> Object -> Text -> a -> Parser a
- rejectUnknownKeys :: [Text] -> Object -> Parser ()
Class
class FromYaml a where Source #
Types that can be parsed from a node. A type with a
Generic instance can derive the instance via
GenericYaml.
An instance for a record reads a mapping with withMapping:
>>>:{data Server = Server {host :: T.Text, port :: Int, tags :: [T.Text]} deriving stock (Show) instance FromYaml Server where parseYaml = withMapping $ \o -> rejectUnknownKeys ["host", "port", "tags"] o *> ( Server <$> parseField o "host" <*> parseFieldDefault o "port" 80 <*> parseFieldDefault o "tags" [] ) :}
>>>decodeText @Server "host: example.com\ntags:\n- web\n"Right (Server {host = "example.com", port = 80, tags = ["web"]})
The decoder reports the errors of all fields together:
>>>either printErrors print (decodeText @Server "hots: example.com\nport: http\n")input.yaml:1:1: unknown key "hots", did you mean "host"? | 1 | hots: example.com | ^ input.yaml:1:1: missing key "host" | 1 | hots: example.com | ^ input.yaml:2:7: port: expected an integer, but got a string | 2 | port: http | ^
The instances of the library copy the texts that they keep, so a decoded
value does not keep the input in memory. A value from a hand-written
instance can keep the input while it has unevaluated parts, e.g. a lazy
list. Evaluate such a value, e.g. with force, to release
the input.
Minimal complete definition
Methods
parseYaml :: Node -> Parser a Source #
parseYamlList :: Node -> Parser [a] Source #
Parse a list. The instance for Char parses a string instead.
parseYamlField :: Node -> Node -> Parser a Source #
Parse the value of a mapping entry, with its key, e.g. to keep the
comments of the key as Commented does. parseField and the
other lookups without a parser argument, the derived decoders and the
instances for maps use it. The default ignores the key.
Instances
| FromYaml IntSet Source # | A list. Two elements that convert to the same value, e.g. |
| FromYaml Void Source # | |
| FromYaml All Source # | The value inside. |
| FromYaml Any Source # | The value inside. |
| FromYaml Int16 Source # | |
| FromYaml Int32 Source # | |
| FromYaml Int64 Source # | |
| FromYaml Int8 Source # | |
| FromYaml Word16 Source # | |
| FromYaml Word32 Source # | |
| FromYaml Word64 Source # | |
| FromYaml Word8 Source # | |
| FromYaml Ordering Source # |
|
| FromYaml Scientific Source # | |
| FromYaml Text Source # | |
| FromYaml Text Source # | |
| FromYaml CalendarDiffDays Source # | A mapping with the keys |
| FromYaml Day Source # |
|
| FromYaml Month Source # |
|
| FromYaml Quarter Source # |
|
| FromYaml QuarterOfYear Source # |
|
| FromYaml DayOfWeek Source # | The English name in any case, e.g. |
| FromYaml DiffTime Source # | A number of seconds, rounded down to a picosecond. |
| FromYaml NominalDiffTime Source # | A number of seconds, rounded down to a picosecond. |
| FromYaml UTCTime Source # | Like |
| FromYaml CalendarDiffTime Source # | A mapping with the keys |
| FromYaml LocalTime Source # | A date and a time, separated by |
| FromYaml TimeOfDay Source # |
|
| FromYaml ZonedTime Source # | A date, a time and a time zone, e.g. |
| FromYaml UUID Source # | The text form with hyphens, e.g. |
| FromYaml Node Source # | The node of the syntax tree, with its styles and comments, e.g. to write a part of a document back as it was written. An alias in the input gives a copy of the node that it refers to. The texts of the node are copies, so that a small part of a document does
not keep the whole input alive. For a whole document without a copy, use
|
| FromYaml Value Source # | The value of the node, with the tags resolved and the aliases replaced. |
| FromYaml Integer Source # | |
| FromYaml Natural Source # | |
| FromYaml () Source # | An empty list, as a tuple without elements. |
| FromYaml Bool Source # | |
| FromYaml Char Source # | |
| FromYaml Double Source # | |
| FromYaml Float Source # | The nearest float. A conversion by way of |
| FromYaml Int Source # | |
| FromYaml Word Source # | |
| FromYaml a => FromYaml (First a) Source # | The value inside. |
| FromYaml a => FromYaml (Last a) Source # | The value inside. |
| FromYaml a => FromYaml (Max a) Source # | The value inside. |
| FromYaml a => FromYaml (Min a) Source # | The value inside. |
| FromYaml v => FromYaml (IntMap v) Source # | Two keys that convert to the same key are an error. |
| FromYaml a => FromYaml (Seq a) Source # | |
| (Ord a, FromYaml a) => FromYaml (Set a) Source # | A list. Two elements that convert to the same value, e.g. |
| FromYaml a => FromYaml (Tree a) Source # | A list of the label and the subtrees, e.g. |
| FromYaml a => FromYaml (NonEmpty a) Source # | |
| FromYaml a => FromYaml (Identity a) Source # | The value inside. |
| FromYaml a => FromYaml (First a) Source # | The value inside, or null for |
| FromYaml a => FromYaml (Last a) Source # | The value inside, or null for |
| FromYaml a => FromYaml (Down a) Source # | The value inside. |
| FromYaml a => FromYaml (Dual a) Source # | The value inside. |
| FromYaml a => FromYaml (Product a) Source # | The value inside. |
| FromYaml a => FromYaml (Sum a) Source # | The value inside. |
| (Integral a, FromYaml a) => FromYaml (Ratio a) Source # | A mapping with the keys |
| (Generic a, GenericYamlOptions a, GDatatype (Rep a), Constructors (Rep a) ~ f, GConstructors f, GEncoding (SumEncoding a) f, GFromConstructor f, FromYaml a) => FromYaml (GenericYaml a) Source # | |
Defined in Yamlet.Generic Methods parseYaml :: Node -> Parser (GenericYaml a) Source # parseYamlList :: Node -> Parser [GenericYaml a] Source # parseYamlField :: Node -> Node -> Parser (GenericYaml a) Source # | |
| FromYaml a => FromYaml (Commented a) Source # | The value with the comments of its entry, or of its node if it has no key, copied like every decoded text. |
| FromYaml a => FromYaml (Located a) Source # | The value with the offset of its node. The key of an entry goes to the
value inside, e.g. for a |
| FromYaml a => FromYaml (Maybe a) Source # | Null is
|
| FromYaml a => FromYaml [a] Source # | |
| HasResolution a => FromYaml (Fixed a) Source # | A number that is a multiple of the step of the type, e.g. If the resolution is not a product of 2s and 5s, e.g. 3, most multiples of
the step have no decimal form, so they cannot come from YAML. For such a
resolution, use |
| (Ord k, FromYaml k, FromYaml v) => FromYaml (Map k v) Source # | Two keys that convert to the same key, e.g. Each key decodes with the instance of its type, so a map with
|
| (FromYaml a, FromYaml b) => FromYaml (Either a b) Source # | A mapping with one key,
|
| (FromYaml a1, FromYaml a2) => FromYaml (a1, a2) Source # | |
| FromYaml a => FromYaml (Const a b) Source # | The value inside. |
| (FromYaml a1, FromYaml a2, FromYaml a3) => FromYaml (a1, a2, a3) Source # | |
| (FromYaml a1, FromYaml a2, FromYaml a3, FromYaml a4) => FromYaml (a1, a2, a3, a4) Source # | |
| (FromYaml a1, FromYaml a2, FromYaml a3, FromYaml a4, FromYaml a5) => FromYaml (a1, a2, a3, a4, a5) Source # | |
| (FromYaml a1, FromYaml a2, FromYaml a3, FromYaml a4, FromYaml a5, FromYaml a6) => FromYaml (a1, a2, a3, a4, a5, a6) Source # | |
| (FromYaml a1, FromYaml a2, FromYaml a3, FromYaml a4, FromYaml a5, FromYaml a6, FromYaml a7) => FromYaml (a1, a2, a3, a4, a5, a6, a7) Source # | |
| (FromYaml a1, FromYaml a2, FromYaml a3, FromYaml a4, FromYaml a5, FromYaml a6, FromYaml a7, FromYaml a8) => FromYaml (a1, a2, a3, a4, a5, a6, a7, a8) Source # | |
| (FromYaml a1, FromYaml a2, FromYaml a3, FromYaml a4, FromYaml a5, FromYaml a6, FromYaml a7, FromYaml a8, FromYaml a9) => FromYaml (a1, a2, a3, a4, a5, a6, a7, a8, a9) Source # | |
| (FromYaml a1, FromYaml a2, FromYaml a3, FromYaml a4, FromYaml a5, FromYaml a6, FromYaml a7, FromYaml a8, FromYaml a9, FromYaml a10) => FromYaml (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) Source # | |
Parser
A parser of nodes. Its errors point to the node that the parser works on,
unless failAt names another one.
The parser has no Alternative instance. To try
another parser after a failure, use orElse. To reject a value, fail with
a message that says why:
port <- parseYaml n unless (port > 0 && port < 65536) $ fail "the port must be from 1 to 65535"
The applicative operators collect the errors of both parts: <*>, *>,
<*, liftA2, and the functions that use them, e.g. traverse,
mapM on a list and for_. This parser gives the errors of
the unknown keys and of all fields together:
rejectUnknownKeys ["name", "paths"] o *> (Config <$> parseField o "name" <*> parseField o "paths")
>>= and >> stop at the first error. A statement of a do block also
stops, e.g. the check of the port above. The functions that use >> also
stop, e.g. mapM_ and forM_.
The choice between them changes only the errors, never the result. With
ApplicativeDo, GHC turns the independent statements of a do block that
ends with pure into <*>. Then they collect errors.
runParser :: (Node -> Parser a) -> Node -> Either (NonEmpty (Offset, String)) a Source #
Run a parser on a node. Each error is the offset of the node that caused it and the message. The errors are in the order of the offsets, and equal errors come only once. A note on an error comes right after it, e.g. the first key of a duplicate key.
First, the function makes the checks of decodeDocument on the
node, e.g. for duplicate keys. It also replaces each alias with the node
that the alias refers to. If a check fails, the result has only the error
of that check, with its notes.
parseNode :: (Node -> Parser a) -> Node -> Parser a Source #
Run a parser on a node, so that fail points to the node.
typeMismatch :: String -> Node -> Parser a Source #
Fail with an error that the node is not of the expected kind, e.g.
typeMismatch "a list" n gives "expected a list, but got a string".
orElse :: Parser a -> Parser a -> Parser a infixl 3 Source #
Run the second parser if the first one fails. A port can be a number or a name:
>>>:{newtype Port = Port (Either Integer T.Text) deriving stock (Show) instance FromYaml Port where parseYaml n = Port <$> ((Left <$> withInt pure n) `orElse` (Right <$> withText pure n)) :}
>>>decodeText @Port "8080"Right (Port (Left 8080))
>>>decodeText @Port "http"Right (Port (Right "http"))
If both parsers fail, the result has only the errors of the second one:
>>>either printErrors print (decodeText @Port "[80]")input.yaml:1:1: expected a string, but got a list | 1 | [80] | ^
Views
The value of a node with its tag resolved. The items and the entries of a
collection stay nodes of the syntax tree. A tag that the schema does not
know does not matter, e.g. !secret abc is a string.
Constructors
| NullView | |
| BoolView !Bool | |
| IntView !Integer | |
| FloatView !FloatValue | |
| StringView !Text | |
| SequenceView ![Node] | |
| MappingView ![(Node, Node)] | |
| AliasView !Text | An alias, e.g. in a node from |
Instances
| Generic View Source # | |||||
Defined in Yamlet.Internal.View Associated Types
| |||||
| Show View Source # | |||||
| Eq View Source # | |||||
| type Rep View Source # | |||||
Defined in Yamlet.Internal.View type Rep View = D1 ('MetaData "View" "Yamlet.Internal.View" "yamlet-1.0.0.1-2nRkbEnNUrLFBALTocq2AS" 'False) (((C1 ('MetaCons "NullView" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "BoolView" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Bool))) :+: (C1 ('MetaCons "IntView" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Integer)) :+: C1 ('MetaCons "FloatView" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 FloatValue)))) :+: ((C1 ('MetaCons "StringView" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Text)) :+: C1 ('MetaCons "SequenceView" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 [Node]))) :+: (C1 ('MetaCons "MappingView" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 [(Node, Node)])) :+: C1 ('MetaCons "AliasView" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Text))))) | |||||
describeNode :: Node -> String Source #
The kind of a node in plain words, e.g. "a list".
>>>map describeNode <$> decodeText @[Node] "- [1, 2]\n- 3.5\n- ~\n- !!str 12\n"Right ["a list","a floating-point number","null","a string"]
Scalars
withFloat :: (Double -> Parser a) -> Node -> Parser a Source #
The nearest double. An integer counts as a floating-point number too.
withScientific :: (Scientific -> Parser a) -> Node -> Parser a Source #
The exact value of a finite number. An integer counts too, and negative zero becomes 0.
A conversion to an exact type, e.g. with truncate, is safe for untrusted
input. An integer has the digits of its text, and the decoder rejects a
float whose exponent in scientific notation is beyond the range from -1000
to 1000, also in a node that a program built.
withText :: (Text -> Parser a) -> Node -> Parser a Source #
The text of a string. The text is a copy, so it does not keep the input
alive. For a plain scalar that YAML reads as a number or a boolean, e.g.
3.10, the error suggests quotes.
oneOf :: [(Text, a)] -> Node -> Parser a Source #
The value that goes with the string in the list of pairs, e.g. for names that the program knows only at run time. An empty list rejects every value. The errors are the same as for the constructors of an enumeration. An unknown name gets the closest name or the list of names:
>>>:{newtype Size = Size Int deriving stock (Show) instance FromYaml Size where parseYaml = oneOf [("small", Size 1), ("large", Size 2)] :}
>>>decodeText @Size "large"Right (Size 2)
>>>either printErrors print (decodeText @Size "lage")input.yaml:1:1: unknown value "lage", did you mean "large"? | 1 | lage | ^
Collections
withSequence :: ([Node] -> Parser a) -> Node -> Parser a Source #
The items of a sequence. As for withMapping, the comments of the
sequence stay with it, not with its first item.
parseItems :: (Node -> Parser a) -> [Node] -> Parser [a] Source #
The values of the items, with the errors of all items, as with mapM.
Unlike mapM, the stack does not grow with the number of items, because
the errors and the values are in accumulators until the end. A decoder of
a list is withSequence (parseItems parseYaml).
withMapping :: (Object -> Parser a) -> Node -> Parser a Source #
The entries of a mapping. As for withText, the tag of a string key does
not matter, so two string keys with the same text are an error, e.g. a
and !foo a.
The comments of the mapping stay with it, not with its first key, e.g. a comment at the top of a file. A record has no place for them.
objectNode :: Object -> Node Source #
The node of the mapping.
objectEntries :: Object -> [(Node, Node)] Source #
The entries of the mapping in the order of the input.
objectKeys :: Object -> [Text] Source #
The string keys of the mapping in the order of the input.
lookupKey :: Object -> Text -> Parser (Maybe Node) Source #
The value of a key, or Nothing if the key is missing. As for
parseField, a key with the same text that is not a string, e.g. 404,
is an error, so that its value does not go away.
parseField :: FromYaml a => Object -> Text -> Parser a Source #
The value of a key. It is an error if the key is missing.
parseFieldMaybe :: FromYaml a => Object -> Text -> Parser (Maybe a) Source #
The value of a key, or Nothing if the key is missing or its value is
null.
parseFieldIfPresent :: FromYaml a => Object -> Text -> Parser (Maybe a) Source #
The value of a key, or Nothing if the key is missing. Unlike
parseFieldMaybe, a null value goes to the parser of the value, e.g.
gives Maybe a for a null value.Just Nothing
>>>:{newtype Limit = Limit (Maybe (Maybe Int)) deriving stock (Show) instance FromYaml Limit where parseYaml = withMapping $ \o -> Limit <$> parseFieldIfPresent o "limit" :}
>>>decodeText @Limit "limit: null\n"Right (Limit (Just Nothing))
>>>decodeText @Limit "{}"Right (Limit Nothing)
parseFieldDefault :: FromYaml a => Object -> Text -> a -> Parser a Source #
The value of a key, or the default if the key is missing or its value is null.
>>>:{newtype Server = Server Int deriving stock (Show) instance FromYaml Server where parseYaml = withMapping $ \o -> Server <$> parseFieldDefault o "port" 80 :}
>>>decodeText @Server "{}"Right (Server 80)
>>>decodeText @Server "port: null\n"Right (Server 80)
>>>decodeText @Server "port: 8080\n"Right (Server 8080)
parseFieldWith :: (Node -> Parser a) -> Object -> Text -> Parser a Source #
Like parseField, with the given parser for the value, e.g. to check a
value without a new type for it. The errors of the parser point to the
value. The parser gets only the value, so it cannot keep the comments of
the key, as a Commented field does with parseField.
>>>:{newtype Port = Port Int deriving stock (Show) instance FromYaml Port where parseYaml = withMapping $ \o -> Port <$> parseFieldWith number o "port" where number :: Node -> Parser Int number = withInt $ \i -> if i >= 1 && i <= 65535 then pure (fromInteger i) else fail "expected a port from 1 to 65535" :}
>>>decodeText @Port "port: 80\n"Right (Port 80)
>>>either printErrors print (decodeText @Port "port: 70000\n")input.yaml:1:7: port: expected a port from 1 to 65535 | 1 | port: 70000 | ^
parseFieldMaybeWith :: (Node -> Parser a) -> Object -> Text -> Parser (Maybe a) Source #
Like parseFieldMaybe, with the given parser for the value, as in
parseFieldWith. The result is Nothing if the key is missing or its
value is null. The parser never gets a null value, so a missing key and a
null value mean the same.
>>>:{newtype Job = Job (Maybe Int) deriving stock (Show) instance FromYaml Job where parseYaml = withMapping $ \o -> Job <$> parseFieldMaybeWith positive o "retries" where positive :: Node -> Parser Int positive = withInt $ \i -> if i > 0 then pure (fromInteger i) else fail "expected a positive number" :}
>>>decodeText @Job "{}"Right (Job Nothing)
>>>decodeText @Job "retries: null\n"Right (Job Nothing)
>>>decodeText @Job "retries: 3\n"Right (Job (Just 3))
To give a null value to the parser, use parseFieldIfPresentWith.
parseFieldIfPresentWith :: (Node -> Parser a) -> Object -> Text -> Parser (Maybe a) Source #
Like parseFieldIfPresent, with the given parser for the value, as in
parseFieldWith. The result is Nothing only if the key is missing.
A null value goes to the parser, so the parser can give it a meaning of
its own. Here a missing key takes the default limit, and null means no
limit:
>>>:{data Limit = Unlimited | Limit Int deriving stock (Show) newtype Job = Job (Maybe Limit) deriving stock (Show) instance FromYaml Job where parseYaml = withMapping $ \o -> Job <$> parseFieldIfPresentWith limit o "limit" where limit :: Node -> Parser Limit limit n = case view n of NullView -> pure Unlimited _ -> withInt (pure . Limit . fromInteger) n :}
>>>decodeText @Job "{}"Right (Job Nothing)
>>>decodeText @Job "limit: null\n"Right (Job (Just Unlimited))
>>>decodeText @Job "limit: 3\n"Right (Job (Just (Limit 3)))
With parseFieldMaybeWith, the null value would give Nothing, the
same as the missing key.
parseFieldDefaultWith :: (Node -> Parser a) -> Object -> Text -> a -> Parser a Source #
Like parseFieldDefault, with the given parser for the value, as in
parseFieldWith. The parser never gets a null value.
>>>:{newtype Job = Job Int deriving stock (Show) instance FromYaml Job where parseYaml = withMapping $ \o -> Job <$> parseFieldDefaultWith positive o "retries" 1 where positive :: Node -> Parser Int positive = withInt $ \i -> if i > 0 then pure (fromInteger i) else fail "expected a positive number" :}
>>>decodeText @Job "{}"Right (Job 1)
>>>decodeText @Job "retries: 3\n"Right (Job 3)
rejectUnknownKeys :: [Text] -> Object -> Parser () Source #
Fail at each key that is not in the list. If a key in the list is close
to an unknown key, e.g. "host" to "hots", its error suggests it. Otherwise
the error lists the known keys. An empty list accepts only an empty
mapping, e.g. for a value written as {}.
A key that is not a string, but has the text of a known key, e.g. true,
is left to the lookup of that key, e.g. parseField or lookupKey,
which reports it.