owanturist / elm-graphql / GraphQL.Selector

Build GraphQL with decoders for turning JSON values into Elm values.

Primitives


type Selector a

A value that knows how to select (receive and decode) a GraphQL values.

string : Selector String

Decode a JSON string into an Elm String.

decodeString string "true"              == Err ...
decodeString string "42"                == Err ...
decodeString string "3.14"              == Err ...
decodeString string "\"hello\""         == Ok "hello"
decodeString string "{ \"hello\": 42 }" == Err ...

bool : Selector Basics.Bool

Decode a JSON string into an Elm Bool.

decodeString bool "true"              == Ok True
decodeString bool "42"                == Err ...
decodeString bool "3.14"              == Err ...
decodeString bool "\"hello\""         == Err ...
decodeString bool "{ \"hello\": 42 }" == Err ...

int : Selector Basics.Int

Decode a JSON number into an Elm Int.

decodeString int "true"              == Err ...
decodeString int "42"                == Ok 42
decodeString int "3.14"              == Err ...
decodeString int "\"hello\""         == Err ...
decodeString int "{ \"hello\": 42 }" == Err ...

float : Selector Basics.Float

Decode a JSON number into an Elm Float.

decodeString float "true"              == Err ..
decodeString float "42"                == Ok 42
decodeString float "3.14"              == Ok 3.14
decodeString float "\"hello\""         == Err ...
decodeString float "{ \"hello\": 42 }" == Err ...

Data Structures

nullable : Selector a -> Selector (Maybe a)

Decode a nullable JSON value into an Elm value.

decodeString (nullable int) "13"    == Ok (Just 13)
decodeString (nullable int) "42"    == Ok (Just 42)
decodeString (nullable int) "null"  == Ok Nothing
decodeString (nullable int) "true"  == Err ...

list : Selector a -> Selector (List a)

Decode a JSON array into an Elm List.

decodeString (list int) "[1,2,3]" == Ok [ 1, 2, 3 ]

decodeString (list bool) "[true,false]" == Ok [ True, False ]

array : Selector a -> Selector (Array a)

Decode a JSON array into an Elm Array.

decodeString (array int) "[1,2,3]" == Ok (Array.fromList [ 1, 2, 3 ])

decodeString (array bool) "[true,false]" == Ok (Array.fromList [ True, False ])

Object Primitives

field : String -> List ( String, Internal.Argument ) -> Selector a -> Selector a

fieldWithAlias : String -> String -> List ( String, Internal.Argument ) -> Selector a -> Selector a

at : List String -> Selector a -> Selector a

Decode a nested JSON object, requiring certain fields.

json = """{ "person": { "name": "tom", "age": 42 } }"""

decodeString (at ["person", "name"] string) json  == Ok "tom"
decodeString (at ["person", "age" ] int   ) json  == Ok "42

This is really just a shorthand for saying things like:

field "person" [] (field "name" [] string) == at [ "person", "name" ] string

index : Basics.Int -> Selector a -> Selector a

Decode a JSON array, requiring a particular index.

json = """[ "alice", "bob", "chuck" ]"""

decodeString (index 0 string) json  == Ok "alice"
decodeString (index 1 string) json  == Ok "bob"
decodeString (index 2 string) json  == Ok "chuck"
decodeString (index 3 string) json  == Err ...

on : List ( String, Selector a ) -> Selector a

Inconsistent Structure

maybe : Selector a -> Selector (Maybe a)

Helpful for dealing with optional fields. Here are a few slightly different examples:

json = """{ "name": "tom", "age": 42 }"""

decodeString (field "age" [] (maybe int)) json    == Ok (Just 42)
decodeString (field "name" [] (maybe int)) json   == Ok Nothing
decodeString (field "height" [] (maybe int)) json == Err ...

Notice the last example! It is saying we must have a field named height and the content may be a float. There is no height field, so the decoder fails.

Point is, maybe will make exactly what it contains conditional. For optional fields, this means you probably want it outside a use of field or at.

oneOf : List (Selector a) -> Selector a

Try a bunch of different selectors. This can be useful if the JSON may come in a couple different formats. For example, say you want to read an array of numbers, but some of them are null.

badInt : Decoder Int
badInt =
    oneOf [ int, null 0 ]

-- decodeString (list badInt) "[1,2,null,4]" == Ok [1,2,0,4]

Why would someone generate JSON like this? Questions like this are not good for your health. The point is that you can use oneOf to handle situations like this!

You could also use oneOf to help version your data. Try the latest format, then a few older ones that you still support. You could use andThen to be even more particular if you wanted.

Mapping

map : (a -> b) -> Selector a -> Selector b

Transform a selector. Maybe you just want to know the length of a string:

stringLength : Selector Int
stringLength =
    map String.length string

It is often helpful to use map with oneOf, like when defining nullable:

nullable : Selector a -> Selector (Maybe a)
nullable selector =
    oneOf
        [ null Nothing
        , map Just selector
        ]

map2 : (a -> b -> c) -> Selector a -> Selector b -> Selector c

Try two selectors and then combine the result We can use this to select objects with many fields:

type alias Point =
    { x : Float, y : Float }

point : Selector Point
point =
    map2 Point
        (field "x" [] float)
        (field "y" [] float)

-- decodeString point """{ "x": 3, "y": 4 }""" == Ok { x = 3, y = 4 }

It tries each individual decoder and puts the result together with the Point constructor.

map3 : (a -> b -> c -> d) -> Selector a -> Selector b -> Selector c -> Selector d

Try three selectors and then combine the result. We can use this to select objects with many fields:

type alias Person =
    { name : String, age : Int, height : Float }

person : Selector Person
person =
    succeed Person
        |> select "name" [] string
        |> select age" [] int
        |> select "height" [] float


-- json = """{ "name": "tom", "info": { "age": 42, "height": 1.8 } }"""
-- decodeString person json == Ok { name = "tom", age = 42, height = 1.8 }

Like map2 it tries each decoder in order and then give the results to the Person constructor. That can be any function though!

map4 : (a -> b -> c -> d -> e) -> Selector a -> Selector b -> Selector c -> Selector d -> Selector e

map5 : (a -> b -> c -> d -> e -> f) -> Selector a -> Selector b -> Selector c -> Selector d -> Selector e -> Selector f

map6 : (a -> b -> c -> d -> e -> f -> g) -> Selector a -> Selector b -> Selector c -> Selector d -> Selector e -> Selector f -> Selector g

map7 : (a -> b -> c -> d -> e -> f -> g -> h) -> Selector a -> Selector b -> Selector c -> Selector d -> Selector e -> Selector f -> Selector g -> Selector h

map8 : (a -> b -> c -> d -> e -> f -> g -> h -> i) -> Selector a -> Selector b -> Selector c -> Selector d -> Selector e -> Selector f -> Selector g -> Selector h -> Selector i

Chaining

select : String -> List ( String, Internal.Argument ) -> Selector a -> Selector (a -> b) -> Selector b

selectWithAlias : String -> String -> List ( String, Internal.Argument ) -> Selector a -> Selector (a -> b) -> Selector b

Run Selectors


type alias Value =
Json.Decode.Value

A JSON value.

render : Selector a -> String

Render GraphQL representation of Selector.

toDecoder : Selector a -> Json.Decode.Decoder a

Build a Decoder of Selector.

decodeString : Selector a -> String -> Result Error a

Parse the given string into a JSON value and then run the Decoder on it. This will fail if the string is not well-formed JSON or if the Decoder fails for some reason.

decodeString int "4"     == Ok 4
decodeString int "1 + 2" == Err ...

decodeValue : Selector a -> Value -> Result Error a

Run a Decoder on some JSON Value. You can send these JSON values through ports, so that is probably the main time you would use this function.

Errors


type Error
    = Field String Error
    | Index Basics.Int Error
    | OneOf (List Error)
    | Failure String Value

A structured error describing exactly how the selector failed. You can use this to create more elaborate visualizations of a selector problem. For example, you could show the entire JSON object and show the part causing the failure in red.

errorToString : Error -> String

Convert a decoding error into a String that is nice for debugging. It produces multiple lines of output, so you may want to peek at it with something like this:

import Html
import Json.Decode as Decode

errorToHtml : Decode.Error -> Html.Html msg
errorToHtml error =
    Html.pre [] [ Html.text (Decode.errorToString error) ]

Note: It would be cool to do nicer coloring and fancier HTML, but we cannot have any HTML dependencies in elm/core. It is totally possible to crawl the Error structure and create this separately though!

Fancy Decoding

andThen : (a -> Selector b) -> Selector a -> Selector b

Create a selector that depend on previous results. Doesn't create depended Selector.

succeed : a -> Selector a

Ignore the JSON and produce a certain Elm value.

decodeString (succeed 42) "true"    == Ok 42
decodeString (succeed 42) "[1,2,3]" == Ok 42
decodeString (succeed 42) "hello"   == Err ... -- this is not a valid JSON string

This is handy when used with `oneOf` or `andThen`.

fail : String -> Selector a

Ignore the JSON and make the selector fail. This is handy when used with oneOf or andThen where you want to give a custom error message in some case.

See the andThen docs for an example.

null : a -> Selector a

Decode a null value into some Elm value.

decodeString (null False) "null" == Ok False
decodeString (null 42) "null"    == Ok 42
decodeString (null 42) "42"      == Err ..
decodeString (null 42) "false"   == Err ..

So if you ever see a null, this will return whatever value you specified.