Refactor to new JSON coders
parent
28d2a17a10
commit
d1fbc87730
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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, [] ))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -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, [] ))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -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