Alch-Emi/elm-kdl/2.0.0

NOTE: the latest version is 2.0.1

Rerouted (renamed, verified). The GitHub user or repository was renamed. The tag is still there, at the new name. The Lamdera package server serves a copy recovered from https://github.com/alch-emi/elm-kdl.

What we checked: The repo moved to github.com/alch-emi/elm-kdl. Rebuilt with git archive from commit 8364a6ebf408 of https://github.com/alch-emi/elm-kdl (zip prefix Alch-Emi-elm-kdl-8884690/, America/Los_Angeles times): the zip is byte-identical to the one package.elm-lang.org recorded, SHA-1 ac04c7f4cf70f96d7b9e0448d68ba667689f4b26.

The Lamdera compiler uses this copy and prints a one-line notice saying so. elm itself fails to download this version.

Details · package.elm-lang.org's zip: https://github.com/Alch-Emi/elm-kdl/zipball/2.0.0/

Kdl.Decode

Tools for building a translator from KDL to your Elm datastructure

This module follow's the same decoding philosophy as Elm uses for its JSON decoders. If you haven't read the JSON chapter of Elm's official guide, I recommend it! It's a 5-10 minute read, and it'll give you a great high-level overview that'll make working with this module a lot easier.

In short, this philosophy centers around Decoders, which are objects that convert data from KDL (or another format) into Elm data structures. At first, you start out with very simple decoders, like a decoder that simply converts a KDL string into an Elm string. What makes this technique powerful, though, is that decoders snap together like legos to construct bigger decoders. The functions that combine these decoders are known as combinators.

By piecing together these simple decoders with simple combinators, you can construct a decoder for pretty much any datastructure you can imagine, and adding validation and intuitive error messages is a snap!

One distinction between this library and Elm's JSON library is that JSON objects all belong to one "class", so Elm only needs one type of decoder. In KDL, values, properties, and nodes behave differently, so this library uses a different decoder type for each. Fear not though! A simple set of combinators is provided for each, and the documentation will make it clear which one you need.

Where The Magic Happens

decode : DocumentDecoder e t -> Document -> Result (Error e) t

Use a decoder to decode a KDL document

At the end of the day, once you've built up your KDL decoder, this function is how you use it. Just provide your decoder and a Document to decode, and you'll either get back the data structure your decoder was designed for, or one of your error types if something went wrong.

Types

These are the decoder types. They fit together much like you would expect: DocumentDecoders are built out of NodeDecoder, and NodeDecoders are built out of ValueDecoders and DocumentDecoders (for decoding child nodes).

You'll notice that all of the decoders here are generic over two types.

  • The last type, t is the output type of the decoder-- The datastructure that the decoder spits out.
  • The e type is your custom error type. For maximum flexibility, you can choose whatever type you want here. The quickest and easiest way to get started is to just use the unit type (aka the empty tuple) (). This will let you build fast, but you won't get much information about what went wrong when a decode fails. At some point, you'll probably want actual error messages, so you may want to use a String or even a custom enum to represent possible errors.
type alias ValueDecoder e t =
Decoder Value e t

A decoder for simple KDL values, like strings, numbers, and booleans

type alias NodeDecoder e t =
Decoder EnumeratedNode e t

A decoder for KDL nodes

type alias DocumentDecoder e t =
Decoder EnumeratedDocument e t

A decoder for KDL documents, and equivalently, child blocks of KDL nodes

'cause they're both just lists of Nodes.

Combinators

Combinators are what you use to combine decoders. The ones in this section are generic, so you can use them with any of the five decoders.

map : (i -> o) -> Decoder f e i -> Decoder f e o

Alter the output type of an existing combinator

Sometimes a simple existing decoder isn't enough. Suppose you want to decode a percentage, but in KDL it's represented as an integer between 0 and 100, but in your datastructure, you want to represent it as a floating point between 0.0 and 1.0. You could represent that decoder like this:

-- the `float` decoder automatically converts integers to floats
decodePercent = float
    |> map (\input -> input / 100.0)

Note: This combinator is generic, and can be used with any of the three decoders in this module.

map2 : (t1 -> t2 -> o) -> Decoder f e t1 -> Decoder f e t2 -> Decoder f e o

Require two things to be true of a value

For those familiar with the JSON decoding module, this function is included to make you feel at home. But most of the time you'll probably want to use the andApply function instead.

This function runs TWO decoders on one object. The decode succeeds only if both of the component decoders succeed. If that's the case, then the output of each constituent decoder is fed to the provided function.

Note: This combinator is generic, and can be used with any of the three decoders in this module.

andApply : Decoder f e i -> Decoder f e (i -> o) -> Decoder f e o

Add another requirement to a decoder and combine their values through application

This can be very helpful when defining decoders for documents, properties, and arguments, especially when combined with succeed. This can be a little easier to see through example:

type MyWeirdDatatype = MyWeirdDatatype String Int


mkMyWeirdDatatype : Int -> String -> Int -> MyWeirdDatatype
mkMyWeirdDatatype x str y = MyWeirdDatatype str (x - y)


decodeMyWeirdData : DocumentDecoder String MyWeirdDatatype
decodeMyWeirdData =
    succeed mkMyWeirdDatatype
        |> andApply (argument int)
        |> andApply (argument string)
        |> andApply (argument int)
        |> andRequire noOtherArguments
        |> andRequire noOtherProperties
        |> andRequire noChildren
        |> singleNodeDocument "my-weird-data"


(parse "my-weird-data 5 meowy 2" |> Result.map (decode decodeMyWeirdData)) == Ok (Ok (MyWeirdDatatype "meowy" 3))

Note: This combinator is generic, and can be used with any of the three decoders in this module, though most of the time you'll be using it with NodeDecoders.

andRequire : Decoder f e ignore -> Decoder f e t -> Decoder f e t

A version of andApply that ignores the output of its first argument and while still adding the constraints

Note: This combinator is generic, and can be used with any of the three decoders in this module, though most of the time you'll be using it with NodeDecoders.

andThen : (i -> Decoder f e o) -> Decoder f e i -> Decoder f e o

Run a decoder, then run a function on the result to produce a new decoder, which is also run.

Sometimes the behaviour of the decoder going forward is contingent on values already decoded. This is the function to turn to in that case. Analagous to Json.Decode.andThen.

Note: This combinator is generic, and can be used with any of the three decoders in this module.

succeed : t -> Decoder f e t

A decoder that always succeeds and produces a value of your choice

Note: This combinator is generic, and can be used with any of the three decoders in this module.

Values

Values are the most basic building block of KDL, and can be used to construct decoders for arguments and properties.

rational : ValueDecoder e BigRational

Decode any rational number

This will handle integers, numbers with decimals, and even numbers with exponents, like 120.9312e100, however, it can't decode special "numbers" like NaN (#nan) or infinity (#inf), and will return its first argument as an error if applied to any of these.

Of course, this will also produced a WrongType error when applied to non-numeric data, like strings or booleans.

float : ValueDecoder e Float

Decode a number as a floating point

Unlike rational, this can decode values like NaN (#nan) and infinity (#inf), but may not necessarily represent a number with maximal position, and can introduce minor numeric errors, so isn't ideal for situations where absolute accuracy is important.

This will produced a WrongType error when applied to non-numeric data, like strings or booleans.

int : ValueDecoder e Int

Decode a number as an integer

This will decode a value as an integer. However, it will fail (and return its first argument as an error) if used to decode non-integer data, or any numbers too large or to small to be represented by Elm's Int type.

Note that 1.1e1 and 1.0 are considered integers.

This will produced a WrongType error when applied to non-numeric data, like strings or booleans.

string : ValueDecoder e String

Decode a string

This will decode any quoted string, bare string, raw string, or multiline string of any variety into an Elm String

This will produced a WrongType error when applied to non-string data, like numbers or booleans.

bool : ValueDecoder e Bool

Decode a Boolean

This will decode #true and #false into their Elm Bool equivalents

This will produce a WrongType error when applied to non-boolean data, like numbers or strings.

null : ValueDecoder e ()

Require a value to be #null

This decoder only succeeds when applied to the KDL value #null. Its return value is always unit, aka the empty tuple, aka ().

This will return a WrongType error if anything other than null is decoded.

guardV : e -> (t -> Bool) -> ValueDecoder e t -> ValueDecoder e t

Require that a condition be true of the value returned by a ValueDecoder

If that condition isn't true, then an error of your choice is raised. Here's a simple example. Say you want a node count that only accepts non-negative integers as an argument. You could describe this node like this:

decodeCount =
    argument (
        int
        |> guardV "can't have a negative count!" (\x -> x >= 0)
    )
    |> andRequire noOtherArguments
    |> singleNodeDocument "count"

See also: guardN, the NodeDecoder equivalent of this function

failV : e -> ValueDecoder e never

A decoder which always fails with an error of your choice rather than parsing a value

oneOfV : List (ValueDecoder e t) -> ValueDecoder e t

Try several different decoders to see if any work

Sometimes, you'll need to decode a value which could have one of several possible types. For example, say you have a date node which can be followed by either an integer (which should be interpretted as a UNIX timestamp) or a string (which should be interpretted as a string). For example, say you have a date node which can be followed by either an integer (which should be interpretted as a UNIX timestamp) or a string (which should be interpretted as a string). In this case, you can't just decode that value using string or int — you'll need to try both to see which one works. Here's how you can do that with oneOfV:

type MyDate
    = Datestring String
    | UnixTime Int


decodeDate : DocumentDecoder String MyDate
decodeDate =
    argument
        (
            oneOfV
                [ string |> map Datestring
                , int |> map UnixTime
                ]
        )
    |> andRequire noOtherArguments
    |> singleNodeDocument "date"


(parse "date 1733882937" |> Result.map (decode decodeDate)) == Ok (Ok (UnixTime 1733882937))

oneOfV takes a list of decoders that should be tried.

If all provided decoders fail, a PossibilitiesExhausted error is produced

Arguments

Arguments are values applied directly to nodes, like my-node 1 2 "hello" myprop=10. In this example, 1, 2, and "hello" are all arguments. myprop=10 is not an argument, but a property, which is the other way of associating values with a node.

The decoders in this section are concerned with making sure that nodes have the right number of arguments, in the right order (if there is one).

argument : ValueDecoder e t -> NodeDecoder e t

Add a required argument to a node

The argument takes the form of a ValueDecoder, which will be used to decode the argument itself. If the argument is absent, however, a TooFewArguments error will be returned

One thing to keep in mind is that arguments are order sensitive. Arguments will be expected to appear in the same order that they are applied to the NodeDecoder being built. That is:

-- Decodes `tuple hi 1` but not `tuple 1 hi`
tuple1 = succeed pair
    |> andApply (argument string)
    |> andApply (argument int)
    |> andRequire noOtherArguments
    |> singleNodeDocument "tuple"


-- Decodes `tuple 1 hi` but not `tuple hi 1`
tuple2 = succeed pair
    |> andApply (argument int)
    |> andApply (argument string)
    |> andRequire noOtherArguments
    |> singleNodeDocument "tuple"

Similarly, if noOtherArguments or arguments is used, they should be come after all other arguments.

If you'd rather the argument be completely optional, see optionalArgument. If you would like to decode an arbitrary number of arguments all using the same decoder, see arguments.

This function mirrors the behavior of property, but for arguments rather than properties.

optionalArgument : ValueDecoder e t -> NodeDecoder e (Maybe t)

Add an optional argument to a node

If any (not-yet-decoded) argument is present, this decodes it with the provided decoder. Notice that the returned parser decodes to Maybe t rather than just t. This is because Nothing will be the result when the argument is absent.

Please read the documentation for argument for important details regarding ordering.

This mirrors the behavior of optionalProperty for arguments rather than properties.

arguments : ValueDecoder e t -> NodeDecoder e (List t)

Decodes all remaining arguments into a list

Requires that you provide a decoder which will be used on all arguments which have not yet been decoded. The results of each decode will be compiled into a list.

This mirrors the behavior of properties but for arguments rather than properties.

noOtherArguments : NodeDecoder e ()

Automatically fails if there are any arguments which have not yet been decoded

By default, if there are trailing arguments which don't get decoded, they are simply ignored. For example, the pair parser from the argument documentation will parse tuple 1 hi, but also tuple 1 hi #nan etc etc. This behavior isn't always optimal, since it can lead to users not realizing they're misusing something. Applying this function causes your parser to error if there are any trailing arguments.

Also notice that noOtherArguments and arguments mutually make the other redundant. When noOtherArguments comes first, arguments is guaranteed to decode nothing (and produce the empty list). Meanwhile, if arguments comes first, noOtherArguments is guaranteed to succeed, as arguments is guaranteed to decode all the arguments if it succeeds.

This mirrors the behavior of noOtherProperties but for arguments rather than properties.

Produces a TooManyArguments error if additional arguments are present

Properties

Properties are key/value associations tied to nodes. For example in my-node hello=world favorite-number=1080, there's two properties: One called "hello" with the value "world", and one called "favorite-number" with the value 1080. Property keys are always strings, while property values can be any KDL value.

This section is dedicated to making sure that all the right properties are specified, and none of the wrong ones.

property : String -> ValueDecoder e t -> NodeDecoder e t

Require that a node have a certain property

This adds a requirement of a certain property to a node, which must be present on the node for the parse to succeed. If the property is missing from the node, a PropertyAbsent error will be returned. Otherwise, it's decoded using the decodePropertyValue node. propertyKey is the name of the property.

Note that this is order sensitive. When building a decoder, if you first decode a property, then use properties or noOtherProperties, this property will be "accounted for", and not count towards the properties decoded by either of those two functions. Inversely, if you run noOtherProperties before running property, the resulting decoder will always fail, either because the property was present, but got passed to noOtherProperties first, or because it was absent.

This function mirrors the behavior of argument but for properties rather than arguments.

optionalProperty : String -> ValueDecoder e t -> NodeDecoder e (Maybe t)

Optionally decode a property

The resulting node decoder will produce Nothing when the property is absent, or Just when the property is present and can be decoded by the provided decoder.

See property for important notes regarding ordering

As with other node decoders, you'll probably want to chain this with other decoders using andThen and succeed.

This mirrors the behavior of optionalArgument for properties rather than arguments.

properties : ValueDecoder e t -> NodeDecoder e (Dict String t)

Decodes an arbitrary number of properties agnostic regardless of key

You provide a decoder for the value attached to each key, and the decoder decodes each value, and produces a Dict mapping each property name to the resulting value.

Pay attention to the ordering between this and property/optionalProperty. Putting either property or optionalProperty will exclude that property from the set of properties decoded by this function.

This is, in effect, mutually exclusive with noOtherProperties

This mirrors the behavior of arguments but for properties rather than arguments.

noOtherProperties : NodeDecoder e ()

Automatically fail if any properties have yet to be decoded

By default, if there are properties which don't get decoded, they are simply ignored. This behavior isn't always optimal, since it can lead to users not realizing they're misusing something. Applying this function causes your parser to error if there are any arguments that are unaccounted for.

This mirrors the behavior of noOtherArguments but for properties rather than arguments.

Children

These decoders help you decode the children of a node. If you don't specify either of these, then by default your decoder will simply ignore any children which are present. It's recommended that you do specify a behavior though, so that accidental misuse is met by a helpful error rather than silent acceptance.

children : DocumentDecoder e t -> NodeDecoder e t

Parse the children of a node

The children of a node is parsed as just another document. Any of the functions you use to create DocumentDecoders can be used to construct a document decoder which works for the children of a node.

By default, any child nodes are simply ignored without fanfare, but you can specify a specific expectation by either using this function or noChildren, which can ensure that the child nodes are what you expect.

See also: document

noChildren : NodeDecoder e ()

Assert that a node does not have any children

Instead of simply ignoring the children of a node, it's often preferable to ensure that the node definitely does not have children, and therefore is less likely to be being used incorrectly. This function concisely accomplishes that.

Equivalent to children noOtherEntries

See also: children, noOtherEntries

Nodes

These decoders help you get a little bit more information about your nodes. Note that the Arguments, Properties, and Children all also contain NodeDecoders as well, but this section is dedicated to decoders for node metadata alone.

nodeName : NodeDecoder e String

Decode a node, yeilding its name

It's still possible to decode other parts of the node using other node decoders. It's often helpful to use a pattern like the one demonstrated in the example of andApply. If you just need to check that the name is equal to a certain value, you might prefer assertNodeName.

guardN : e -> (t -> Bool) -> NodeDecoder e t -> NodeDecoder e t

Require that a condition be true of the value returned by a NodeDecoder

If that condition isn't true, then an error of your choice is raised. Here's one way you could use this to parse a list that needs at least three elments:

-- Decodes "favorite-things \"raindrops on roses\" \"whiskers on kittens\" \"bright copper kettles\""
-- But fails on "favorite-things \"pussy willows\" \"christmas\""
decodeFavoriteThings =
    arguments string
    |> guardN "You need to least at least three of your favorite things" (\l -> length l >= 3)
    |> singleNodeDocument "favorite-things"

See also: guardV, the ValueDecoder equivalent of this function

failN : e -> NodeDecoder e never

A decoder which always fails with an error of your choice rather than parsing a node

Documents & Child Blocks

There are several strategies you can use to decode documents and child blocks (which are treated as equivalent by this decoder). This section is broken up by strategy.

Property-List Documents

One of the more common types of documents you'll see has a series of nodes which each encode some property. For example, the block of child nodes for an employee record might contain a name node, an optional birthday node, and a several project nodes. Note that this does not fit neatly as a dict-like node (from the next section) since there are a fixed number of keys, and each key has a special meaning.

In parse this example, you could parse the employee's name with [entry] (#entry), their birthday with optionalEntry, and their project with entries. This method would be agnostic to the order of the properties in the block, but would require exactly one name and no more more one birthday.

type alias EmployeeData =
    { name : String
    , birthday : Maybe Int
    , projects : List String
    }


decodeEmployeeData = succeed EmployeeData
    |> andApply (entry "name" (argument string))
    |> andApply (optionalEntry "birthday" (argument int))
    |> andApply (entries "project" (argument string))
    |> andRequire noOtherEntries

This decoder could successfully decode this document:

name bob
birthday 944937799
project big-freaking-contraption
project evil-laser-beam

One thing to note about this parsing strategy is that while most of the rest of the parsing strategies simply take arguments and return a complete Documentdecoder, this parsing strategy looks a little bit more like the parsing strategy for arguments and values, where smaller unit decoders are combined with andApply to form a bigger decoder.

entry : String -> NodeDecoder e t -> DocumentDecoder e t

Decode one entry which should occur exactly once

Returns MissingNode when the entry is absent, and DuplicateEntry if the entry is present more than once

Takes two arguments:

  • The name of the entry (aka the name of the node to look for)
  • A decoder for the node once it's found
entries : String -> NodeDecoder e t -> DocumentDecoder e (List t)

Decode an entry which may occur any number of times (including zero)

Takes two arguments:

  • The name of the entries (aka the name of the nodes to look for)
  • A decoder for the node once it's found
optionalEntry : String -> NodeDecoder e t -> DocumentDecoder e (Maybe t)

Decode an entry which can occur zero or one times

Produces a DuplicateNode error if the entry occurs more than once

Takes two arguments:

  • The name of the entry (aka the name of the node to look for)
  • A decoder for the node once it's found
noOtherEntries : DocumentDecoder e ()

Only succeed when a document has no nodes left

Any nodes proccessed by entry, optionalEntry, and entries are omitted from the count.

If no nodes are present (or remain) at all, then a TooManyNodes error is produced.

This can also be used to decode an empty document.

Dict-Like Documents

A common use of documents is to represent dict-like structures. In these cases, the names of nodes are arbitrary strings which represent keys in a dictionary, and the bodies of those nodes are simple types corresponding to the values in that dictionary.

Crucially, the keys shouldn't be preset keywords, and the values should all be decoded in the same way.

dictDocument : NodeDecoder e t -> DocumentDecoder e (Dict String t)

Decode a dict-like document

For each node in the document, that node is treated like an entry in a dictionary. The name of the node is seen as the key, and the value is what that node decodes to. Consider this subsection of the cargo.kdl official KDL example:

dependencies {
    nom "6.0.1"
    thiserror "1.0.22"
}

We could parse this like so:

decodeDependencyVersion : NodeDecoder String String
decodeDependencyVersion =
    argument string
    |> andRequire noOtherArguments
    |> andRequire noOtherProperties
    |> andRequire noChildren


decodeDependencies : DocumentDecoder String (Dict String String)
decodeDependencies =
    children (dictDocument decodeDependencyVersion)
    |> andRequire noOtherArguments
    |> andRequire noOtherProperties
    |> singleNodeDocument "dependencies"

Notice that this will return DuplicateNode if someone tries to specify a dependency twice.

multidictDocument : NodeDecoder e t -> DocumentDecoder e (Dict String (List t))

Decode a document as if it were a multidict (a dict that can have multiple values associated with a single key)

This behaves much like dictDocument, but rather than raise an error when there are multiple nodes with one key,

Single-Node Documents

Sometimes your document only has one node, so you don't need much to parse it!

singleNodeDocument : String -> NodeDecoder e t -> DocumentDecoder e t

Decode a document with exactly one node with a fixed name

Best used when your document only has one node, and that node should always have the same name. If you have multiple types of nodes that could appear, you might prefer to use singleNodeDocumentF.

The first three arguments are error values to return if the document is empty (contains no nodes), if there is an extra node that's unaccounted for, and if the name of the node in the document isn't the expected one.

If more than one node is present, then a TooManyNodes error will be produced. If No nodes are present, a MissingNode error will be produced. If the node has the incorrect name, than an UnexpectedNode error will be produced.

The next two arguments specify the expected name of the node and the NodeDecoder that should be used for it.

singleNodeDocumentF : (String -> Maybe (NodeDecoder e t)) -> DocumentDecoder e t

Decode a document with exactly one node, but where there might be several possible types of nodes.

Rather than only parse a single type of node (with a fixed name), this function lets you conditionally accept or deny multiple types of nodes based on their name.

The argument is expected to be a function which decides what NodeDecoder to use for a given node name. If a node name is unrecognized/unsupported, then returning Nothing will produce a UnexpectedNode error.

If the document being parsed is empty, a NoNodes error is produced. If there is more than one node, an UnexpectedNode error is produced.

Keep in mind that you can return failN to reject a node that has a name you don't support.

See also:

  • singleNodeDocument, a version of this function for a fixed node name
  • documentF, a version of this which can accept multiple nodes, not just one.

Order-Signifigant Documents

The most powerful document decoders are these, which preserve the order of the nodes which they parse rather than forming them into a more ergonomic datastructure. This means that you can arrange the nodes into your own order-aware datastructure, although it forces you to do a little bit more work.

document : Dict String (NodeDecoder e t) -> DocumentDecoder e (List t)

Decode a docmunt with an arbitrary number of nodes

This is the most basic, unspecialized type of document decoder. It simply decodes all of the nodes that appear in the document, in order, choosing a NodeDecoder based on the provided lookup table.

For each node to be decoded, its name will be checked against the provided Dict. If a decoder is found there, it will be returned. Otherwise, an UnexpectedNode error is returned.

It is often helpful to set fallbackNodeDecoder to fail with some error.

For a more powerful version of this, see documentF.

documentF : (String -> NodeDecoder e t) -> DocumentDecoder e (List t)

Decode a document with an arbitrary number of nodes, and the power to choose which decoder to use based on an algorithm

Similar to document, documentF allows for parsing an arbitrary number of nodes in a document, ultimately yeilding a list. But while document chose how to decode a node based on a Dict lookup, and always failed when the node was absent from the dict, documentF allows you to specify your own algorithm for choosing a subsequent decoder.

Error Handling

Sometimes, users or programmers make mistakes, and produce a KDL document which is valid KDL, but still contains mistakes that make it un-decodeable. When this happens, it's important to provide helpful and specific error messages so that the problem can be quickly identified and resolved. This module contains some error handling tools which aim to make this proccess simpler.

type alias Error e =
{ kind : ErrorKind e
, context : List ContextFrame
, location : SourceRange
}

An error which occurs during decoding

Contains information about exactly where the error occured through the context stack, which describes the location in terms of semantics (see ContextFrame), and the location attribute, which describes the location within the original text document.

The kind attribute describes the error itself -- What exactly went wrong. See ErrorKind for more details.

You'll notice that Error and ErrorKind are both parameterized in terms of some type e. That is a custom error type that you can use to pass more information about certain kinds of failures. In particular, when guard methods like guardV or guardN fail, or when you deliberately trigger a failure with the fail decoder, you may use your own error type to describe why the failure occured. If you don't use either of these decoders, feel free to set this type to Never.

type ErrorKind e
= WrongType ValueType ValueContents
| Failed e
| PossibilitiesExhausted (List (Error e))
| TooFewArguments
| TooManyArguments
| PropertyAbsent String
| UnexpectedProperty
| NoNodes
| MissingNode String
| DuplicateNode
| UnexpectedNode
| TooManyNodes

Describes what went wrong in a decoding error

See Error for more information about decoding errors in general.

There are several ways that a decode can fail, all of which are enumerated by this type:

  • WrongType - This error occurs when attempting to decode a number as a string, when trying to decode #inf as a rational, or any other time that the type you're trying to decode into can't handle whatever the actual KDL value is. Takes two arguments, the first being the expected type, and the latter being the actual type.
  • Failed - This error occurs whenever a decoder failure is requested by the programmer, for example in guard methods or by the fail decoder. See the docs for Error for more information.
  • PossibilitiesExhausted - Any time that an error occurs within a oneOf decoder, there tend to be multiple possibilities for why that failure occured, since if any of the branches had succeeded, the decode would have succeeded. When this occurs, this error kind is returned, describing all of the errors that occured for each branch of the oneOf. The order of errors in the list is guaranteed to exactly match the order of cases in the oneOf that triggered the error.
  • TooFewArguments - A node had a minimum number of required arguments that wasn't met
  • TooManyArguments - A node had more arguments than could be decoded
  • PropertyAbsent - A node lacked a required property. The attached string is the name of the missing property.
  • UnexpectedProperty - A property was present that wasn't expected. The name of the property can be found in the context stack.
  • MissingNode - The decoder expected to see a node, but it was absent. The attached string is the name of the node that was expected, or an empty string if an expected name wasn't explicitly passed.
  • DuplicateNode - More than one node appears with a given name, but the document decoder being used doesn't support nodes with duplicate names.
  • UnexpectedNode - A node was seen that the document decoder didn't recognize.
  • TooManyNodes - The decoder expected a specific, finite number of nodes, but saw more than it could handle.
= WithinNode String Int
| WithinProperty String
| WithinValue Int

Describes where in the document tree an error occured

WithinProperty and WithinValue of course describe the name of the property the error occured in and the order of the value that the problem occured in, respectively. WithinNode describes both the name of the node, and the order the node appears within its parent document/child block.

"Deeper" parts of the structure will be located closer to the top/front of the context stack. By the nature of KDL.

= IntType
| RationalType
| FloatType
| StringType
| BoolType
| NullType

Represents each of the possible types that built-in decoders are available for.

Used to describe the expected type in a WrongType error.

showDecodeError : (e -> String) -> Error e -> String

Renders a DecodeError to a string

Takes as an argument the renderer to use for custom errors. If your custom error type is Never, you can pass the Basics.never function here. Custom errors are rendered directly using this function, and the result is returned unmodifier.

Note that while this function will try its best to provide friendly error messages, it may be better in some cases to disect the error yourself and produce error messages tailored to the datastructure that you're decoding.

In particular, the error messages for PossibilitiesExhausted will likely be poor quality.