Refactor to new JSON coders

json-extra
Bram 2024-01-19 16:22:51 +01:00
parent 28d2a17a10
commit d1fbc87730
10 changed files with 414 additions and 158 deletions

View File

@ -1,8 +1,9 @@
module Internal.Config.Text exposing module Internal.Config.Text exposing
( accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid ( docs, failures, fields
, accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid
, versionsFoundLocally, versionsReceived, versionsFailedToDecode , versionsFoundLocally, versionsReceived, versionsFailedToDecode
, unsupportedVersionForEndpoint , unsupportedVersionForEndpoint
, decodedDictSize, leakingValueFound , decodedDictSize, invalidHashInHashdict, invalidHashInMashdict, 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
@ -24,6 +25,11 @@ 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
@ -53,11 +59,19 @@ 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, leakingValueFound @docs decodedDictSize, invalidHashInHashdict, invalidHashInMashdict, 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
@ -95,6 +109,117 @@ 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

View File

@ -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
, encode, decoder, softDecoder , coder, 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,13 +40,14 @@ This allows you to store values based on an externally defined identifier.
## JSON coders ## JSON coders
@docs encode, decoder, softDecoder @docs coder, encode, decoder, softDecoder
-} -}
import FastDict as Dict exposing (Dict) import FastDict as Dict exposing (Dict)
import Json.Decode as D import Internal.Config.Log as Log
import Json.Encode as E import Internal.Config.Text as Text
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
@ -80,25 +81,41 @@ 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) -> D.Decoder a -> D.Decoder (Hashdict a) decoder : (a -> String) -> Json.Coder a -> Json.Decoder (Hashdict a)
decoder f xDecoder = decoder f c1 =
D.keyValuePairs xDecoder Json.decode (coder f c1)
|> 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.
@ -112,12 +129,9 @@ 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 : (a -> E.Value) -> Hashdict a -> E.Value encode : Json.Coder a -> Json.Encoder (Hashdict a)
encode encodeX (Hashdict h) = encode c1 (Hashdict h) =
h.values Json.encode (coder h.hash c1) (Hashdict h)
|> Dict.toList
|> List.map (Tuple.mapSecond encodeX)
|> E.object
{-| Convert an association list into a hashdict. {-| Convert an association list into a hashdict.
@ -240,10 +254,20 @@ 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) -> D.Decoder a -> D.Decoder (Hashdict a) softDecoder : (a -> String) -> Json.Coder a -> Json.Decoder (Hashdict a)
softDecoder f xDecoder = softDecoder f c1 =
D.keyValuePairs xDecoder c1
|> D.map (List.map Tuple.second >> fromList f) |> Json.fastDict
|> 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

View File

@ -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
, encode, decoder, softDecoder , coder, encode, decoder, softDecoder
) )
{-| {-|
@ -48,13 +48,14 @@ In general, you are advised to learn more about the
## JSON coders ## JSON coders
@docs encode, decoder, softDecoder @docs coder, encode, decoder, softDecoder
-} -}
import FastDict as Dict exposing (Dict) import FastDict as Dict exposing (Dict)
import Json.Decode as D import Internal.Config.Log as Log
import Json.Encode as E import Internal.Config.Text as Text
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
@ -92,25 +93,39 @@ 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) -> D.Decoder a -> D.Decoder (Mashdict a) decoder : (a -> Maybe String) -> Json.Coder a -> Json.Decoder (Mashdict a)
decoder f xDecoder = decoder f c1 =
D.keyValuePairs xDecoder Json.decode (coder f c1)
|> 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.
@ -124,12 +139,9 @@ 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 : (a -> E.Value) -> Mashdict a -> E.Value encode : Json.Coder a -> Json.Encoder (Mashdict a)
encode encodeX (Mashdict h) = encode c1 (Mashdict h) =
h.values Json.encode (coder h.hash c1) (Mashdict h)
|> Dict.toList
|> List.map (Tuple.mapSecond encodeX)
|> E.object
{-| Convert an association list into a mashdict. {-| Convert an association list into a mashdict.
@ -266,10 +278,20 @@ 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) -> D.Decoder a -> D.Decoder (Mashdict a) softDecoder : (a -> Maybe String) -> Json.Coder a -> Json.Decoder (Mashdict a)
softDecoder f xDecoder = softDecoder f c1 =
D.keyValuePairs xDecoder c1
|> D.map (List.map Tuple.second >> fromList f) |> Json.fastDict
|> 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

View File

@ -1,6 +1,6 @@
module Internal.Tools.Timestamp exposing module Internal.Tools.Timestamp exposing
( Timestamp ( Timestamp
, encode, decoder , coder, 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,12 +14,11 @@ elm/time. This module offers ways to work with the timestamp in meaningful ways.
## JSON coders ## JSON coders
@docs encode, decoder @docs coder, encode, decoder
-} -}
import Json.Decode as D import Internal.Tools.Json as Json
import Json.Encode as E
import Time import Time
@ -29,15 +28,30 @@ 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 : Timestamp -> E.Value encode : Json.Encoder Timestamp
encode = encode =
Time.posixToMillis >> E.int Json.encode coder
{-| Decode a timestamp from a JSON value. {-| Decode a timestamp from a JSON value.
-} -}
decoder : D.Decoder Timestamp decoder : Json.Decoder Timestamp
decoder = decoder =
D.map Time.millisToPosix D.int Json.decode coder

View File

@ -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
, encode, decoder , coder, encode, decoder
) )
{-| {-|
@ -22,22 +22,20 @@ of a room.
## JSON Coder ## JSON Coder
@docs encode, decoder @docs coder, encode, decoder
-} -}
import Internal.Config.Default as Default import Internal.Config.Default as Default
import Internal.Tools.DecodeExtra as D import Internal.Config.Text as Text
import Internal.Tools.EncodeExtra as E import Internal.Tools.Json as Json
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 : E.Value { content : Json.Value
, eventId : String , eventId : String
, originServerTs : Timestamp , originServerTs : Timestamp
, roomId : String , roomId : String
@ -54,7 +52,7 @@ helper functions.
type UnsignedData type UnsignedData
= UnsignedData = UnsignedData
{ age : Maybe Int { age : Maybe Int
, prevContent : Maybe E.Value , prevContent : Maybe Json.Value
, redactedBecause : Maybe Event , redactedBecause : Maybe Event
, transactionId : Maybe String , transactionId : Maybe String
} }
@ -67,66 +65,93 @@ 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 : D.Decoder Event decoder : Json.Decoder Event
decoder = decoder =
D.map8 Event Json.decode coder
(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 : Event -> E.Value encode : Json.Encoder Event
encode event = encode =
E.maybeObject Json.encode coder
[ ( "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 E.Value prevContent : Event -> Maybe Json.Value
prevContent event = prevContent event =
Maybe.andThen (\(UnsignedData data) -> data.prevContent) event.unsigned Maybe.andThen (\(UnsignedData data) -> data.prevContent) event.unsigned
@ -145,3 +170,40 @@ 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
}
)

View File

@ -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
, encode, decoder , coder, 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 encode, decoder @docs coder, 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,15 +93,24 @@ 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 : D.Decoder StateManager decoder : Json.Decoder StateManager
decoder = decoder =
Event.decoder Json.decode coder
|> Mashdict.decoder .stateKey
|> D.keyValuePairs
|> D.map Dict.fromList
|> D.map StateManager
{-| Create an empty StateManager. {-| Create an empty StateManager.
@ -113,11 +122,9 @@ empty =
{-| Encode a StateManager into a JSON value. {-| Encode a StateManager into a JSON value.
-} -}
encode : StateManager -> E.Value encode : Json.Encoder StateManager
encode (StateManager manager) = encode =
manager Json.encode coder
|> 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.

View File

@ -3,6 +3,7 @@ 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
@ -93,11 +94,11 @@ suite =
"JSON encode -> JSON decode" "JSON encode -> JSON decode"
(\indent -> (\indent ->
Hashdict.empty identity Hashdict.empty identity
|> Hashdict.encode E.string |> Json.encode (Hashdict.coder identity Json.string)
|> E.encode indent |> E.encode indent
|> D.decodeString (Hashdict.decoder identity D.string) |> D.decodeString (Json.decode <| Hashdict.coder identity Json.string)
|> Result.map (Hashdict.isEqual (Hashdict.empty String.toUpper)) |> Result.map (Tuple.mapFirst (Hashdict.isEqual (Hashdict.empty String.toUpper)))
|> Expect.equal (Ok True) |> Expect.equal (Ok ( True, [] ))
) )
] ]
, describe "singleton" , describe "singleton"
@ -164,11 +165,11 @@ suite =
"JSON encode -> JSON decode" "JSON encode -> JSON decode"
(\hashdict indent -> (\hashdict indent ->
hashdict hashdict
|> Hashdict.encode Event.encode |> Json.encode (Hashdict.coder .eventId Event.coder)
|> E.encode indent |> E.encode indent
|> D.decodeString (Hashdict.decoder .eventId Event.decoder) |> D.decodeString (Json.decode <| Hashdict.coder .eventId Event.coder)
|> Result.map Hashdict.toList |> Result.map (Tuple.mapFirst Hashdict.toList)
|> Expect.equal (Ok <| Hashdict.toList hashdict) |> Expect.equal (Ok ( Hashdict.toList hashdict, [] ))
) )
] ]
] ]

View File

@ -2,6 +2,7 @@ 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
@ -93,11 +94,11 @@ suite =
"JSON encode -> JSON decode" "JSON encode -> JSON decode"
(\indent -> (\indent ->
Mashdict.empty Just Mashdict.empty Just
|> Mashdict.encode E.string |> Json.encode (Mashdict.coder Just Json.string)
|> E.encode indent |> E.encode indent
|> D.decodeString (Mashdict.decoder Just D.string) |> D.decodeString (Json.decode <| Mashdict.coder Just Json.string)
|> Result.map (Mashdict.isEqual (Mashdict.empty Just)) |> Result.map (Tuple.mapFirst <| Mashdict.isEqual (Mashdict.empty Just))
|> Expect.equal (Ok True) |> Expect.equal (Ok ( True, [] ))
) )
] ]
, describe "singleton" , describe "singleton"
@ -194,11 +195,11 @@ suite =
"JSON encode -> JSON decode" "JSON encode -> JSON decode"
(\hashdict indent -> (\hashdict indent ->
hashdict hashdict
|> Mashdict.encode Event.encode |> Json.encode (Mashdict.coder .stateKey Event.coder)
|> E.encode indent |> E.encode indent
|> D.decodeString (Mashdict.decoder .stateKey Event.decoder) |> D.decodeString (Json.decode <| Mashdict.coder .stateKey Event.coder)
|> Result.map Mashdict.toList |> Result.map (Tuple.mapFirst Mashdict.toList)
|> Expect.equal (Ok <| Mashdict.toList hashdict) |> Expect.equal (Ok ( Mashdict.toList hashdict, [] ))
) )
] ]
] ]

View File

@ -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"

View File

@ -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
) )
] ]