diff --git a/src/Internal/Config/Text.elm b/src/Internal/Config/Text.elm index 745b15b..1159868 100644 --- a/src/Internal/Config/Text.elm +++ b/src/Internal/Config/Text.elm @@ -1,8 +1,9 @@ module Internal.Config.Text exposing - ( accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid + ( docs, failures, fields + , accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid , versionsFoundLocally, versionsReceived, versionsFailedToDecode , unsupportedVersionForEndpoint - , decodedDictSize, leakingValueFound + , decodedDictSize, invalidHashInHashdict, invalidHashInMashdict, leakingValueFound ) {-| 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. +## Type documentation + +@docs docs, failures, fields + + ## API Authentication 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. -@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. -} 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), 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 diff --git a/src/Internal/Tools/Hashdict.elm b/src/Internal/Tools/Hashdict.elm index f2e4fdb..3b46f30 100644 --- a/src/Internal/Tools/Hashdict.elm +++ b/src/Internal/Tools/Hashdict.elm @@ -4,7 +4,7 @@ module Internal.Tools.Hashdict exposing , isEmpty, member, memberKey, get, size, isEqual , keys, values, toList, fromList , rehash, union - , encode, decoder, softDecoder + , coder, encode, decoder, softDecoder ) {-| 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 -@docs encode, decoder, softDecoder +@docs coder, encode, decoder, softDecoder -} import FastDict as Dict exposing (Dict) -import Json.Decode as D -import Json.Encode as E +import Internal.Config.Log as Log +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 @@ -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 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. -} -decoder : (a -> String) -> D.Decoder a -> D.Decoder (Hashdict a) -decoder f xDecoder = - 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" - ) +decoder : (a -> String) -> Json.Coder a -> Json.Decoder (Hashdict a) +decoder f c1 = + Json.decode (coder f c1) {-| 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 hash function! -} -encode : (a -> E.Value) -> Hashdict a -> E.Value -encode encodeX (Hashdict h) = - h.values - |> Dict.toList - |> List.map (Tuple.mapSecond encodeX) - |> E.object +encode : Json.Coder a -> Json.Encoder (Hashdict a) +encode c1 (Hashdict h) = + Json.encode (coder h.hash c1) (Hashdict h) {-| 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 decode and rehash the Hashdict using your new hash function. -} -softDecoder : (a -> String) -> D.Decoder a -> D.Decoder (Hashdict a) -softDecoder f xDecoder = - D.keyValuePairs xDecoder - |> D.map (List.map Tuple.second >> fromList f) +softDecoder : (a -> String) -> Json.Coder a -> Json.Decoder (Hashdict a) +softDecoder f c1 = + c1 + |> 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 diff --git a/src/Internal/Tools/Mashdict.elm b/src/Internal/Tools/Mashdict.elm index 22c27a8..7ede8c3 100644 --- a/src/Internal/Tools/Mashdict.elm +++ b/src/Internal/Tools/Mashdict.elm @@ -4,7 +4,7 @@ module Internal.Tools.Mashdict exposing , isEmpty, member, memberKey, get, size, isEqual , keys, values, toList, fromList , 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 -@docs encode, decoder, softDecoder +@docs coder, encode, decoder, softDecoder -} import FastDict as Dict exposing (Dict) -import Json.Decode as D -import Json.Encode as E +import Internal.Config.Log as Log +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 @@ -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 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. -} -decoder : (a -> Maybe String) -> D.Decoder a -> D.Decoder (Mashdict a) -decoder f xDecoder = - 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" - ) +decoder : (a -> Maybe String) -> Json.Coder a -> Json.Decoder (Mashdict a) +decoder f c1 = + Json.decode (coder f c1) {-| 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 hash function! -} -encode : (a -> E.Value) -> Mashdict a -> E.Value -encode encodeX (Mashdict h) = - h.values - |> Dict.toList - |> List.map (Tuple.mapSecond encodeX) - |> E.object +encode : Json.Coder a -> Json.Encoder (Mashdict a) +encode c1 (Mashdict h) = + Json.encode (coder h.hash c1) (Mashdict h) {-| 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 decode and rehash the Mashdict using your new hash function. -} -softDecoder : (a -> Maybe String) -> D.Decoder a -> D.Decoder (Mashdict a) -softDecoder f xDecoder = - D.keyValuePairs xDecoder - |> D.map (List.map Tuple.second >> fromList f) +softDecoder : (a -> Maybe String) -> Json.Coder a -> Json.Decoder (Mashdict a) +softDecoder f c1 = + c1 + |> 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 diff --git a/src/Internal/Tools/Timestamp.elm b/src/Internal/Tools/Timestamp.elm index a0ed35c..0f96a77 100644 --- a/src/Internal/Tools/Timestamp.elm +++ b/src/Internal/Tools/Timestamp.elm @@ -1,6 +1,6 @@ module Internal.Tools.Timestamp exposing ( Timestamp - , encode, decoder + , coder, encode, decoder ) {-| 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 -@docs encode, decoder +@docs coder, encode, decoder -} -import Json.Decode as D -import Json.Encode as E +import Internal.Tools.Json as Json import Time @@ -29,15 +28,30 @@ type alias Timestamp = 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 : Timestamp -> E.Value +encode : Json.Encoder Timestamp encode = - Time.posixToMillis >> E.int + Json.encode coder {-| Decode a timestamp from a JSON value. -} -decoder : D.Decoder Timestamp +decoder : Json.Decoder Timestamp decoder = - D.map Time.millisToPosix D.int + Json.decode coder diff --git a/src/Internal/Values/Event.elm b/src/Internal/Values/Event.elm index b27848d..304e7cb 100644 --- a/src/Internal/Values/Event.elm +++ b/src/Internal/Values/Event.elm @@ -1,7 +1,7 @@ module Internal.Values.Event exposing ( Event , UnsignedData(..), age, prevContent, redactedBecause, transactionId - , encode, decoder + , coder, encode, decoder ) {-| @@ -22,22 +22,20 @@ of a room. ## JSON Coder -@docs encode, decoder +@docs coder, encode, decoder -} import Internal.Config.Default as Default -import Internal.Tools.DecodeExtra as D -import Internal.Tools.EncodeExtra as E +import Internal.Config.Text as Text +import Internal.Tools.Json as Json 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. -} type alias Event = - { content : E.Value + { content : Json.Value , eventId : String , originServerTs : Timestamp , roomId : String @@ -54,7 +52,7 @@ helper functions. type UnsignedData = UnsignedData { age : Maybe Int - , prevContent : Maybe E.Value + , prevContent : Maybe Json.Value , redactedBecause : Maybe Event , transactionId : Maybe String } @@ -67,66 +65,93 @@ age event = 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. -} -decoder : D.Decoder Event +decoder : Json.Decoder Event decoder = - 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) + Json.decode coder {-| Encode an Event into a JSON value. -} -encode : Event -> E.Value -encode event = - 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 ) - ] +encode : Json.Encoder Event +encode = + Json.encode coder {-| 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 to see the previous content. -} -prevContent : Event -> Maybe E.Value +prevContent : Event -> Maybe Json.Value prevContent event = 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.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 + } + ) diff --git a/src/Internal/Values/StateManager.elm b/src/Internal/Values/StateManager.elm index 46282aa..635470a 100644 --- a/src/Internal/Values/StateManager.elm +++ b/src/Internal/Values/StateManager.elm @@ -3,7 +3,7 @@ module Internal.Values.StateManager exposing , empty, singleton, insert, remove, append , isEmpty, member, memberKey, get, size, isEqual , keys, values, fromList, toList - , encode, decoder + , coder, encode, decoder ) {-| 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 -@docs encode, decoder +@docs coder, encode, decoder -} 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.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 @@ -93,15 +93,24 @@ cleanKey key (StateManager manager) = |> 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. -} -decoder : D.Decoder StateManager +decoder : Json.Decoder StateManager decoder = - Event.decoder - |> Mashdict.decoder .stateKey - |> D.keyValuePairs - |> D.map Dict.fromList - |> D.map StateManager + Json.decode coder {-| Create an empty StateManager. @@ -113,11 +122,9 @@ empty = {-| Encode a StateManager into a JSON value. -} -encode : StateManager -> E.Value -encode (StateManager manager) = - manager - |> Dict.toCoreDict - |> E.dict identity (Mashdict.encode Event.encode) +encode : Json.Encoder StateManager +encode = + Json.encode coder {-| Build a StateManager using a list of events. diff --git a/tests/Test/Tools/Hashdict.elm b/tests/Test/Tools/Hashdict.elm index 500503c..cdfdf43 100644 --- a/tests/Test/Tools/Hashdict.elm +++ b/tests/Test/Tools/Hashdict.elm @@ -3,6 +3,7 @@ module Test.Tools.Hashdict exposing (..) import Expect import Fuzz exposing (Fuzzer) import Internal.Tools.Hashdict as Hashdict exposing (Hashdict) +import Internal.Tools.Json as Json import Internal.Values.Event as Event import Json.Decode as D import Json.Encode as E @@ -93,11 +94,11 @@ suite = "JSON encode -> JSON decode" (\indent -> Hashdict.empty identity - |> Hashdict.encode E.string + |> Json.encode (Hashdict.coder identity Json.string) |> E.encode indent - |> D.decodeString (Hashdict.decoder identity D.string) - |> Result.map (Hashdict.isEqual (Hashdict.empty String.toUpper)) - |> Expect.equal (Ok True) + |> D.decodeString (Json.decode <| Hashdict.coder identity Json.string) + |> Result.map (Tuple.mapFirst (Hashdict.isEqual (Hashdict.empty String.toUpper))) + |> Expect.equal (Ok ( True, [] )) ) ] , describe "singleton" @@ -164,11 +165,11 @@ suite = "JSON encode -> JSON decode" (\hashdict indent -> hashdict - |> Hashdict.encode Event.encode + |> Json.encode (Hashdict.coder .eventId Event.coder) |> E.encode indent - |> D.decodeString (Hashdict.decoder .eventId Event.decoder) - |> Result.map Hashdict.toList - |> Expect.equal (Ok <| Hashdict.toList hashdict) + |> D.decodeString (Json.decode <| Hashdict.coder .eventId Event.coder) + |> Result.map (Tuple.mapFirst Hashdict.toList) + |> Expect.equal (Ok ( Hashdict.toList hashdict, [] )) ) ] ] diff --git a/tests/Test/Tools/Mashdict.elm b/tests/Test/Tools/Mashdict.elm index dfddc6c..0425dc0 100644 --- a/tests/Test/Tools/Mashdict.elm +++ b/tests/Test/Tools/Mashdict.elm @@ -2,6 +2,7 @@ module Test.Tools.Mashdict exposing (..) import Expect import Fuzz exposing (Fuzzer) +import Internal.Tools.Json as Json import Internal.Tools.Mashdict as Mashdict exposing (Mashdict) import Internal.Values.Event as Event import Json.Decode as D @@ -93,11 +94,11 @@ suite = "JSON encode -> JSON decode" (\indent -> Mashdict.empty Just - |> Mashdict.encode E.string + |> Json.encode (Mashdict.coder Just Json.string) |> E.encode indent - |> D.decodeString (Mashdict.decoder Just D.string) - |> Result.map (Mashdict.isEqual (Mashdict.empty Just)) - |> Expect.equal (Ok True) + |> D.decodeString (Json.decode <| Mashdict.coder Just Json.string) + |> Result.map (Tuple.mapFirst <| Mashdict.isEqual (Mashdict.empty Just)) + |> Expect.equal (Ok ( True, [] )) ) ] , describe "singleton" @@ -194,11 +195,11 @@ suite = "JSON encode -> JSON decode" (\hashdict indent -> hashdict - |> Mashdict.encode Event.encode + |> Json.encode (Mashdict.coder .stateKey Event.coder) |> E.encode indent - |> D.decodeString (Mashdict.decoder .stateKey Event.decoder) - |> Result.map Mashdict.toList - |> Expect.equal (Ok <| Mashdict.toList hashdict) + |> D.decodeString (Json.decode <| Mashdict.coder .stateKey Event.coder) + |> Result.map (Tuple.mapFirst Mashdict.toList) + |> Expect.equal (Ok ( Mashdict.toList hashdict, [] )) ) ] ] diff --git a/tests/Test/Tools/Timestamp.elm b/tests/Test/Tools/Timestamp.elm index d98cafb..5721821 100644 --- a/tests/Test/Tools/Timestamp.elm +++ b/tests/Test/Tools/Timestamp.elm @@ -26,7 +26,7 @@ suite = |> Timestamp.encode |> E.encode indent |> D.decodeString Timestamp.decoder - |> Expect.equal (Ok time) + |> Expect.equal (Ok ( time, [] )) ) , fuzz fuzzer "JSON decode -> millis" @@ -42,7 +42,7 @@ suite = n |> E.int |> D.decodeValue Timestamp.decoder - |> Expect.equal (Ok <| Time.millisToPosix n) + |> Expect.equal (Ok ( Time.millisToPosix n, [] )) ) ] , describe "Identity" diff --git a/tests/Test/Values/StateManager.elm b/tests/Test/Values/StateManager.elm index ec15032..7e0839e 100644 --- a/tests/Test/Values/StateManager.elm +++ b/tests/Test/Values/StateManager.elm @@ -84,7 +84,7 @@ suite = |> StateManager.encode |> E.encode 0 |> D.decodeString StateManager.decoder - |> Expect.equal (Ok StateManager.empty) + |> Expect.equal (Ok ( StateManager.empty, [] )) |> always ) ]