Compare commits
No commits in common. "d1fbc87730b2143bbd9f34db938c808d5f4438e0" and "ecdc136f9e24fd6db89f7403c6bdf7aaaf6901e8" have entirely different histories.
d1fbc87730
...
ecdc136f9e
|
@ -1,9 +1,8 @@
|
||||||
module Internal.Config.Text exposing
|
module Internal.Config.Text exposing
|
||||||
( docs, failures, fields
|
( accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid
|
||||||
, accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid
|
|
||||||
, versionsFoundLocally, versionsReceived, versionsFailedToDecode
|
, versionsFoundLocally, versionsReceived, versionsFailedToDecode
|
||||||
, unsupportedVersionForEndpoint
|
, unsupportedVersionForEndpoint
|
||||||
, decodedDictSize, invalidHashInHashdict, invalidHashInMashdict, leakingValueFound
|
, decodedDictSize, leakingValueFound
|
||||||
)
|
)
|
||||||
|
|
||||||
{-| Throughout the Elm SDK, there are lots of pieces of text being used for
|
{-| Throughout the Elm SDK, there are lots of pieces of text being used for
|
||||||
|
@ -25,11 +24,6 @@ This is a risky feature, keep in mind that even a patch update might break this!
|
||||||
You should only do this if you know what you're doing.
|
You should only do this if you know what you're doing.
|
||||||
|
|
||||||
|
|
||||||
## Type documentation
|
|
||||||
|
|
||||||
@docs docs, failures, fields
|
|
||||||
|
|
||||||
|
|
||||||
## API Authentication
|
## API Authentication
|
||||||
|
|
||||||
Messages sent as API logs during the authentication phase of the API
|
Messages sent as API logs during the authentication phase of the API
|
||||||
|
@ -59,19 +53,11 @@ Messages sent as API logs during communication with the API.
|
||||||
|
|
||||||
Messages sent as API logs when a JSON value is being decoded.
|
Messages sent as API logs when a JSON value is being decoded.
|
||||||
|
|
||||||
@docs decodedDictSize, invalidHashInHashdict, invalidHashInMashdict, leakingValueFound
|
@docs decodedDictSize, leakingValueFound
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
|
||||||
type alias Desc =
|
|
||||||
List String
|
|
||||||
|
|
||||||
|
|
||||||
type alias TypeDocs =
|
|
||||||
{ name : String, description : Desc }
|
|
||||||
|
|
||||||
|
|
||||||
{-| Logs when the Matrix API returns that an access token is no longer valid.
|
{-| Logs when the Matrix API returns that an access token is no longer valid.
|
||||||
-}
|
-}
|
||||||
accessTokenExpired : String
|
accessTokenExpired : String
|
||||||
|
@ -109,117 +95,6 @@ decodedDictSize from to =
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
{-| Documentation used for all functions and data types in JSON coders
|
|
||||||
-}
|
|
||||||
docs :
|
|
||||||
{ event : TypeDocs
|
|
||||||
, hashdict : TypeDocs
|
|
||||||
, mashdict : TypeDocs
|
|
||||||
, stateManager : TypeDocs
|
|
||||||
, unsigned : TypeDocs
|
|
||||||
}
|
|
||||||
docs =
|
|
||||||
{ event =
|
|
||||||
{ name = "Event"
|
|
||||||
, description =
|
|
||||||
[ "The Event type represents a single value that contains all the information for a single event in the room."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
, hashdict =
|
|
||||||
{ name = "Hashdict"
|
|
||||||
, description =
|
|
||||||
[ "This allows you to store values based on an externally defined identifier."
|
|
||||||
, "For example, the hashdict can store events and use their event id as their key."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
, mashdict =
|
|
||||||
{ name = "Mashdict"
|
|
||||||
, description =
|
|
||||||
[ "The mashdict exclusively stores values for which the hashing algorithm returns a value, and it ignores the outcome for all other scenarios."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
, stateManager =
|
|
||||||
{ name = "StateManager"
|
|
||||||
, description =
|
|
||||||
[ "The StateManager tracks the room state based on events, their event types and the optional state keys they provide."
|
|
||||||
, "Instead of making the user loop through the room's timeline of events, the StateManager offers the user a dictionary-like experience to navigate through the Matrix room state."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
, unsigned =
|
|
||||||
{ name = "Unsigned Data"
|
|
||||||
, description =
|
|
||||||
[ "Unsigned data is optional data that might come along with the event."
|
|
||||||
, "This information is often supportive but not necessary to the context."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{-| Description of all edge cases where a JSON decoder can fail.
|
|
||||||
-}
|
|
||||||
failures : { hashdict : Desc, mashdict : Desc }
|
|
||||||
failures =
|
|
||||||
{ hashdict =
|
|
||||||
[ "Not all values map to thir respected hash with the given hash function."
|
|
||||||
]
|
|
||||||
, mashdict =
|
|
||||||
[ "Not all values map to thir respected hash with the given hash function."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- TODO
|
|
||||||
|
|
||||||
|
|
||||||
fields :
|
|
||||||
{ event :
|
|
||||||
{ content : Desc
|
|
||||||
, eventId : Desc
|
|
||||||
, originServerTs : Desc
|
|
||||||
, roomId : Desc
|
|
||||||
, sender : Desc
|
|
||||||
, stateKey : Desc
|
|
||||||
, eventType : Desc
|
|
||||||
, unsigned : Desc
|
|
||||||
}
|
|
||||||
, unsigned :
|
|
||||||
{ age : Desc
|
|
||||||
, prevContent : Desc
|
|
||||||
, redactedBecause : Desc
|
|
||||||
, transactionId : Desc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fields =
|
|
||||||
{ event =
|
|
||||||
{ content = []
|
|
||||||
, eventId = []
|
|
||||||
, originServerTs = []
|
|
||||||
, roomId = []
|
|
||||||
, sender = []
|
|
||||||
, stateKey = []
|
|
||||||
, eventType = []
|
|
||||||
, unsigned = []
|
|
||||||
}
|
|
||||||
, unsigned =
|
|
||||||
{ age = []
|
|
||||||
, prevContent = []
|
|
||||||
, redactedBecause = []
|
|
||||||
, transactionId = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
invalidHashInHashdict : String
|
|
||||||
invalidHashInHashdict =
|
|
||||||
"Invalid hash function: not all elements hash to their JSON-stored hashes"
|
|
||||||
|
|
||||||
|
|
||||||
invalidHashInMashdict : String
|
|
||||||
invalidHashInMashdict =
|
|
||||||
"Invalid hash function: not all elements hash to their JSON-stored hashes"
|
|
||||||
|
|
||||||
|
|
||||||
{-| The Elm SDK occassionally uses [leaking values](Internal-Config-Leaks),
|
{-| The Elm SDK occassionally uses [leaking values](Internal-Config-Leaks),
|
||||||
which might indicate exceptional behaviour. As such, this log is sent when one
|
which might indicate exceptional behaviour. As such, this log is sent when one
|
||||||
of those leaking values is found: to alert the user that something fishy might
|
of those leaking values is found: to alert the user that something fishy might
|
||||||
|
|
|
@ -0,0 +1,691 @@
|
||||||
|
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
|
|
@ -4,7 +4,7 @@ module Internal.Tools.Hashdict exposing
|
||||||
, isEmpty, member, memberKey, get, size, isEqual
|
, isEmpty, member, memberKey, get, size, isEqual
|
||||||
, keys, values, toList, fromList
|
, keys, values, toList, fromList
|
||||||
, rehash, union
|
, rehash, union
|
||||||
, coder, encode, decoder, softDecoder
|
, encode, decoder, softDecoder
|
||||||
)
|
)
|
||||||
|
|
||||||
{-| This module abstracts the `Dict` type with one function that assigns a
|
{-| This module abstracts the `Dict` type with one function that assigns a
|
||||||
|
@ -40,14 +40,13 @@ This allows you to store values based on an externally defined identifier.
|
||||||
|
|
||||||
## JSON coders
|
## JSON coders
|
||||||
|
|
||||||
@docs coder, encode, decoder, softDecoder
|
@docs encode, decoder, softDecoder
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import FastDict as Dict exposing (Dict)
|
import FastDict as Dict exposing (Dict)
|
||||||
import Internal.Config.Log as Log
|
import Json.Decode as D
|
||||||
import Internal.Config.Text as Text
|
import Json.Encode as E
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
|
|
||||||
|
|
||||||
{-| A dictionary of keys and values where each key is defined by its value. For
|
{-| A dictionary of keys and values where each key is defined by its value. For
|
||||||
|
@ -81,41 +80,25 @@ type Hashdict a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
coder : (a -> String) -> Json.Coder a -> Json.Coder (Hashdict a)
|
|
||||||
coder f c1 =
|
|
||||||
Json.andThen
|
|
||||||
{ name = Text.docs.hashdict.name
|
|
||||||
, description = Text.docs.hashdict.description
|
|
||||||
, forth =
|
|
||||||
-- TODO: Implement fastDictWithFilter function
|
|
||||||
\items ->
|
|
||||||
case List.filter (\( k, v ) -> f v /= k) (Dict.toList items) of
|
|
||||||
[] ->
|
|
||||||
{ hash = f, values = items }
|
|
||||||
|> Hashdict
|
|
||||||
|> Json.succeed
|
|
||||||
|> (|>) []
|
|
||||||
|
|
||||||
wrongHashes ->
|
|
||||||
wrongHashes
|
|
||||||
|> List.map Tuple.first
|
|
||||||
|> List.map ((++) "Invalid hash")
|
|
||||||
|> List.map Log.log.error
|
|
||||||
|> Json.fail Text.invalidHashInHashdict
|
|
||||||
, back = \(Hashdict h) -> h.values
|
|
||||||
, failure =
|
|
||||||
Text.failures.hashdict
|
|
||||||
}
|
|
||||||
(Json.fastDict c1)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a hashdict from a JSON value. To create a hashdict, you are expected
|
{-| Decode a hashdict from a JSON value. To create a hashdict, you are expected
|
||||||
to insert a hash function. If the hash function doesn't properly hash the values
|
to insert a hash function. If the hash function doesn't properly hash the values
|
||||||
as expected, the decoder will fail to decode the hashdict.
|
as expected, the decoder will fail to decode the hashdict.
|
||||||
-}
|
-}
|
||||||
decoder : (a -> String) -> Json.Coder a -> Json.Decoder (Hashdict a)
|
decoder : (a -> String) -> D.Decoder a -> D.Decoder (Hashdict a)
|
||||||
decoder f c1 =
|
decoder f xDecoder =
|
||||||
Json.decode (coder f c1)
|
D.keyValuePairs xDecoder
|
||||||
|
|> D.andThen
|
||||||
|
(\items ->
|
||||||
|
if List.all (\( hash, value ) -> f value == hash) items then
|
||||||
|
items
|
||||||
|
|> Dict.fromList
|
||||||
|
|> (\d -> { hash = f, values = d })
|
||||||
|
|> Hashdict
|
||||||
|
|> D.succeed
|
||||||
|
|
||||||
|
else
|
||||||
|
D.fail "Hash function fails to properly hash all values"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
{-| Create an empty hashdict.
|
{-| Create an empty hashdict.
|
||||||
|
@ -129,9 +112,12 @@ empty hash =
|
||||||
cannot be universally converted to JSON, so it is up to you to preserve that
|
cannot be universally converted to JSON, so it is up to you to preserve that
|
||||||
hash function!
|
hash function!
|
||||||
-}
|
-}
|
||||||
encode : Json.Coder a -> Json.Encoder (Hashdict a)
|
encode : (a -> E.Value) -> Hashdict a -> E.Value
|
||||||
encode c1 (Hashdict h) =
|
encode encodeX (Hashdict h) =
|
||||||
Json.encode (coder h.hash c1) (Hashdict h)
|
h.values
|
||||||
|
|> Dict.toList
|
||||||
|
|> List.map (Tuple.mapSecond encodeX)
|
||||||
|
|> E.object
|
||||||
|
|
||||||
|
|
||||||
{-| Convert an association list into a hashdict.
|
{-| Convert an association list into a hashdict.
|
||||||
|
@ -254,20 +240,10 @@ size (Hashdict h) =
|
||||||
used hash function, (or if you simply do not care) you can use this function to
|
used hash function, (or if you simply do not care) you can use this function to
|
||||||
decode and rehash the Hashdict using your new hash function.
|
decode and rehash the Hashdict using your new hash function.
|
||||||
-}
|
-}
|
||||||
softDecoder : (a -> String) -> Json.Coder a -> Json.Decoder (Hashdict a)
|
softDecoder : (a -> String) -> D.Decoder a -> D.Decoder (Hashdict a)
|
||||||
softDecoder f c1 =
|
softDecoder f xDecoder =
|
||||||
c1
|
D.keyValuePairs xDecoder
|
||||||
|> Json.fastDict
|
|> D.map (List.map Tuple.second >> fromList f)
|
||||||
|> Json.map
|
|
||||||
{ name = Text.docs.hashdict.name
|
|
||||||
, description = Text.docs.hashdict.description
|
|
||||||
, forth =
|
|
||||||
\items ->
|
|
||||||
Hashdict { hash = f, values = items }
|
|
||||||
|> rehash f
|
|
||||||
, back = \(Hashdict h) -> h.values
|
|
||||||
}
|
|
||||||
|> Json.decode
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert a hashdict into an association list of key-value pairs, sorted by
|
{-| Convert a hashdict into an association list of key-value pairs, sorted by
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
module Internal.Tools.Json exposing
|
module Internal.Tools.Json exposing
|
||||||
( Coder, string, bool, int, float, value
|
( Coder, string, bool, int, float
|
||||||
, Encoder, encode, Decoder, decode, Value
|
, encode, decode
|
||||||
, 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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -30,17 +28,12 @@ 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, value
|
@docs Coder, string, bool, int, float
|
||||||
|
|
||||||
|
|
||||||
## JSON Coding
|
## JSON Coding
|
||||||
|
|
||||||
@docs Encoder, encode, Decoder, decode, Value
|
@docs encode, decode
|
||||||
|
|
||||||
|
|
||||||
## Optional coding
|
|
||||||
|
|
||||||
@docs succeed, fail, andThen, lazy
|
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
@ -116,22 +109,6 @@ 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.
|
||||||
-}
|
-}
|
||||||
|
@ -140,12 +117,11 @@ type Docs
|
||||||
| DocsDict Docs
|
| DocsDict Docs
|
||||||
| DocsFloat
|
| DocsFloat
|
||||||
| DocsInt
|
| DocsInt
|
||||||
| DocsLazy (() -> Docs)
|
|
||||||
| DocsList Docs
|
| DocsList Docs
|
||||||
| DocsMap (Descriptive { content : Docs })
|
|
||||||
| DocsObject
|
| DocsObject
|
||||||
(Descriptive
|
{ name : String
|
||||||
{ keys :
|
, description : List String
|
||||||
|
, keys :
|
||||||
List
|
List
|
||||||
{ field : String
|
{ field : String
|
||||||
, description : List String
|
, description : List String
|
||||||
|
@ -153,17 +129,8 @@ type Docs
|
||||||
, 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
|
||||||
|
@ -176,42 +143,6 @@ 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
|
||||||
|
@ -259,21 +190,14 @@ 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 old) =
|
fastDict (Coder value) =
|
||||||
Coder
|
Coder
|
||||||
{ encoder = FastDict.toCoreDict >> E.dict identity old.encoder
|
{ encoder = FastDict.toCoreDict >> E.dict identity value.encoder
|
||||||
, decoder =
|
, decoder =
|
||||||
old.decoder
|
value.decoder
|
||||||
|> D.keyValuePairs
|
|> D.keyValuePairs
|
||||||
|> D.map
|
|> D.map
|
||||||
(\items ->
|
(\items ->
|
||||||
|
@ -285,7 +209,7 @@ fastDict (Coder old) =
|
||||||
|> List.concatMap Tuple.second
|
|> List.concatMap Tuple.second
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
, docs = DocsDict old.docs
|
, docs = DocsDict value.docs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -363,8 +287,8 @@ field =
|
||||||
decoder
|
decoder
|
||||||
|> D.opField fieldName
|
|> D.opField fieldName
|
||||||
|> D.map
|
|> D.map
|
||||||
(\out ->
|
(\value ->
|
||||||
case out of
|
case value of
|
||||||
Just ( v, l ) ->
|
Just ( v, l ) ->
|
||||||
( Just v, l )
|
( Just v, l )
|
||||||
|
|
||||||
|
@ -417,25 +341,6 @@ 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)
|
||||||
|
@ -455,23 +360,6 @@ 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
|
||||||
|
@ -486,8 +374,8 @@ maybe (Coder old) =
|
||||||
old.decoder
|
old.decoder
|
||||||
|> D.nullable
|
|> D.nullable
|
||||||
|> D.map
|
|> D.map
|
||||||
(\out ->
|
(\value ->
|
||||||
case out of
|
case value of
|
||||||
Just ( v, logs ) ->
|
Just ( v, logs ) ->
|
||||||
( Just v, logs )
|
( Just v, logs )
|
||||||
|
|
||||||
|
@ -542,7 +430,7 @@ objectEncoder items object =
|
||||||
|
|
||||||
-}
|
-}
|
||||||
object2 :
|
object2 :
|
||||||
Descriptive { init : a -> b -> object }
|
{ name : String, description : List String, init : a -> b -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Coder object
|
-> Coder object
|
||||||
|
@ -577,7 +465,7 @@ object2 { name, description, init } fa fb =
|
||||||
{-| Define an object with 3 keys
|
{-| Define an object with 3 keys
|
||||||
-}
|
-}
|
||||||
object3 :
|
object3 :
|
||||||
Descriptive { init : a -> b -> c -> object }
|
{ name : String, description : List String, init : a -> b -> c -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -616,7 +504,7 @@ object3 { name, description, init } fa fb fc =
|
||||||
{-| Define an object with 4 keys
|
{-| Define an object with 4 keys
|
||||||
-}
|
-}
|
||||||
object4 :
|
object4 :
|
||||||
Descriptive { init : a -> b -> c -> d -> object }
|
{ name : String, description : List String, init : a -> b -> c -> d -> object }
|
||||||
-> Field a object
|
-> Field a object
|
||||||
-> Field b object
|
-> Field b object
|
||||||
-> Field c object
|
-> Field c object
|
||||||
|
@ -659,7 +547,7 @@ object4 { name, description, init } fa fb fc fd =
|
||||||
{-| Define an object with 5 keys
|
{-| Define an object with 5 keys
|
||||||
-}
|
-}
|
||||||
object5 :
|
object5 :
|
||||||
Descriptive { init : a -> b -> c -> d -> e -> object }
|
{ name : String, description : List String, 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
|
||||||
|
@ -706,7 +594,7 @@ object5 { name, description, init } fa fb fc fd fe =
|
||||||
{-| Define an object with 6 keys
|
{-| Define an object with 6 keys
|
||||||
-}
|
-}
|
||||||
object6 :
|
object6 :
|
||||||
Descriptive { init : a -> b -> c -> d -> e -> f -> object }
|
{ name : String, description : List String, 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
|
||||||
|
@ -757,7 +645,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 :
|
||||||
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> object }
|
{ name : String, description : List String, 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
|
||||||
|
@ -812,7 +700,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 :
|
||||||
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> object }
|
{ name : String, description : List String, 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
|
||||||
|
@ -871,7 +759,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 :
|
||||||
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> object }
|
{ name : String, description : List String, 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
|
||||||
|
@ -934,7 +822,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 :
|
||||||
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> object }
|
{ name : String, description : List String, 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
|
||||||
|
@ -1001,7 +889,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 :
|
||||||
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> object }
|
{ name : String, description : List String, 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
|
||||||
|
@ -1103,13 +991,6 @@ 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 )
|
||||||
|
@ -1135,12 +1016,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ module Internal.Tools.Mashdict exposing
|
||||||
, isEmpty, member, memberKey, get, size, isEqual
|
, isEmpty, member, memberKey, get, size, isEqual
|
||||||
, keys, values, toList, fromList
|
, keys, values, toList, fromList
|
||||||
, rehash, union
|
, rehash, union
|
||||||
, coder, encode, decoder, softDecoder
|
, encode, decoder, softDecoder
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -48,14 +48,13 @@ In general, you are advised to learn more about the
|
||||||
|
|
||||||
## JSON coders
|
## JSON coders
|
||||||
|
|
||||||
@docs coder, encode, decoder, softDecoder
|
@docs encode, decoder, softDecoder
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import FastDict as Dict exposing (Dict)
|
import FastDict as Dict exposing (Dict)
|
||||||
import Internal.Config.Log as Log
|
import Json.Decode as D
|
||||||
import Internal.Config.Text as Text
|
import Json.Encode as E
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
|
|
||||||
|
|
||||||
{-| A dictionary of keys and values where each key is defined by its value, but
|
{-| A dictionary of keys and values where each key is defined by its value, but
|
||||||
|
@ -93,39 +92,25 @@ type Mashdict a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
coder : (a -> Maybe String) -> Json.Coder a -> Json.Coder (Mashdict a)
|
|
||||||
coder f c1 =
|
|
||||||
Json.andThen
|
|
||||||
{ name = Text.docs.mashdict.name
|
|
||||||
, description = Text.docs.mashdict.description
|
|
||||||
, forth =
|
|
||||||
\items ->
|
|
||||||
case List.filter (\( k, v ) -> f v /= Just k) (Dict.toList items) of
|
|
||||||
[] ->
|
|
||||||
{ hash = f, values = items }
|
|
||||||
|> Mashdict
|
|
||||||
|> Json.succeed
|
|
||||||
|> (|>) []
|
|
||||||
|
|
||||||
wrongHashes ->
|
|
||||||
wrongHashes
|
|
||||||
|> List.map Tuple.first
|
|
||||||
|> List.map ((++) "Invalid hash")
|
|
||||||
|> List.map Log.log.error
|
|
||||||
|> Json.fail Text.invalidHashInMashdict
|
|
||||||
, back = \(Mashdict h) -> h.values
|
|
||||||
, failure = Text.failures.mashdict
|
|
||||||
}
|
|
||||||
(Json.fastDict c1)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a mashdict from a JSON value. To create a mashdict, you are expected
|
{-| Decode a mashdict from a JSON value. To create a mashdict, you are expected
|
||||||
to insert a hash function. If the hash function doesn't properly hash the values
|
to insert a hash function. If the hash function doesn't properly hash the values
|
||||||
as expected, the decoder will fail to decode the mashdict.
|
as expected, the decoder will fail to decode the mashdict.
|
||||||
-}
|
-}
|
||||||
decoder : (a -> Maybe String) -> Json.Coder a -> Json.Decoder (Mashdict a)
|
decoder : (a -> Maybe String) -> D.Decoder a -> D.Decoder (Mashdict a)
|
||||||
decoder f c1 =
|
decoder f xDecoder =
|
||||||
Json.decode (coder f c1)
|
D.keyValuePairs xDecoder
|
||||||
|
|> D.andThen
|
||||||
|
(\items ->
|
||||||
|
if List.all (\( hash, value ) -> f value == Just hash) items then
|
||||||
|
items
|
||||||
|
|> Dict.fromList
|
||||||
|
|> (\d -> { hash = f, values = d })
|
||||||
|
|> Mashdict
|
||||||
|
|> D.succeed
|
||||||
|
|
||||||
|
else
|
||||||
|
D.fail "Hash function fails to properly hash all values"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
{-| Create an empty mashdict.
|
{-| Create an empty mashdict.
|
||||||
|
@ -139,9 +124,12 @@ empty hash =
|
||||||
cannot be universally converted to JSON, so it is up to you to preserve that
|
cannot be universally converted to JSON, so it is up to you to preserve that
|
||||||
hash function!
|
hash function!
|
||||||
-}
|
-}
|
||||||
encode : Json.Coder a -> Json.Encoder (Mashdict a)
|
encode : (a -> E.Value) -> Mashdict a -> E.Value
|
||||||
encode c1 (Mashdict h) =
|
encode encodeX (Mashdict h) =
|
||||||
Json.encode (coder h.hash c1) (Mashdict h)
|
h.values
|
||||||
|
|> Dict.toList
|
||||||
|
|> List.map (Tuple.mapSecond encodeX)
|
||||||
|
|> E.object
|
||||||
|
|
||||||
|
|
||||||
{-| Convert an association list into a mashdict.
|
{-| Convert an association list into a mashdict.
|
||||||
|
@ -278,20 +266,10 @@ size (Mashdict h) =
|
||||||
used hash function, (or if you simply do not care) you can use this function to
|
used hash function, (or if you simply do not care) you can use this function to
|
||||||
decode and rehash the Mashdict using your new hash function.
|
decode and rehash the Mashdict using your new hash function.
|
||||||
-}
|
-}
|
||||||
softDecoder : (a -> Maybe String) -> Json.Coder a -> Json.Decoder (Mashdict a)
|
softDecoder : (a -> Maybe String) -> D.Decoder a -> D.Decoder (Mashdict a)
|
||||||
softDecoder f c1 =
|
softDecoder f xDecoder =
|
||||||
c1
|
D.keyValuePairs xDecoder
|
||||||
|> Json.fastDict
|
|> D.map (List.map Tuple.second >> fromList f)
|
||||||
|> Json.map
|
|
||||||
{ name = Text.docs.hashdict.name
|
|
||||||
, description = Text.docs.hashdict.description
|
|
||||||
, forth =
|
|
||||||
\items ->
|
|
||||||
Mashdict { hash = f, values = items }
|
|
||||||
|> rehash f
|
|
||||||
, back = \(Mashdict h) -> h.values
|
|
||||||
}
|
|
||||||
|> Json.decode
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert a mashdict into an association list of key-value pairs, sorted by
|
{-| Convert a mashdict into an association list of key-value pairs, sorted by
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module Internal.Tools.Timestamp exposing
|
module Internal.Tools.Timestamp exposing
|
||||||
( Timestamp
|
( Timestamp
|
||||||
, coder, encode, decoder
|
, encode, decoder
|
||||||
)
|
)
|
||||||
|
|
||||||
{-| The Timestamp module is a simplification of the Timestamp as delivered by
|
{-| The Timestamp module is a simplification of the Timestamp as delivered by
|
||||||
|
@ -14,11 +14,12 @@ elm/time. This module offers ways to work with the timestamp in meaningful ways.
|
||||||
|
|
||||||
## JSON coders
|
## JSON coders
|
||||||
|
|
||||||
@docs coder, encode, decoder
|
@docs encode, decoder
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import Internal.Tools.Json as Json
|
import Json.Decode as D
|
||||||
|
import Json.Encode as E
|
||||||
import Time
|
import Time
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,30 +29,15 @@ type alias Timestamp =
|
||||||
Time.Posix
|
Time.Posix
|
||||||
|
|
||||||
|
|
||||||
{-| Create a Json coder
|
|
||||||
-}
|
|
||||||
coder : Json.Coder Timestamp
|
|
||||||
coder =
|
|
||||||
Json.map
|
|
||||||
{ back = Time.posixToMillis
|
|
||||||
, forth = Time.millisToPosix
|
|
||||||
, name = "Milliseconds to POSIX"
|
|
||||||
, description =
|
|
||||||
[ "Converts the timestamp from milliseconds to a POSIX timestamp."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Json.int
|
|
||||||
|
|
||||||
|
|
||||||
{-| Encode a timestamp into a JSON value.
|
{-| Encode a timestamp into a JSON value.
|
||||||
-}
|
-}
|
||||||
encode : Json.Encoder Timestamp
|
encode : Timestamp -> E.Value
|
||||||
encode =
|
encode =
|
||||||
Json.encode coder
|
Time.posixToMillis >> E.int
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a timestamp from a JSON value.
|
{-| Decode a timestamp from a JSON value.
|
||||||
-}
|
-}
|
||||||
decoder : Json.Decoder Timestamp
|
decoder : D.Decoder Timestamp
|
||||||
decoder =
|
decoder =
|
||||||
Json.decode coder
|
D.map Time.millisToPosix D.int
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Internal.Values.Event exposing
|
module Internal.Values.Event exposing
|
||||||
( Event
|
( Event
|
||||||
, UnsignedData(..), age, prevContent, redactedBecause, transactionId
|
, UnsignedData(..), age, prevContent, redactedBecause, transactionId
|
||||||
, coder, encode, decoder
|
, encode, decoder
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -22,20 +22,22 @@ of a room.
|
||||||
|
|
||||||
## JSON Coder
|
## JSON Coder
|
||||||
|
|
||||||
@docs coder, encode, decoder
|
@docs encode, decoder
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import Internal.Config.Default as Default
|
import Internal.Config.Default as Default
|
||||||
import Internal.Config.Text as Text
|
import Internal.Tools.DecodeExtra as D
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.EncodeExtra as E
|
||||||
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
||||||
|
import Json.Decode as D
|
||||||
|
import Json.Encode as E
|
||||||
|
|
||||||
|
|
||||||
{-| The Event type occurs everywhere on a user's timeline.
|
{-| The Event type occurs everywhere on a user's timeline.
|
||||||
-}
|
-}
|
||||||
type alias Event =
|
type alias Event =
|
||||||
{ content : Json.Value
|
{ content : E.Value
|
||||||
, eventId : String
|
, eventId : String
|
||||||
, originServerTs : Timestamp
|
, originServerTs : Timestamp
|
||||||
, roomId : String
|
, roomId : String
|
||||||
|
@ -52,7 +54,7 @@ helper functions.
|
||||||
type UnsignedData
|
type UnsignedData
|
||||||
= UnsignedData
|
= UnsignedData
|
||||||
{ age : Maybe Int
|
{ age : Maybe Int
|
||||||
, prevContent : Maybe Json.Value
|
, prevContent : Maybe E.Value
|
||||||
, redactedBecause : Maybe Event
|
, redactedBecause : Maybe Event
|
||||||
, transactionId : Maybe String
|
, transactionId : Maybe String
|
||||||
}
|
}
|
||||||
|
@ -65,93 +67,66 @@ age event =
|
||||||
Maybe.andThen (\(UnsignedData data) -> data.age) event.unsigned
|
Maybe.andThen (\(UnsignedData data) -> data.age) event.unsigned
|
||||||
|
|
||||||
|
|
||||||
coder : Json.Coder Event
|
|
||||||
coder =
|
|
||||||
Json.object8
|
|
||||||
{ name = Text.docs.event.name
|
|
||||||
, description = Text.docs.event.description
|
|
||||||
, init = Event
|
|
||||||
}
|
|
||||||
(Json.field.required
|
|
||||||
{ fieldName = "content"
|
|
||||||
, toField = .content
|
|
||||||
, description = Text.fields.event.content
|
|
||||||
, coder = Json.value
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.required
|
|
||||||
{ fieldName = "eventId"
|
|
||||||
, toField = .eventId
|
|
||||||
, description = Text.fields.event.eventId
|
|
||||||
, coder = Json.string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.required
|
|
||||||
{ fieldName = "originServerTs"
|
|
||||||
, toField = .originServerTs
|
|
||||||
, description = Text.fields.event.originServerTs
|
|
||||||
, coder = Timestamp.coder
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.required
|
|
||||||
{ fieldName = "roomId"
|
|
||||||
, toField = .roomId
|
|
||||||
, description = Text.fields.event.roomId
|
|
||||||
, coder = Json.string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.required
|
|
||||||
{ fieldName = "sender"
|
|
||||||
, toField = .sender
|
|
||||||
, description = Text.fields.event.sender
|
|
||||||
, coder = Json.string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.optional.value
|
|
||||||
{ fieldName = "stateKey"
|
|
||||||
, toField = .stateKey
|
|
||||||
, description = Text.fields.event.stateKey
|
|
||||||
, coder = Json.string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.required
|
|
||||||
-- NOTE! | In JSON we call it `type`, not `eventType`,
|
|
||||||
-- NOTE! | so that the data is easier to read for other non-Elm
|
|
||||||
-- NOTE! | JSON parsers
|
|
||||||
{ fieldName = "type"
|
|
||||||
, toField = .eventType
|
|
||||||
, description = Text.fields.event.eventType
|
|
||||||
, coder = Json.string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.optional.value
|
|
||||||
{ fieldName = "unsigned"
|
|
||||||
, toField = .unsigned
|
|
||||||
, description = Text.fields.event.unsigned
|
|
||||||
, coder = unsignedCoder
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode an Event from a JSON value.
|
{-| Decode an Event from a JSON value.
|
||||||
-}
|
-}
|
||||||
decoder : Json.Decoder Event
|
decoder : D.Decoder Event
|
||||||
decoder =
|
decoder =
|
||||||
Json.decode coder
|
D.map8 Event
|
||||||
|
(D.field "content" D.value)
|
||||||
|
(D.field "eventId" D.string)
|
||||||
|
(D.field "originServerTs" Timestamp.decoder)
|
||||||
|
(D.field "roomId" D.string)
|
||||||
|
(D.field "sender" D.string)
|
||||||
|
(D.opField "stateKey" D.string)
|
||||||
|
(D.field "eventType" D.string)
|
||||||
|
(D.opField "unsigned" decoderUnsignedData)
|
||||||
|
|
||||||
|
|
||||||
|
{-| Decode Unsigned Data from a JSON value.
|
||||||
|
-}
|
||||||
|
decoderUnsignedData : D.Decoder UnsignedData
|
||||||
|
decoderUnsignedData =
|
||||||
|
D.map4 (\a b c d -> UnsignedData { age = a, prevContent = b, redactedBecause = c, transactionId = d })
|
||||||
|
(D.opField "age" D.int)
|
||||||
|
(D.opField "prevContent" D.value)
|
||||||
|
(D.opField "redactedBecause" (D.lazy (\_ -> decoder)))
|
||||||
|
(D.opField "transactionId" D.string)
|
||||||
|
|
||||||
|
|
||||||
{-| Encode an Event into a JSON value.
|
{-| Encode an Event into a JSON value.
|
||||||
-}
|
-}
|
||||||
encode : Json.Encoder Event
|
encode : Event -> E.Value
|
||||||
encode =
|
encode event =
|
||||||
Json.encode coder
|
E.maybeObject
|
||||||
|
[ ( "content", Just event.content )
|
||||||
|
, ( "eventId", Just <| E.string event.eventId )
|
||||||
|
, ( "originServerTs", Just <| Timestamp.encode event.originServerTs )
|
||||||
|
, ( "roomId", Just <| E.string event.roomId )
|
||||||
|
, ( "sender", Just <| E.string event.sender )
|
||||||
|
, ( "stateKey", Maybe.map E.string event.stateKey )
|
||||||
|
, ( "eventType", Just <| E.string event.eventType )
|
||||||
|
, ( "unsigned", Maybe.map encodeUnsignedData event.unsigned )
|
||||||
|
, ( "version", Just <| E.string Default.currentVersion )
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
{-| Encode Unsigned Data into a JSON value.
|
||||||
|
-}
|
||||||
|
encodeUnsignedData : UnsignedData -> E.Value
|
||||||
|
encodeUnsignedData (UnsignedData data) =
|
||||||
|
E.maybeObject
|
||||||
|
[ ( "age", Maybe.map E.int data.age )
|
||||||
|
, ( "prevContent", data.prevContent )
|
||||||
|
, ( "redactedBecause", Maybe.map encode data.redactedBecause )
|
||||||
|
, ( "transactionId", Maybe.map E.string data.transactionId )
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
{-| Determine the previous `content` value for this event. This field is only a
|
{-| Determine the previous `content` value for this event. This field is only a
|
||||||
`Just value` if the event is a state event, and the Matrix Vault has permission
|
`Just value` if the event is a state event, and the Matrix Vault has permission
|
||||||
to see the previous content.
|
to see the previous content.
|
||||||
-}
|
-}
|
||||||
prevContent : Event -> Maybe Json.Value
|
prevContent : Event -> Maybe E.Value
|
||||||
prevContent event =
|
prevContent event =
|
||||||
Maybe.andThen (\(UnsignedData data) -> data.prevContent) event.unsigned
|
Maybe.andThen (\(UnsignedData data) -> data.prevContent) event.unsigned
|
||||||
|
|
||||||
|
@ -170,40 +145,3 @@ display the original transaction id used for the event.
|
||||||
transactionId : Event -> Maybe String
|
transactionId : Event -> Maybe String
|
||||||
transactionId event =
|
transactionId event =
|
||||||
Maybe.andThen (\(UnsignedData data) -> data.transactionId) event.unsigned
|
Maybe.andThen (\(UnsignedData data) -> data.transactionId) event.unsigned
|
||||||
|
|
||||||
|
|
||||||
unsignedCoder : Json.Coder UnsignedData
|
|
||||||
unsignedCoder =
|
|
||||||
Json.object4
|
|
||||||
{ name = Text.docs.unsigned.name
|
|
||||||
, description = Text.docs.unsigned.description
|
|
||||||
, init = \a b c d -> UnsignedData { age = a, prevContent = b, redactedBecause = c, transactionId = d }
|
|
||||||
}
|
|
||||||
(Json.field.optional.value
|
|
||||||
{ fieldName = "age"
|
|
||||||
, toField = \(UnsignedData data) -> data.age
|
|
||||||
, description = Text.fields.unsigned.age
|
|
||||||
, coder = Json.int
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.optional.value
|
|
||||||
{ fieldName = "prevContent"
|
|
||||||
, toField = \(UnsignedData data) -> data.prevContent
|
|
||||||
, description = Text.fields.unsigned.prevContent
|
|
||||||
, coder = Json.value
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.optional.value
|
|
||||||
{ fieldName = "redactedBecause"
|
|
||||||
, toField = \(UnsignedData data) -> data.redactedBecause
|
|
||||||
, description = Text.fields.unsigned.redactedBecause
|
|
||||||
, coder = Json.lazy (\_ -> coder)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.optional.value
|
|
||||||
{ fieldName = "transactionId"
|
|
||||||
, toField = \(UnsignedData data) -> data.transactionId
|
|
||||||
, description = Text.fields.unsigned.transactionId
|
|
||||||
, coder = Json.string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ module Internal.Values.StateManager exposing
|
||||||
, empty, singleton, insert, remove, append
|
, empty, singleton, insert, remove, append
|
||||||
, isEmpty, member, memberKey, get, size, isEqual
|
, isEmpty, member, memberKey, get, size, isEqual
|
||||||
, keys, values, fromList, toList
|
, keys, values, fromList, toList
|
||||||
, coder, encode, decoder
|
, encode, decoder
|
||||||
)
|
)
|
||||||
|
|
||||||
{-| The StateManager tracks the room state based on events, their event types
|
{-| The StateManager tracks the room state based on events, their event types
|
||||||
|
@ -34,15 +34,15 @@ dictionary-like experience to navigate through the Matrix room state.
|
||||||
|
|
||||||
## JSON coders
|
## JSON coders
|
||||||
|
|
||||||
@docs coder, encode, decoder
|
@docs encode, decoder
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import FastDict as Dict exposing (Dict)
|
import FastDict as Dict exposing (Dict)
|
||||||
import Internal.Config.Text as Text
|
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
import Internal.Tools.Mashdict as Mashdict exposing (Mashdict)
|
import Internal.Tools.Mashdict as Mashdict exposing (Mashdict)
|
||||||
import Internal.Values.Event as Event exposing (Event)
|
import Internal.Values.Event as Event exposing (Event)
|
||||||
|
import Json.Decode as D
|
||||||
|
import Json.Encode as E
|
||||||
|
|
||||||
|
|
||||||
{-| The StateManager manages the room state by gathering events and looking at
|
{-| The StateManager manages the room state by gathering events and looking at
|
||||||
|
@ -93,24 +93,15 @@ cleanKey key (StateManager manager) =
|
||||||
|> StateManager
|
|> StateManager
|
||||||
|
|
||||||
|
|
||||||
coder : Json.Coder StateManager
|
|
||||||
coder =
|
|
||||||
Event.coder
|
|
||||||
|> Mashdict.coder .stateKey
|
|
||||||
|> Json.fastDict
|
|
||||||
|> Json.map
|
|
||||||
{ name = Text.docs.stateManager.name
|
|
||||||
, description = Text.docs.stateManager.description
|
|
||||||
, forth = StateManager
|
|
||||||
, back = \(StateManager manager) -> manager
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode a StateManager from a JSON value.
|
{-| Decode a StateManager from a JSON value.
|
||||||
-}
|
-}
|
||||||
decoder : Json.Decoder StateManager
|
decoder : D.Decoder StateManager
|
||||||
decoder =
|
decoder =
|
||||||
Json.decode coder
|
Event.decoder
|
||||||
|
|> Mashdict.decoder .stateKey
|
||||||
|
|> D.keyValuePairs
|
||||||
|
|> D.map Dict.fromList
|
||||||
|
|> D.map StateManager
|
||||||
|
|
||||||
|
|
||||||
{-| Create an empty StateManager.
|
{-| Create an empty StateManager.
|
||||||
|
@ -122,9 +113,11 @@ empty =
|
||||||
|
|
||||||
{-| Encode a StateManager into a JSON value.
|
{-| Encode a StateManager into a JSON value.
|
||||||
-}
|
-}
|
||||||
encode : Json.Encoder StateManager
|
encode : StateManager -> E.Value
|
||||||
encode =
|
encode (StateManager manager) =
|
||||||
Json.encode coder
|
manager
|
||||||
|
|> Dict.toCoreDict
|
||||||
|
|> E.dict identity (Mashdict.encode Event.encode)
|
||||||
|
|
||||||
|
|
||||||
{-| Build a StateManager using a list of events.
|
{-| Build a StateManager using a list of events.
|
||||||
|
|
|
@ -3,7 +3,6 @@ module Test.Tools.Hashdict exposing (..)
|
||||||
import Expect
|
import Expect
|
||||||
import Fuzz exposing (Fuzzer)
|
import Fuzz exposing (Fuzzer)
|
||||||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
import Internal.Values.Event as Event
|
import Internal.Values.Event as Event
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
|
@ -94,11 +93,11 @@ suite =
|
||||||
"JSON encode -> JSON decode"
|
"JSON encode -> JSON decode"
|
||||||
(\indent ->
|
(\indent ->
|
||||||
Hashdict.empty identity
|
Hashdict.empty identity
|
||||||
|> Json.encode (Hashdict.coder identity Json.string)
|
|> Hashdict.encode E.string
|
||||||
|> E.encode indent
|
|> E.encode indent
|
||||||
|> D.decodeString (Json.decode <| Hashdict.coder identity Json.string)
|
|> D.decodeString (Hashdict.decoder identity D.string)
|
||||||
|> Result.map (Tuple.mapFirst (Hashdict.isEqual (Hashdict.empty String.toUpper)))
|
|> Result.map (Hashdict.isEqual (Hashdict.empty String.toUpper))
|
||||||
|> Expect.equal (Ok ( True, [] ))
|
|> Expect.equal (Ok True)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
, describe "singleton"
|
, describe "singleton"
|
||||||
|
@ -165,11 +164,11 @@ suite =
|
||||||
"JSON encode -> JSON decode"
|
"JSON encode -> JSON decode"
|
||||||
(\hashdict indent ->
|
(\hashdict indent ->
|
||||||
hashdict
|
hashdict
|
||||||
|> Json.encode (Hashdict.coder .eventId Event.coder)
|
|> Hashdict.encode Event.encode
|
||||||
|> E.encode indent
|
|> E.encode indent
|
||||||
|> D.decodeString (Json.decode <| Hashdict.coder .eventId Event.coder)
|
|> D.decodeString (Hashdict.decoder .eventId Event.decoder)
|
||||||
|> Result.map (Tuple.mapFirst Hashdict.toList)
|
|> Result.map Hashdict.toList
|
||||||
|> Expect.equal (Ok ( Hashdict.toList hashdict, [] ))
|
|> Expect.equal (Ok <| Hashdict.toList hashdict)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -33,50 +33,6 @@ type alias Human5 =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type alias Human6 =
|
|
||||||
{ name : String
|
|
||||||
, age : Maybe Int
|
|
||||||
, hobbies : List String
|
|
||||||
, weight : Maybe Float
|
|
||||||
, height : Float
|
|
||||||
, invitedToParty : Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias Human7 =
|
|
||||||
{ name : String
|
|
||||||
, age : Maybe Int
|
|
||||||
, hobbies : List String
|
|
||||||
, weight : Maybe Float
|
|
||||||
, height : Float
|
|
||||||
, invitedToParty : Bool
|
|
||||||
, presentGiven : Maybe String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias Human8 =
|
|
||||||
{ name : String
|
|
||||||
, age : Maybe Int
|
|
||||||
, hobbies : List String
|
|
||||||
, weight : Maybe Float
|
|
||||||
, height : Float
|
|
||||||
, invitedToParty : Bool
|
|
||||||
, presentGiven : Maybe String
|
|
||||||
, grid : List (List Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias MegaHuman =
|
|
||||||
{ human2 : Human2
|
|
||||||
, human3 : Human3
|
|
||||||
, human4 : Human4
|
|
||||||
, human5 : Human5
|
|
||||||
, human6 : Human6
|
|
||||||
, human7 : Human7
|
|
||||||
, human8 : Human8
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ageField : Json.Field (Maybe Int) { a | age : Maybe Int }
|
ageField : Json.Field (Maybe Int) { a | age : Maybe Int }
|
||||||
ageField =
|
ageField =
|
||||||
Json.field.optional.value
|
Json.field.optional.value
|
||||||
|
@ -92,23 +48,6 @@ ageFuzzer =
|
||||||
Fuzz.maybe Fuzz.int
|
Fuzz.maybe Fuzz.int
|
||||||
|
|
||||||
|
|
||||||
gridField : Json.Field (List (List Int)) { a | grid : List (List Int) }
|
|
||||||
gridField =
|
|
||||||
Json.field.optional.withDefault
|
|
||||||
{ fieldName = "grid"
|
|
||||||
, toField = .grid
|
|
||||||
, description = []
|
|
||||||
, coder = Json.list (Json.list Json.int)
|
|
||||||
, default = ( [], [] )
|
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
gridFuzzer : Fuzzer (List (List Int))
|
|
||||||
gridFuzzer =
|
|
||||||
Fuzz.list (Fuzz.list Fuzz.int)
|
|
||||||
|
|
||||||
|
|
||||||
heightField : Json.Field Float { a | height : Float }
|
heightField : Json.Field Float { a | height : Float }
|
||||||
heightField =
|
heightField =
|
||||||
Json.field.required
|
Json.field.required
|
||||||
|
@ -141,29 +80,6 @@ hobbiesFuzzer =
|
||||||
Fuzz.list Fuzz.string
|
Fuzz.list Fuzz.string
|
||||||
|
|
||||||
|
|
||||||
invitedToPartyField : Json.Field Bool { a | invitedToParty : Bool }
|
|
||||||
invitedToPartyField =
|
|
||||||
Json.field.optional.withDefault
|
|
||||||
{ fieldName = "invitedToParty"
|
|
||||||
, toField = .invitedToParty
|
|
||||||
, description = []
|
|
||||||
, coder = Json.bool
|
|
||||||
, default = ( False, [] )
|
|
||||||
, defaultToString =
|
|
||||||
\b ->
|
|
||||||
if b then
|
|
||||||
"True"
|
|
||||||
|
|
||||||
else
|
|
||||||
"False"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
invitedToPartyFuzzer : Fuzzer Bool
|
|
||||||
invitedToPartyFuzzer =
|
|
||||||
Fuzz.bool
|
|
||||||
|
|
||||||
|
|
||||||
nameField : Json.Field String { a | name : String }
|
nameField : Json.Field String { a | name : String }
|
||||||
nameField =
|
nameField =
|
||||||
Json.field.required
|
Json.field.required
|
||||||
|
@ -179,21 +95,6 @@ nameFuzzer =
|
||||||
Fuzz.string
|
Fuzz.string
|
||||||
|
|
||||||
|
|
||||||
presentGivenField : Json.Field (Maybe String) { a | presentGiven : Maybe String }
|
|
||||||
presentGivenField =
|
|
||||||
Json.field.required
|
|
||||||
{ fieldName = "presentGiven"
|
|
||||||
, toField = .presentGiven
|
|
||||||
, description = []
|
|
||||||
, coder = Json.maybe Json.string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
presentGivenFuzzer : Fuzzer (Maybe String)
|
|
||||||
presentGivenFuzzer =
|
|
||||||
Fuzz.maybe Fuzz.string
|
|
||||||
|
|
||||||
|
|
||||||
weightField : Json.Field (Maybe Float) { a | weight : Maybe Float }
|
weightField : Json.Field (Maybe Float) { a | weight : Maybe Float }
|
||||||
weightField =
|
weightField =
|
||||||
Json.field.optional.value
|
Json.field.optional.value
|
||||||
|
@ -294,118 +195,6 @@ human5Fuzzer =
|
||||||
heightFuzzer
|
heightFuzzer
|
||||||
|
|
||||||
|
|
||||||
human6Coder : Json.Coder Human6
|
|
||||||
human6Coder =
|
|
||||||
Json.object6
|
|
||||||
{ name = "Human6"
|
|
||||||
, description = []
|
|
||||||
, init = Human6
|
|
||||||
}
|
|
||||||
nameField
|
|
||||||
ageField
|
|
||||||
hobbiesField
|
|
||||||
weightField
|
|
||||||
heightField
|
|
||||||
invitedToPartyField
|
|
||||||
|
|
||||||
|
|
||||||
human6Fuzzer : Fuzzer Human6
|
|
||||||
human6Fuzzer =
|
|
||||||
Fuzz.map6 Human6
|
|
||||||
nameFuzzer
|
|
||||||
ageFuzzer
|
|
||||||
hobbiesFuzzer
|
|
||||||
weightFuzzer
|
|
||||||
heightFuzzer
|
|
||||||
invitedToPartyFuzzer
|
|
||||||
|
|
||||||
|
|
||||||
human7Coder : Json.Coder Human7
|
|
||||||
human7Coder =
|
|
||||||
Json.object7
|
|
||||||
{ name = "Human7"
|
|
||||||
, description = []
|
|
||||||
, init = Human7
|
|
||||||
}
|
|
||||||
nameField
|
|
||||||
ageField
|
|
||||||
hobbiesField
|
|
||||||
weightField
|
|
||||||
heightField
|
|
||||||
invitedToPartyField
|
|
||||||
presentGivenField
|
|
||||||
|
|
||||||
|
|
||||||
human7Fuzzer : Fuzzer Human7
|
|
||||||
human7Fuzzer =
|
|
||||||
Fuzz.map7 Human7
|
|
||||||
nameFuzzer
|
|
||||||
ageFuzzer
|
|
||||||
hobbiesFuzzer
|
|
||||||
weightFuzzer
|
|
||||||
heightFuzzer
|
|
||||||
invitedToPartyFuzzer
|
|
||||||
presentGivenFuzzer
|
|
||||||
|
|
||||||
|
|
||||||
human8Coder : Json.Coder Human8
|
|
||||||
human8Coder =
|
|
||||||
Json.object8
|
|
||||||
{ name = "Human8"
|
|
||||||
, description = []
|
|
||||||
, init = Human8
|
|
||||||
}
|
|
||||||
nameField
|
|
||||||
ageField
|
|
||||||
hobbiesField
|
|
||||||
weightField
|
|
||||||
heightField
|
|
||||||
invitedToPartyField
|
|
||||||
presentGivenField
|
|
||||||
gridField
|
|
||||||
|
|
||||||
|
|
||||||
human8Fuzzer : Fuzzer Human8
|
|
||||||
human8Fuzzer =
|
|
||||||
Fuzz.map8 Human8
|
|
||||||
nameFuzzer
|
|
||||||
ageFuzzer
|
|
||||||
hobbiesFuzzer
|
|
||||||
weightFuzzer
|
|
||||||
heightFuzzer
|
|
||||||
invitedToPartyFuzzer
|
|
||||||
presentGivenFuzzer
|
|
||||||
gridFuzzer
|
|
||||||
|
|
||||||
|
|
||||||
megaHumanCoder : Json.Coder MegaHuman
|
|
||||||
megaHumanCoder =
|
|
||||||
Json.object7
|
|
||||||
{ name = "MegaHuman"
|
|
||||||
, description = []
|
|
||||||
, init = MegaHuman
|
|
||||||
}
|
|
||||||
(Json.field.required { fieldName = "h2", toField = .human2, description = [], coder = human2Coder })
|
|
||||||
(Json.field.required { fieldName = "h3", toField = .human3, description = [], coder = human3Coder })
|
|
||||||
(Json.field.required { fieldName = "h4", toField = .human4, description = [], coder = human4Coder })
|
|
||||||
(Json.field.required { fieldName = "h5", toField = .human5, description = [], coder = human5Coder })
|
|
||||||
(Json.field.required { fieldName = "h6", toField = .human6, description = [], coder = human6Coder })
|
|
||||||
(Json.field.required { fieldName = "h7", toField = .human7, description = [], coder = human7Coder })
|
|
||||||
(Json.field.required { fieldName = "h8", toField = .human8, description = [], coder = human8Coder })
|
|
||||||
|
|
||||||
|
|
||||||
megahumanFuzzer : Fuzzer MegaHuman
|
|
||||||
megahumanFuzzer =
|
|
||||||
Fuzz.map7 MegaHuman
|
|
||||||
human2Fuzzer
|
|
||||||
human3Fuzzer
|
|
||||||
human4Fuzzer
|
|
||||||
human5Fuzzer
|
|
||||||
human6Fuzzer
|
|
||||||
human7Fuzzer
|
|
||||||
human8Fuzzer
|
|
||||||
|
|
||||||
|
|
||||||
suite : Test
|
suite : Test
|
||||||
suite =
|
suite =
|
||||||
describe "JSON module"
|
describe "JSON module"
|
||||||
|
@ -457,52 +246,4 @@ suite =
|
||||||
|> Expect.equal (Ok human)
|
|> Expect.equal (Ok human)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
, describe "Human6"
|
|
||||||
[ fuzz human6Fuzzer
|
|
||||||
"Recoding succeeds"
|
|
||||||
(\human ->
|
|
||||||
human
|
|
||||||
|> Json.encode human6Coder
|
|
||||||
|> E.encode 0
|
|
||||||
|> D.decodeString (Json.decode human6Coder)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Expect.equal (Ok human)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
, describe "Human7"
|
|
||||||
[ fuzz human7Fuzzer
|
|
||||||
"Recoding succeeds"
|
|
||||||
(\human ->
|
|
||||||
human
|
|
||||||
|> Json.encode human7Coder
|
|
||||||
|> E.encode 0
|
|
||||||
|> D.decodeString (Json.decode human7Coder)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Expect.equal (Ok human)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
, describe "Human8"
|
|
||||||
[ fuzz human8Fuzzer
|
|
||||||
"Recoding succeeds"
|
|
||||||
(\human ->
|
|
||||||
human
|
|
||||||
|> Json.encode human8Coder
|
|
||||||
|> E.encode 0
|
|
||||||
|> D.decodeString (Json.decode human8Coder)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Expect.equal (Ok human)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
, describe "MegaHuman"
|
|
||||||
[ fuzz megahumanFuzzer
|
|
||||||
"Recoding succeeds"
|
|
||||||
(\megahuman ->
|
|
||||||
megahuman
|
|
||||||
|> Json.encode megaHumanCoder
|
|
||||||
|> E.encode 0
|
|
||||||
|> D.decodeString (Json.decode megaHumanCoder)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Expect.equal (Ok megahuman)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,6 @@ module Test.Tools.Mashdict exposing (..)
|
||||||
|
|
||||||
import Expect
|
import Expect
|
||||||
import Fuzz exposing (Fuzzer)
|
import Fuzz exposing (Fuzzer)
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
import Internal.Tools.Mashdict as Mashdict exposing (Mashdict)
|
import Internal.Tools.Mashdict as Mashdict exposing (Mashdict)
|
||||||
import Internal.Values.Event as Event
|
import Internal.Values.Event as Event
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
|
@ -94,11 +93,11 @@ suite =
|
||||||
"JSON encode -> JSON decode"
|
"JSON encode -> JSON decode"
|
||||||
(\indent ->
|
(\indent ->
|
||||||
Mashdict.empty Just
|
Mashdict.empty Just
|
||||||
|> Json.encode (Mashdict.coder Just Json.string)
|
|> Mashdict.encode E.string
|
||||||
|> E.encode indent
|
|> E.encode indent
|
||||||
|> D.decodeString (Json.decode <| Mashdict.coder Just Json.string)
|
|> D.decodeString (Mashdict.decoder Just D.string)
|
||||||
|> Result.map (Tuple.mapFirst <| Mashdict.isEqual (Mashdict.empty Just))
|
|> Result.map (Mashdict.isEqual (Mashdict.empty Just))
|
||||||
|> Expect.equal (Ok ( True, [] ))
|
|> Expect.equal (Ok True)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
, describe "singleton"
|
, describe "singleton"
|
||||||
|
@ -195,11 +194,11 @@ suite =
|
||||||
"JSON encode -> JSON decode"
|
"JSON encode -> JSON decode"
|
||||||
(\hashdict indent ->
|
(\hashdict indent ->
|
||||||
hashdict
|
hashdict
|
||||||
|> Json.encode (Mashdict.coder .stateKey Event.coder)
|
|> Mashdict.encode Event.encode
|
||||||
|> E.encode indent
|
|> E.encode indent
|
||||||
|> D.decodeString (Json.decode <| Mashdict.coder .stateKey Event.coder)
|
|> D.decodeString (Mashdict.decoder .stateKey Event.decoder)
|
||||||
|> Result.map (Tuple.mapFirst Mashdict.toList)
|
|> Result.map Mashdict.toList
|
||||||
|> Expect.equal (Ok ( Mashdict.toList hashdict, [] ))
|
|> Expect.equal (Ok <| Mashdict.toList hashdict)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,7 +26,7 @@ suite =
|
||||||
|> Timestamp.encode
|
|> Timestamp.encode
|
||||||
|> E.encode indent
|
|> E.encode indent
|
||||||
|> D.decodeString Timestamp.decoder
|
|> D.decodeString Timestamp.decoder
|
||||||
|> Expect.equal (Ok ( time, [] ))
|
|> Expect.equal (Ok time)
|
||||||
)
|
)
|
||||||
, fuzz fuzzer
|
, fuzz fuzzer
|
||||||
"JSON decode -> millis"
|
"JSON decode -> millis"
|
||||||
|
@ -42,7 +42,7 @@ suite =
|
||||||
n
|
n
|
||||||
|> E.int
|
|> E.int
|
||||||
|> D.decodeValue Timestamp.decoder
|
|> D.decodeValue Timestamp.decoder
|
||||||
|> Expect.equal (Ok ( Time.millisToPosix n, [] ))
|
|> Expect.equal (Ok <| Time.millisToPosix n)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
, describe "Identity"
|
, describe "Identity"
|
||||||
|
|
|
@ -84,7 +84,7 @@ suite =
|
||||||
|> StateManager.encode
|
|> StateManager.encode
|
||||||
|> E.encode 0
|
|> E.encode 0
|
||||||
|> D.decodeString StateManager.decoder
|
|> D.decodeString StateManager.decoder
|
||||||
|> Expect.equal (Ok ( StateManager.empty, [] ))
|
|> Expect.equal (Ok StateManager.empty)
|
||||||
|> always
|
|> always
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue