Add final features to JSON coder module
parent
3f08e4a3e7
commit
28d2a17a10
|
@ -1,691 +0,0 @@
|
||||||
module Internal.Tools.Decode exposing
|
|
||||||
( Decoder, string, bool, int, float
|
|
||||||
, nullable, list, slowDict, fastDict, keyValuePairs
|
|
||||||
, field, at, index, opField, opFieldWithDefault
|
|
||||||
, maybe, oneOf
|
|
||||||
, map, map2, map3, map4, map5, map6, map7, map8, map9, map10, map11
|
|
||||||
, lazy, value, null, succeed, fail, andThen
|
|
||||||
, pString, pBool, pInt, pList
|
|
||||||
)
|
|
||||||
|
|
||||||
{-|
|
|
||||||
|
|
||||||
|
|
||||||
# Advanced security Json.Decode
|
|
||||||
|
|
||||||
This module extends the standard JSON encode / decode library for security
|
|
||||||
measures. Most Elm libraries do not access an API this often without insight
|
|
||||||
for the user, and hence this module aims to offer the user more insight into
|
|
||||||
what is going on.
|
|
||||||
|
|
||||||
Additionally, the decoder will warn for suspicious values, and provide helpful
|
|
||||||
errors when the JSON fails to decode.
|
|
||||||
|
|
||||||
|
|
||||||
## Primitives
|
|
||||||
|
|
||||||
@docs Decoder, string, bool, int, float
|
|
||||||
|
|
||||||
|
|
||||||
## Data structures
|
|
||||||
|
|
||||||
@docs nullable, list, slowDict, fastDict, keyValuePairs
|
|
||||||
|
|
||||||
|
|
||||||
## Object primitives
|
|
||||||
|
|
||||||
@docs field, at, index, opField, opFieldWithDefault
|
|
||||||
|
|
||||||
|
|
||||||
## Inconsistent structure
|
|
||||||
|
|
||||||
@docs maybe, oneOf
|
|
||||||
|
|
||||||
|
|
||||||
## Mapping
|
|
||||||
|
|
||||||
@docs map, map2, map3, map4, map5, map6, map7, map8, map9, map10, map11
|
|
||||||
|
|
||||||
|
|
||||||
## Fancy decoding
|
|
||||||
|
|
||||||
@docs lazy, value, null, succeed, fail, andThen
|
|
||||||
|
|
||||||
|
|
||||||
## Phantom decoding
|
|
||||||
|
|
||||||
Phantom decoders allow you to create phantom types of standard Elm types.
|
|
||||||
|
|
||||||
@docs pString, pBool, pInt, pList
|
|
||||||
|
|
||||||
-}
|
|
||||||
|
|
||||||
import Dict as SDict
|
|
||||||
import FastDict as FDict
|
|
||||||
import Internal.Config.Leaks as L
|
|
||||||
import Internal.Config.Log as Log
|
|
||||||
import Internal.Config.Phantom as Phantom
|
|
||||||
import Internal.Config.Text as Text
|
|
||||||
import Internal.Tools.DecodeExtra as D
|
|
||||||
import Json.Decode as D
|
|
||||||
import Set
|
|
||||||
|
|
||||||
|
|
||||||
{-| A value that knows how to decode JSON values.
|
|
||||||
-}
|
|
||||||
type alias Decoder a =
|
|
||||||
D.Decoder { content : a, messages : List ( String, String ) }
|
|
||||||
|
|
||||||
|
|
||||||
{-| Create decoders that depend on previous results.
|
|
||||||
-}
|
|
||||||
andThen : (a -> Decoder b) -> Decoder a -> Decoder b
|
|
||||||
andThen func =
|
|
||||||
D.andThen
|
|
||||||
(\a ->
|
|
||||||
D.map
|
|
||||||
(\b -> { b | messages = a.messages ++ b.messages })
|
|
||||||
(func a.content)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a nested JSON object, requiring certain fields.
|
|
||||||
-}
|
|
||||||
at : List String -> Decoder a -> Decoder a
|
|
||||||
at =
|
|
||||||
D.at
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON boolean into an Elm bool.
|
|
||||||
-}
|
|
||||||
bool : Decoder Bool
|
|
||||||
bool =
|
|
||||||
D.map empty D.bool
|
|
||||||
|
|
||||||
|
|
||||||
{-| Initialize a standard object for the decoder.
|
|
||||||
-}
|
|
||||||
empty : a -> { content : a, messages : List ( String, String ) }
|
|
||||||
empty x =
|
|
||||||
{ content = x, messages = [] }
|
|
||||||
|
|
||||||
|
|
||||||
{-| Ignore the JSON and make the decoder fail.
|
|
||||||
-}
|
|
||||||
fail : String -> Decoder a
|
|
||||||
fail =
|
|
||||||
D.fail
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON object into a fast Elm dict from miniBill/elm-fast-dict.
|
|
||||||
-}
|
|
||||||
fastDict : Decoder a -> Decoder (FDict.Dict String a)
|
|
||||||
fastDict x =
|
|
||||||
keyValuePairs x
|
|
||||||
|> andThen
|
|
||||||
(\pairs ->
|
|
||||||
let
|
|
||||||
dict =
|
|
||||||
FDict.fromList pairs
|
|
||||||
|
|
||||||
oldLength =
|
|
||||||
List.length pairs
|
|
||||||
|
|
||||||
newLength =
|
|
||||||
FDict.size dict
|
|
||||||
in
|
|
||||||
if oldLength == newLength then
|
|
||||||
succeed dict
|
|
||||||
|
|
||||||
else
|
|
||||||
D.succeed
|
|
||||||
{ content = dict
|
|
||||||
, messages =
|
|
||||||
[ ( Log.warn, Text.decodedDictSize oldLength newLength ) ]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON object, requiring a particular field.
|
|
||||||
-}
|
|
||||||
field : String -> Decoder a -> Decoder a
|
|
||||||
field =
|
|
||||||
D.field
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON number into an Elm flaot.
|
|
||||||
-}
|
|
||||||
float : Decoder Float
|
|
||||||
float =
|
|
||||||
D.map empty D.float
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON array, requiring a particular index.
|
|
||||||
-}
|
|
||||||
index : Int -> Decoder a -> Decoder a
|
|
||||||
index =
|
|
||||||
D.index
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON number into an Elm int.
|
|
||||||
-}
|
|
||||||
int : Decoder Int
|
|
||||||
int =
|
|
||||||
D.map empty D.int
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON object into a list of pairs.
|
|
||||||
-}
|
|
||||||
keyValuePairs : Decoder a -> Decoder (List ( String, a ))
|
|
||||||
keyValuePairs x =
|
|
||||||
D.map
|
|
||||||
(\result ->
|
|
||||||
{ content = List.map (Tuple.mapSecond .content) result
|
|
||||||
, messages =
|
|
||||||
result
|
|
||||||
|> List.map Tuple.second
|
|
||||||
|> List.map .messages
|
|
||||||
|> List.concat
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(D.keyValuePairs x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Sometimes you have JSON with recursive structure, like nested comments.
|
|
||||||
You can use `lazy` to make sure your decoder unrolls lazily.
|
|
||||||
-}
|
|
||||||
lazy : (() -> Decoder a) -> Decoder a
|
|
||||||
lazy =
|
|
||||||
D.lazy
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON array into an Elm list.
|
|
||||||
-}
|
|
||||||
list : Decoder a -> Decoder (List a)
|
|
||||||
list x =
|
|
||||||
D.map
|
|
||||||
(\result ->
|
|
||||||
{ content = List.map .content result
|
|
||||||
, messages =
|
|
||||||
result
|
|
||||||
|> List.map .messages
|
|
||||||
|> List.concat
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(D.list x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Transform a decoder.
|
|
||||||
-}
|
|
||||||
map : (a -> value) -> Decoder a -> Decoder value
|
|
||||||
map func da =
|
|
||||||
D.map
|
|
||||||
(\a ->
|
|
||||||
{ content = func a.content
|
|
||||||
, messages = a.messages
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try two decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value
|
|
||||||
map2 func da db =
|
|
||||||
D.map2
|
|
||||||
(\a b ->
|
|
||||||
{ content = func a.content b.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try three decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value
|
|
||||||
map3 func da db dc =
|
|
||||||
D.map3
|
|
||||||
(\a b c ->
|
|
||||||
{ content = func a.content b.content c.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try four decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value
|
|
||||||
map4 func da db dc dd =
|
|
||||||
D.map4
|
|
||||||
(\a b c d ->
|
|
||||||
{ content = func a.content b.content c.content d.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try five decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value
|
|
||||||
map5 func da db dc dd de =
|
|
||||||
D.map5
|
|
||||||
(\a b c d e ->
|
|
||||||
{ content = func a.content b.content c.content d.content e.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
, e.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
de
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try six decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map6 : (a -> b -> c -> d -> e -> f -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder value
|
|
||||||
map6 func da db dc dd de df =
|
|
||||||
D.map6
|
|
||||||
(\a b c d e f ->
|
|
||||||
{ content = func a.content b.content c.content d.content e.content f.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
, e.messages
|
|
||||||
, f.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
de
|
|
||||||
df
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try seven decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder value
|
|
||||||
map7 func da db dc dd de df dg =
|
|
||||||
D.map7
|
|
||||||
(\a b c d e f g ->
|
|
||||||
{ content = func a.content b.content c.content d.content e.content f.content g.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
, e.messages
|
|
||||||
, f.messages
|
|
||||||
, g.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
de
|
|
||||||
df
|
|
||||||
dg
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try eight decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder value
|
|
||||||
map8 func da db dc dd de df dg dh =
|
|
||||||
D.map8
|
|
||||||
(\a b c d e f g h ->
|
|
||||||
{ content = func a.content b.content c.content d.content e.content f.content g.content h.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
, e.messages
|
|
||||||
, f.messages
|
|
||||||
, g.messages
|
|
||||||
, h.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
de
|
|
||||||
df
|
|
||||||
dg
|
|
||||||
dh
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try 9 decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map9 :
|
|
||||||
(a -> b -> c -> d -> e -> f -> g -> h -> i -> value)
|
|
||||||
-> Decoder a
|
|
||||||
-> Decoder b
|
|
||||||
-> Decoder c
|
|
||||||
-> Decoder d
|
|
||||||
-> Decoder e
|
|
||||||
-> Decoder f
|
|
||||||
-> Decoder g
|
|
||||||
-> Decoder h
|
|
||||||
-> Decoder i
|
|
||||||
-> Decoder value
|
|
||||||
map9 func da db dc dd de df dg dh di =
|
|
||||||
D.map8
|
|
||||||
(\a b c d e f g ( h, i ) ->
|
|
||||||
{ content = func a.content b.content c.content d.content e.content f.content g.content h.content i.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
, e.messages
|
|
||||||
, f.messages
|
|
||||||
, g.messages
|
|
||||||
, h.messages
|
|
||||||
, i.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
de
|
|
||||||
df
|
|
||||||
dg
|
|
||||||
(D.map2 Tuple.pair dh di)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try 10 decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map10 :
|
|
||||||
(a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> value)
|
|
||||||
-> Decoder a
|
|
||||||
-> Decoder b
|
|
||||||
-> Decoder c
|
|
||||||
-> Decoder d
|
|
||||||
-> Decoder e
|
|
||||||
-> Decoder f
|
|
||||||
-> Decoder g
|
|
||||||
-> Decoder h
|
|
||||||
-> Decoder i
|
|
||||||
-> Decoder j
|
|
||||||
-> Decoder value
|
|
||||||
map10 func da db dc dd de df dg dh di dj =
|
|
||||||
D.map8
|
|
||||||
(\a b c d e f ( g, h ) ( i, j ) ->
|
|
||||||
{ content = func a.content b.content c.content d.content e.content f.content g.content h.content i.content j.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
, e.messages
|
|
||||||
, f.messages
|
|
||||||
, g.messages
|
|
||||||
, h.messages
|
|
||||||
, i.messages
|
|
||||||
, j.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
de
|
|
||||||
df
|
|
||||||
(D.map2 Tuple.pair dg dh)
|
|
||||||
(D.map2 Tuple.pair di dj)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try 11 decoders and combine the result.
|
|
||||||
-}
|
|
||||||
map11 :
|
|
||||||
(a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> value)
|
|
||||||
-> Decoder a
|
|
||||||
-> Decoder b
|
|
||||||
-> Decoder c
|
|
||||||
-> Decoder d
|
|
||||||
-> Decoder e
|
|
||||||
-> Decoder f
|
|
||||||
-> Decoder g
|
|
||||||
-> Decoder h
|
|
||||||
-> Decoder i
|
|
||||||
-> Decoder j
|
|
||||||
-> Decoder k
|
|
||||||
-> Decoder value
|
|
||||||
map11 func da db dc dd de df dg dh di dj dk =
|
|
||||||
D.map8
|
|
||||||
(\a b c d e ( f, g ) ( h, i ) ( j, k ) ->
|
|
||||||
{ content = func a.content b.content c.content d.content e.content f.content g.content h.content i.content j.content k.content
|
|
||||||
, messages =
|
|
||||||
List.concat
|
|
||||||
[ a.messages
|
|
||||||
, b.messages
|
|
||||||
, c.messages
|
|
||||||
, d.messages
|
|
||||||
, e.messages
|
|
||||||
, f.messages
|
|
||||||
, g.messages
|
|
||||||
, h.messages
|
|
||||||
, i.messages
|
|
||||||
, j.messages
|
|
||||||
, k.messages
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
da
|
|
||||||
db
|
|
||||||
dc
|
|
||||||
dd
|
|
||||||
de
|
|
||||||
(D.map2 Tuple.pair df dg)
|
|
||||||
(D.map2 Tuple.pair dh di)
|
|
||||||
(D.map2 Tuple.pair dj dk)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Helpful for dealing with optional fields
|
|
||||||
-}
|
|
||||||
maybe : Decoder a -> Decoder (Maybe a)
|
|
||||||
maybe x =
|
|
||||||
D.map
|
|
||||||
(\result ->
|
|
||||||
case result of
|
|
||||||
Just { content, messages } ->
|
|
||||||
{ content = Just content, messages = messages }
|
|
||||||
|
|
||||||
Nothing ->
|
|
||||||
empty Nothing
|
|
||||||
)
|
|
||||||
(D.maybe x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a `null` value into some Elm value.
|
|
||||||
-}
|
|
||||||
null : a -> Decoder a
|
|
||||||
null =
|
|
||||||
D.null >> D.map empty
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a nullable JSON value into an Elm value.
|
|
||||||
-}
|
|
||||||
nullable : Decoder a -> Decoder (Maybe a)
|
|
||||||
nullable x =
|
|
||||||
D.map
|
|
||||||
(\result ->
|
|
||||||
case result of
|
|
||||||
Just { content, messages } ->
|
|
||||||
{ content = Just content, messages = messages }
|
|
||||||
|
|
||||||
Nothing ->
|
|
||||||
empty Nothing
|
|
||||||
)
|
|
||||||
(D.nullable x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Try a bunch of different decoders. This can be useful if the JSON may come
|
|
||||||
in a couple different formats.
|
|
||||||
-}
|
|
||||||
oneOf : List (Decoder a) -> Decoder a
|
|
||||||
oneOf =
|
|
||||||
D.oneOf
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON object, requiring a particular field:
|
|
||||||
|
|
||||||
- If the field does not exist, the decoder decodes `Nothing`
|
|
||||||
- If the field DOES exist, the decoder must always return a `Just content` or fail
|
|
||||||
|
|
||||||
-}
|
|
||||||
opField : String -> Decoder a -> Decoder (Maybe a)
|
|
||||||
opField key x =
|
|
||||||
D.map
|
|
||||||
(\result ->
|
|
||||||
case result of
|
|
||||||
Just { content, messages } ->
|
|
||||||
{ content = Just content, messages = messages }
|
|
||||||
|
|
||||||
Nothing ->
|
|
||||||
empty Nothing
|
|
||||||
)
|
|
||||||
(D.opField key x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON object, requiring a particular field or raising a default:
|
|
||||||
|
|
||||||
- If the field does not exist, the decoder returns the default
|
|
||||||
- If the field DOES exist, the decoder must always return value or fail
|
|
||||||
|
|
||||||
-}
|
|
||||||
opFieldWithDefault : String -> a -> Decoder a -> Decoder a
|
|
||||||
opFieldWithDefault key default x =
|
|
||||||
opField key x |> map (Maybe.withDefault default)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Transform a JSON boolean into a phantom Elm bool.
|
|
||||||
-}
|
|
||||||
pBool : Decoder Bool -> Decoder (Phantom.PBool ph)
|
|
||||||
pBool =
|
|
||||||
map Phantom.PBool
|
|
||||||
|
|
||||||
|
|
||||||
{-| Transform a JSON number into a phantom Elm int.
|
|
||||||
-}
|
|
||||||
pInt : Decoder Int -> Decoder (Phantom.PInt ph)
|
|
||||||
pInt =
|
|
||||||
map Phantom.PInt
|
|
||||||
|
|
||||||
|
|
||||||
{-| Transform a JSON list into a phantom Elm list.
|
|
||||||
-}
|
|
||||||
pList : Decoder (List a) -> Decoder (Phantom.PList ph a)
|
|
||||||
pList =
|
|
||||||
map Phantom.PList
|
|
||||||
|
|
||||||
|
|
||||||
{-| Transform a JSON string into a phantom Elm string.
|
|
||||||
-}
|
|
||||||
pString : Decoder String -> Decoder (Phantom.PString ph)
|
|
||||||
pString =
|
|
||||||
map Phantom.PString
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON object into an Elm dict.
|
|
||||||
-}
|
|
||||||
slowDict : Decoder a -> Decoder (SDict.Dict String a)
|
|
||||||
slowDict x =
|
|
||||||
D.map
|
|
||||||
(\result ->
|
|
||||||
{ content =
|
|
||||||
result
|
|
||||||
|> List.map (Tuple.mapSecond .content)
|
|
||||||
|> SDict.fromList
|
|
||||||
, messages =
|
|
||||||
result
|
|
||||||
|> List.map Tuple.second
|
|
||||||
|> List.map .messages
|
|
||||||
|> List.concat
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(D.keyValuePairs x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a JSON string into an Elm string.
|
|
||||||
|
|
||||||
This decoder also checks for suspicious inputs, such as the
|
|
||||||
[Leaking values](Internal-Config-Leaks), to look for suspicious inputs.
|
|
||||||
|
|
||||||
-}
|
|
||||||
string : Decoder String
|
|
||||||
string =
|
|
||||||
D.map
|
|
||||||
(\content ->
|
|
||||||
{ content = content
|
|
||||||
, messages =
|
|
||||||
if Set.member content L.allLeaks then
|
|
||||||
[ ( Log.securityWarn, Text.leakingValueFound content )
|
|
||||||
]
|
|
||||||
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
D.string
|
|
||||||
|
|
||||||
|
|
||||||
{-| Ignore the JSON and produce a certain Elm value.
|
|
||||||
-}
|
|
||||||
succeed : a -> Decoder a
|
|
||||||
succeed =
|
|
||||||
D.succeed >> D.map empty
|
|
||||||
|
|
||||||
|
|
||||||
{-| Do not do anything with a JSON value, just bring it into Elm as a `Value`.
|
|
||||||
-}
|
|
||||||
value : Decoder D.Value
|
|
||||||
value =
|
|
||||||
D.map empty D.value
|
|
|
@ -1,10 +1,12 @@
|
||||||
module Internal.Tools.Json exposing
|
module Internal.Tools.Json exposing
|
||||||
( Coder, string, bool, int, float
|
( Coder, string, bool, int, float, value
|
||||||
, encode, decode
|
, Encoder, encode, Decoder, decode, Value
|
||||||
|
, succeed, fail, andThen, lazy
|
||||||
, Docs(..), RequiredField(..), toDocs
|
, Docs(..), RequiredField(..), toDocs
|
||||||
, list, slowDict, fastDict, maybe
|
, list, slowDict, fastDict, maybe
|
||||||
, Field, field
|
, Field, field
|
||||||
, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11
|
, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11
|
||||||
|
, map
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -28,12 +30,17 @@ data types. Because this module uses dynamic builder types, this also means it
|
||||||
is relatively easy to write documentation for any data type that uses this
|
is relatively easy to write documentation for any data type that uses this
|
||||||
module to build its encoders and decoders.
|
module to build its encoders and decoders.
|
||||||
|
|
||||||
@docs Coder, string, bool, int, float
|
@docs Coder, string, bool, int, float, value
|
||||||
|
|
||||||
|
|
||||||
## JSON Coding
|
## JSON Coding
|
||||||
|
|
||||||
@docs encode, decode
|
@docs Encoder, encode, Decoder, decode, Value
|
||||||
|
|
||||||
|
|
||||||
|
## Optional coding
|
||||||
|
|
||||||
|
@docs succeed, fail, andThen, lazy
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
@ -109,6 +116,22 @@ type Coder a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type DecodeResult a
|
||||||
|
= Success ( a, List Log )
|
||||||
|
| Fail ( String, List Log )
|
||||||
|
|
||||||
|
|
||||||
|
{-| Decoder type that describes the format of a JSON value that can be decoded
|
||||||
|
as a given type.
|
||||||
|
-}
|
||||||
|
type alias Decoder a =
|
||||||
|
D.Decoder ( a, List Log )
|
||||||
|
|
||||||
|
|
||||||
|
type alias Descriptive a =
|
||||||
|
{ a | name : String, description : List String }
|
||||||
|
|
||||||
|
|
||||||
{-| Structure of JSON documentation. It is up to an external module to turn the
|
{-| Structure of JSON documentation. It is up to an external module to turn the
|
||||||
documentation structure into a readable format.
|
documentation structure into a readable format.
|
||||||
-}
|
-}
|
||||||
|
@ -117,20 +140,30 @@ type Docs
|
||||||
| DocsDict Docs
|
| DocsDict Docs
|
||||||
| DocsFloat
|
| DocsFloat
|
||||||
| DocsInt
|
| DocsInt
|
||||||
|
| DocsLazy (() -> Docs)
|
||||||
| DocsList Docs
|
| DocsList Docs
|
||||||
|
| DocsMap (Descriptive { content : Docs })
|
||||||
| DocsObject
|
| DocsObject
|
||||||
{ name : String
|
(Descriptive
|
||||||
, description : List String
|
{ keys :
|
||||||
, keys :
|
List
|
||||||
List
|
{ field : String
|
||||||
{ field : String
|
, description : List String
|
||||||
, description : List String
|
, required : RequiredField
|
||||||
, required : RequiredField
|
, content : Docs
|
||||||
, content : Docs
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
| DocsOptional Docs
|
| DocsOptional Docs
|
||||||
|
| DocsRiskyMap (Descriptive { content : Docs, failure : List String })
|
||||||
| DocsString
|
| DocsString
|
||||||
|
| DocsValue
|
||||||
|
|
||||||
|
|
||||||
|
{-| Encoder type that takes an input and converts it to a JSON value.
|
||||||
|
-}
|
||||||
|
type alias Encoder a =
|
||||||
|
a -> E.Value
|
||||||
|
|
||||||
|
|
||||||
{-| Value that tells whether an object field is required to be included. If it
|
{-| Value that tells whether an object field is required to be included. If it
|
||||||
|
@ -143,6 +176,42 @@ type RequiredField
|
||||||
| OptionalFieldWithDefault String
|
| OptionalFieldWithDefault String
|
||||||
|
|
||||||
|
|
||||||
|
type alias Value =
|
||||||
|
E.Value
|
||||||
|
|
||||||
|
|
||||||
|
{-| Continue decoding a result. This function tests if it meets the criteria,
|
||||||
|
and then it manages the results.
|
||||||
|
-}
|
||||||
|
andThen : Descriptive { back : b -> a, forth : a -> DecodeResult b, failure : List String } -> Coder a -> Coder b
|
||||||
|
andThen { name, description, failure, back, forth } (Coder old) =
|
||||||
|
Coder
|
||||||
|
{ encoder = back >> old.encoder
|
||||||
|
, decoder =
|
||||||
|
old.decoder
|
||||||
|
|> D.andThen
|
||||||
|
(\result ->
|
||||||
|
case result of
|
||||||
|
( out, logs ) ->
|
||||||
|
case forth out of
|
||||||
|
Success x ->
|
||||||
|
x
|
||||||
|
|> Tuple.mapSecond (List.append logs)
|
||||||
|
|> D.succeed
|
||||||
|
|
||||||
|
Fail ( f, _ ) ->
|
||||||
|
D.fail f
|
||||||
|
)
|
||||||
|
, docs =
|
||||||
|
DocsRiskyMap
|
||||||
|
{ name = name
|
||||||
|
, description = description
|
||||||
|
, content = old.docs
|
||||||
|
, failure = failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Define a boolean value.
|
{-| Define a boolean value.
|
||||||
-}
|
-}
|
||||||
bool : Coder Bool
|
bool : Coder Bool
|
||||||
|
@ -190,14 +259,21 @@ encode (Coder data) =
|
||||||
data.encoder
|
data.encoder
|
||||||
|
|
||||||
|
|
||||||
|
{-| Fail a decoder.
|
||||||
|
-}
|
||||||
|
fail : String -> List Log -> DecodeResult a
|
||||||
|
fail reason logs =
|
||||||
|
Fail ( reason, logs )
|
||||||
|
|
||||||
|
|
||||||
{-| Define a fast dict. The dict can only have strings as keys.
|
{-| Define a fast dict. The dict can only have strings as keys.
|
||||||
-}
|
-}
|
||||||
fastDict : Coder value -> Coder (FastDict.Dict String value)
|
fastDict : Coder value -> Coder (FastDict.Dict String value)
|
||||||
fastDict (Coder value) =
|
fastDict (Coder old) =
|
||||||
Coder
|
Coder
|
||||||
{ encoder = FastDict.toCoreDict >> E.dict identity value.encoder
|
{ encoder = FastDict.toCoreDict >> E.dict identity old.encoder
|
||||||
, decoder =
|
, decoder =
|
||||||
value.decoder
|
old.decoder
|
||||||
|> D.keyValuePairs
|
|> D.keyValuePairs
|
||||||
|> D.map
|
|> D.map
|
||||||
(\items ->
|
(\items ->
|
||||||
|
@ -209,7 +285,7 @@ fastDict (Coder value) =
|
||||||
|> List.concatMap Tuple.second
|
|> List.concatMap Tuple.second
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
, docs = DocsDict value.docs
|
, docs = DocsDict old.docs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -287,8 +363,8 @@ field =
|
||||||
decoder
|
decoder
|
||||||
|> D.opField fieldName
|
|> D.opField fieldName
|
||||||
|> D.map
|
|> D.map
|
||||||
(\value ->
|
(\out ->
|
||||||
case value of
|
case out of
|
||||||
Just ( v, l ) ->
|
Just ( v, l ) ->
|
||||||
( Just v, l )
|
( Just v, l )
|
||||||
|
|
||||||
|
@ -341,6 +417,25 @@ int =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lazy : (() -> Coder value) -> Coder value
|
||||||
|
lazy f =
|
||||||
|
Coder
|
||||||
|
{ encoder =
|
||||||
|
\v ->
|
||||||
|
case f () of
|
||||||
|
Coder old ->
|
||||||
|
old.encoder v
|
||||||
|
, decoder =
|
||||||
|
D.lazy
|
||||||
|
(\() ->
|
||||||
|
case f () of
|
||||||
|
Coder old ->
|
||||||
|
old.decoder
|
||||||
|
)
|
||||||
|
, docs = DocsLazy (f >> toDocs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Define a list.
|
{-| Define a list.
|
||||||
-}
|
-}
|
||||||
list : Coder a -> Coder (List a)
|
list : Coder a -> Coder (List a)
|
||||||
|
@ -360,6 +455,23 @@ list (Coder old) =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Map a value.
|
||||||
|
|
||||||
|
Given that the value needs to be both encoded and decoded, the map function
|
||||||
|
should be invertible.
|
||||||
|
|
||||||
|
-}
|
||||||
|
map : Descriptive { back : b -> a, forth : a -> b } -> Coder a -> Coder b
|
||||||
|
map { name, description, back, forth } (Coder old) =
|
||||||
|
Coder
|
||||||
|
{ encoder = back >> old.encoder
|
||||||
|
, decoder = D.map (Tuple.mapFirst forth) old.decoder
|
||||||
|
, docs =
|
||||||
|
DocsMap
|
||||||
|
{ name = name, description = description, content = old.docs }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Define a maybe value.
|
{-| Define a maybe value.
|
||||||
|
|
||||||
NOTE: most of the time, you wish to avoid this function! Make sure to look at
|
NOTE: most of the time, you wish to avoid this function! Make sure to look at
|
||||||
|
@ -374,8 +486,8 @@ maybe (Coder old) =
|
||||||
old.decoder
|
old.decoder
|
||||||
|> D.nullable
|
|> D.nullable
|
||||||
|> D.map
|
|> D.map
|
||||||
(\value ->
|
(\out ->
|
||||||
case value of
|
case out of
|
||||||
Just ( v, logs ) ->
|
Just ( v, logs ) ->
|
||||||
( Just v, logs )
|
( Just v, logs )
|
||||||
|
|
||||||
|
@ -430,7 +542,7 @@ objectEncoder items object =
|
||||||
|
|
||||||
-}
|
-}
|
||||||
object2 :
|
object2 :
|
||||||
{ name : String, description : List String, init : a -> b -> object }
|
Descriptive { init : a -> b -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Coder object
|
-> Coder object
|
||||||
|
@ -465,7 +577,7 @@ object2 { name, description, init } fa fb =
|
||||||
{-| Define an object with 3 keys
|
{-| Define an object with 3 keys
|
||||||
-}
|
-}
|
||||||
object3 :
|
object3 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> object }
|
Descriptive { init : a -> b -> c -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -504,7 +616,7 @@ object3 { name, description, init } fa fb fc =
|
||||||
{-| Define an object with 4 keys
|
{-| Define an object with 4 keys
|
||||||
-}
|
-}
|
||||||
object4 :
|
object4 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> object }
|
Descriptive { init : a -> b -> c -> d -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -547,7 +659,7 @@ object4 { name, description, init } fa fb fc fd =
|
||||||
{-| Define an object with 5 keys
|
{-| Define an object with 5 keys
|
||||||
-}
|
-}
|
||||||
object5 :
|
object5 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> e -> object }
|
Descriptive { init : a -> b -> c -> d -> e -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -594,7 +706,7 @@ object5 { name, description, init } fa fb fc fd fe =
|
||||||
{-| Define an object with 6 keys
|
{-| Define an object with 6 keys
|
||||||
-}
|
-}
|
||||||
object6 :
|
object6 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> e -> f -> object }
|
Descriptive { init : a -> b -> c -> d -> e -> f -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -645,7 +757,7 @@ object6 { name, description, init } fa fb fc fd fe ff =
|
||||||
{-| Define an object with 7 keys
|
{-| Define an object with 7 keys
|
||||||
-}
|
-}
|
||||||
object7 :
|
object7 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> e -> f -> g -> object }
|
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -700,7 +812,7 @@ object7 { name, description, init } fa fb fc fd fe ff fg =
|
||||||
{-| Define an object with 8 keys
|
{-| Define an object with 8 keys
|
||||||
-}
|
-}
|
||||||
object8 :
|
object8 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> e -> f -> g -> h -> object }
|
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -759,7 +871,7 @@ object8 { name, description, init } fa fb fc fd fe ff fg fh =
|
||||||
{-| Define an object with 9 keys
|
{-| Define an object with 9 keys
|
||||||
-}
|
-}
|
||||||
object9 :
|
object9 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> e -> f -> g -> h -> i -> object }
|
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -822,7 +934,7 @@ object9 { name, description, init } fa fb fc fd fe ff fg fh fi =
|
||||||
{-| Define an object with 10 keys
|
{-| Define an object with 10 keys
|
||||||
-}
|
-}
|
||||||
object10 :
|
object10 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> object }
|
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -889,7 +1001,7 @@ object10 { name, description, init } fa fb fc fd fe ff fg fh fi fj =
|
||||||
{-| Define an object with 11 keys
|
{-| Define an object with 11 keys
|
||||||
-}
|
-}
|
||||||
object11 :
|
object11 :
|
||||||
{ name : String, description : List String, init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> object }
|
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -991,6 +1103,13 @@ string =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Succeed a decoder.
|
||||||
|
-}
|
||||||
|
succeed : a -> List Log -> DecodeResult a
|
||||||
|
succeed x logs =
|
||||||
|
Success ( x, logs )
|
||||||
|
|
||||||
|
|
||||||
{-| Turn a Field type into a usable JSON decoder
|
{-| Turn a Field type into a usable JSON decoder
|
||||||
-}
|
-}
|
||||||
toDecoderField : Field a object -> D.Decoder ( a, List Log )
|
toDecoderField : Field a object -> D.Decoder ( a, List Log )
|
||||||
|
@ -1016,3 +1135,12 @@ toDocsField x =
|
||||||
toEncodeField : Field a object -> ( String, object -> Maybe E.Value )
|
toEncodeField : Field a object -> ( String, object -> Maybe E.Value )
|
||||||
toEncodeField (Field data) =
|
toEncodeField (Field data) =
|
||||||
( data.fieldName, data.toField >> data.encoder )
|
( data.fieldName, data.toField >> data.encoder )
|
||||||
|
|
||||||
|
|
||||||
|
value : Coder Value
|
||||||
|
value =
|
||||||
|
Coder
|
||||||
|
{ encoder = identity
|
||||||
|
, decoder = D.map (\v -> ( v, [] )) D.value
|
||||||
|
, docs = DocsValue
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue