Refactor to new JSON coders
parent
28d2a17a10
commit
d1fbc87730
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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, [] ))
|
||||
)
|
||||
]
|
||||
]
|
||||
|
|
|
@ -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, [] ))
|
||||
)
|
||||
]
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
)
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue