Compare commits

...

5 Commits

Author SHA1 Message Date
BramvdnHeuvel cee33f6f0a
Merge f714438dd4 into a95fbbb856 2024-07-02 10:25:05 +02:00
noordstar a95fbbb856
Merge pull request #30 from noordstar/patch-iddict
Move Iddict to noordstar/elm-iddict
2024-07-02 10:24:54 +02:00
Bram 6300d15edf Move Iddict to noordstar/elm-iddict 2024-06-13 19:47:39 +02:00
Bram f714438dd4 Add logs function for better runtime documentation 2024-06-03 11:05:06 +02:00
Bram a514497406 Improve documentation of sendMessageEvent 2024-06-03 11:04:34 +02:00
7 changed files with 67 additions and 496 deletions

View File

@ -20,7 +20,8 @@
"elm/time": "1.0.0 <= v < 2.0.0",
"elm/url": "1.0.0 <= v < 2.0.0",
"micahhahn/elm-safe-recursion": "2.0.0 <= v < 3.0.0",
"miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0"
"miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0",
"noordstar/elm-iddict": "1.0.1 <= v < 2.0.0"
},
"test-dependencies": {
"elm-explorations/test": "2.1.2 <= v < 3.0.0"

View File

@ -118,7 +118,6 @@ docs :
, event : TypeDocs
, hashdict : TypeDocs
, ibatch : TypeDocs
, iddict : TypeDocs
, itoken : TypeDocs
, mashdict : TypeDocs
, room : TypeDocs
@ -169,12 +168,6 @@ docs =
[ "The internal batch tracks a patch of events on the Matrix timeline."
]
}
, iddict =
{ name = "Iddict"
, description =
[ "An iddict automatically handles creating appropriate keys by incrementally assiging a new key to new values."
]
}
, itoken =
{ name = "IToken"
, description =

View File

@ -1,198 +0,0 @@
module Internal.Tools.Iddict exposing
( Iddict
, empty, singleton, insert, map, remove
, isEmpty, member, get, size
, keys, values
, coder, encode, decoder
)
{-| The id-dict is a data type that lets us store values in a dictionary using
unique identifiers. This can be used as a dictionary where the keys do not
matter.
The benefit of the iddict is that it generates the keys FOR you. This way, you
do not need to generate identifiers yourself.
## Id-dict
@docs Iddict
## Build
@docs empty, singleton, insert, map, remove
## Query
@docs isEmpty, member, get, size
## Lists
@docs keys, values
## JSON coders
@docs coder, encode, decoder
-}
import FastDict as Dict exposing (Dict)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
{-| The Iddict data type.
-}
type Iddict a
= Iddict
{ cursor : Int
, dict : Dict Int a
}
{-| Define how an Iddict can be encoded and decoded to and from a JSON value.
-}
coder : Json.Coder a -> Json.Coder (Iddict a)
coder x =
Json.object2
{ name = Text.docs.iddict.name
, description = Text.docs.iddict.description
, init =
\c d ->
Iddict
{ cursor =
Dict.keys d
|> List.maximum
|> Maybe.map ((+) 1)
|> Maybe.withDefault 0
|> max (Dict.size d)
|> max c
, dict = d
}
}
(Json.field.optional.withDefault
{ fieldName = "cursor"
, toField = \(Iddict i) -> i.cursor
, description = Text.fields.iddict.cursor
, coder = Json.int
, default = ( 0, [] )
, defaultToString = String.fromInt
}
)
(Json.field.required
{ fieldName = "dict"
, toField = \(Iddict i) -> i.dict
, description = Text.fields.iddict.dict
, coder = Json.fastIntDict x
}
)
{-| Decode an id-dict from a JSON value.
-}
decoder : Json.Coder a -> Json.Decoder (Iddict a)
decoder x =
Json.decode (coder x)
{-| Create an empty id-dict.
-}
empty : Iddict a
empty =
Iddict
{ cursor = 0
, dict = Dict.empty
}
{-| Encode an id-dict to a JSON value.
-}
encode : Json.Coder a -> Json.Encoder (Iddict a)
encode x =
Json.encode (coder x)
{-| Get a value from the id-dict using its key.
-}
get : Int -> Iddict a -> Maybe a
get k (Iddict { dict }) =
Dict.get k dict
{-| Insert a new value into the id-dict. Given that the id-dict generates its
key, the function returns both the updated id-dict as the newly generated key.
x = empty |> insert "hello" -- ( 0, <Iddict with value "hello"> )
case x of
( _, iddict ) ->
get 0 iddict -- Just "hello"
-}
insert : a -> Iddict a -> ( Int, Iddict a )
insert v (Iddict d) =
( d.cursor
, Iddict { cursor = d.cursor + 1, dict = Dict.insert d.cursor v d.dict }
)
{-| Determine if an id-dict is empty.
-}
isEmpty : Iddict a -> Bool
isEmpty (Iddict d) =
Dict.isEmpty d.dict
{-| Get all of the keys from the id-dict, sorted from lowest to highest.
-}
keys : Iddict a -> List Int
keys (Iddict { dict }) =
Dict.keys dict
{-| Map an existing value at a given key, if it exists. If it does not exist,
the operation does nothing.
-}
map : Int -> (a -> a) -> Iddict a -> Iddict a
map k f (Iddict d) =
Iddict { d | dict = Dict.update k (Maybe.map f) d.dict }
{-| Determine if a key is in an id-dict.
-}
member : Int -> Iddict a -> Bool
member k (Iddict d) =
k < d.cursor && Dict.member k d.dict
{-| Remove a key-value pair from the id-dict. If the key is not found, no
changes are made.
-}
remove : Int -> Iddict a -> Iddict a
remove k (Iddict d) =
Iddict { d | dict = Dict.remove k d.dict }
{-| Create an id-dict with a single value.
-}
singleton : a -> ( Int, Iddict a )
singleton v =
insert v empty
{-| Determine the number of key-value pairs in the id-dict.
-}
size : Iddict a -> Int
size (Iddict d) =
Dict.size d.dict
{-| Get all of the values from an id-dict, in the order of their keys.
-}
values : Iddict a -> List a
values (Iddict { dict }) =
Dict.values dict

View File

@ -3,7 +3,7 @@ module Internal.Tools.Json exposing
, Encoder, encode, Decoder, decode, Value
, succeed, fail, andThen, lazy, map
, Docs(..), RequiredField(..), toDocs
, list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
, list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe
, Field, field, parser
, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11
)
@ -49,7 +49,7 @@ module to build its encoders and decoders.
## Data types
@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe
## Objects
@ -68,6 +68,7 @@ Once all fields are constructed, the user can create JSON objects.
import Dict as SlowDict
import FastDict
import Iddict exposing (Iddict)
import Internal.Config.Log as Log exposing (Log)
import Internal.Config.Text as Text
import Internal.Tools.DecodeExtra as D
@ -141,6 +142,7 @@ type Docs
= DocsBool
| DocsDict Docs
| DocsFloat
| DocsIddict Docs
| DocsInt
| DocsIntDict Docs
| DocsLazy (() -> Docs)
@ -467,6 +469,25 @@ float =
}
{-| Define an Iddict as defined in
[noordstar/elm-iddict](https://package.elm-lang.org/packages/noordstar/elm-iddict/latest/).
-}
iddict : Coder a -> Coder (Iddict a)
iddict (Coder old) =
Coder
{ encoder = Iddict.encode old.encoder
, decoder =
D.andThen
(\( out, logs ) ->
D.succeed out
|> Iddict.decoder
|> D.map (\o -> ( o, logs ))
)
old.decoder
, docs = DocsIddict old.docs
}
{-| Define an int value.
-}
int : Coder Int

View File

@ -67,10 +67,10 @@ events!
-}
import FastDict as Dict exposing (Dict)
import Iddict exposing (Iddict)
import Internal.Config.Text as Text
import Internal.Filter.Timeline as Filter exposing (Filter)
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
import Internal.Tools.Iddict as Iddict exposing (Iddict)
import Internal.Tools.Json as Json
import Recursion
import Recursion.Traverse
@ -210,7 +210,7 @@ coder =
{ fieldName = "batches"
, toField = \(Timeline t) -> t.batches
, description = Text.fields.timeline.batches
, coder = Iddict.coder coderIBatch
, coder = Json.iddict coderIBatch
}
)
(Json.field.required
@ -411,8 +411,8 @@ connectIBatchToIToken (IBatchPTR bptr) pointer (Timeline tl) =
Timeline
{ tl
| batches =
Iddict.map bptr
(\batch -> { batch | end = pointer })
Iddict.update bptr
(Maybe.map (\batch -> { batch | end = pointer }))
tl.batches
, tokens =
Hashdict.map tptr
@ -437,8 +437,8 @@ connectITokenToIBatch pointer (IBatchPTR bptr) (Timeline tl) =
(\token -> { token | starts = Set.insert bptr token.starts })
tl.tokens
, batches =
Iddict.map bptr
(\batch -> { batch | start = pointer })
Iddict.update bptr
(Maybe.map (\batch -> { batch | start = pointer }))
tl.batches
}

View File

@ -1,6 +1,6 @@
module Matrix exposing
( Vault, fromUserId, fromUsername
, VaultUpdate, update
, VaultUpdate, update, logs
, addAccessToken, sendMessageEvent
)
@ -24,7 +24,7 @@ support a monolithic public registry. (:
## Keeping the Vault up-to-date
@docs VaultUpdate, update
@docs VaultUpdate, update, logs
## Debugging
@ -112,6 +112,28 @@ fromUsername { username, host, port_ } =
|> Vault
{-| The VaultUpdate is a complex type that helps update the Vault. However,
it also contains a human output!
Using this function, you can get a human output that describes everything that
the VaultUpdate has to tell the Vault.
The `channel` field describes the context of the log, allowing you to filter
further. For example:
- `debug` is a comprehensive channel describing everything the Elm runtime has
executed.
- `warn` contains warnings that aren't breaking, but relevant.
- `securityWarn` warns about potential security issues or potential attacks.
- `error` has errors that were encountered.
- `caughtError` has errors that were dealt with successfully.
-}
logs : VaultUpdate -> List { channel : String, content : String }
logs (VaultUpdate vu) =
vu.logs
{-| Send a message event to a room.
This function can be used in a scenario where the user does not want to sync
@ -119,6 +141,18 @@ the client, or is unable to. This function doesn't check whether the given room
exists and the user is able to send a message to, and instead just sends the
request to the Matrix API.
The fields stand for the following:
- `content` is the JSON object that is sent to the Matrix room.
- `eventType` is the event type that is sent to the Matrix room.
- `roomId` is the Matrix room ID.
- `toMsg` is the `msg` type that is returned after the message has been sent.
- `transactionId` is a unique identifier that helps the Matrix server
distringuish messages. If you send the same message with the same transactionId,
the server promises to register it only once.
- `vault` is the Matrix Vault that contains all the latest and most relevant
information.
-}
sendMessageEvent :
{ content : E.Value

View File

@ -1,280 +0,0 @@
module Test.Tools.Iddict exposing (..)
import Expect
import Fuzz exposing (Fuzzer)
import Internal.Tools.Iddict as Iddict exposing (Iddict)
import Internal.Tools.Json as Json
import Json.Decode as D
import Json.Encode as E
import Test exposing (..)
fuzzer : Fuzzer a -> Fuzzer (Iddict a)
fuzzer fuz =
fuz
|> Fuzz.pair Fuzz.bool
|> Fuzz.list
|> Fuzz.map
(\items ->
List.foldl
(\( rm, item ) dict ->
case Iddict.insert item dict of
( key, d ) ->
if rm then
Iddict.remove key d
else
d
)
Iddict.empty
items
)
empty : Test
empty =
describe "empty"
[ test "isEmpty"
(Iddict.empty
|> Iddict.isEmpty
|> Expect.equal True
|> always
)
, fuzz Fuzz.int
"No members"
(\i ->
Iddict.empty
|> Iddict.member i
|> Expect.equal False
)
, fuzz Fuzz.int
"Get gets Nothing"
(\i ->
Iddict.empty
|> Iddict.get i
|> Expect.equal Nothing
)
, test "Size = 0"
(Iddict.empty
|> Iddict.size
|> Expect.equal 0
|> always
)
, test "No keys"
(Iddict.empty
|> Iddict.keys
|> Expect.equal []
|> always
)
, test "No values"
(Iddict.empty
|> Iddict.values
|> Expect.equal []
|> always
)
, test "JSON encode -> decode -> empty"
(Iddict.empty
|> Iddict.encode Json.value
|> D.decodeValue (Iddict.decoder Json.value)
|> Result.map Tuple.first
|> Expect.equal (Ok Iddict.empty)
|> always
)
, test "JSON encode"
(Iddict.empty
|> Iddict.encode Json.value
|> E.encode 0
|> Expect.equal "{\"dict\":{}}"
|> always
)
, test "JSON decode"
("{\"dict\":{}}"
|> D.decodeString (Iddict.decoder Json.value)
|> Result.map Tuple.first
|> Expect.equal (Ok Iddict.empty)
|> always
)
]
singleton : Test
singleton =
let
singleFuzzer : Fuzzer (Iddict Int)
singleFuzzer =
Fuzz.map
(\i ->
Iddict.singleton i
|> Tuple.second
)
Fuzz.int
in
describe "singleton"
[ fuzz singleFuzzer
"not isEmpty"
(\single ->
single
|> Iddict.isEmpty
|> Expect.equal False
)
, fuzz Fuzz.int
"singleton == insert empty"
(\i ->
Iddict.empty
|> Iddict.insert i
|> Expect.equal (Iddict.singleton i)
)
, fuzz Fuzz.int
"First item is key 0"
(\i ->
Iddict.singleton i
|> Tuple.first
|> Expect.equal 0
)
, fuzz singleFuzzer
"Key 0 is member"
(\single ->
single
|> Iddict.member 0
|> Expect.equal True
)
, fuzz Fuzz.int
"Key 0 get returns Just value"
(\i ->
Iddict.singleton i
|> Tuple.second
|> Iddict.get 0
|> Expect.equal (Just i)
)
, fuzz singleFuzzer
"Size == 1"
(\single ->
single
|> Iddict.size
|> Expect.equal 1
)
, fuzz Fuzz.int
"Only key 0"
(\i ->
Iddict.singleton i
|> Tuple.second
|> Iddict.keys
|> Expect.equal [ 0 ]
)
, fuzz Fuzz.int
"Only value value"
(\i ->
Iddict.singleton i
|> Tuple.second
|> Iddict.values
|> Expect.equal [ i ]
)
, fuzz singleFuzzer
"JSON encode -> decode -> singleton"
(\single ->
single
|> Iddict.encode Json.int
|> D.decodeValue (Iddict.decoder Json.int)
|> Result.map Tuple.first
|> Expect.equal (Ok single)
)
, fuzz Fuzz.int
"JSON encode"
(\i ->
Iddict.singleton i
|> Tuple.second
|> Iddict.encode Json.int
|> E.encode 0
|> Expect.equal ("{\"cursor\":1,\"dict\":{\"0\":" ++ String.fromInt i ++ "}}")
)
, fuzz Fuzz.int
"JSON decode"
(\i ->
("{\"cursor\":1,\"dict\":{\"0\":" ++ String.fromInt i ++ "}}")
|> D.decodeString (Iddict.decoder Json.int)
|> Result.map Tuple.first
|> Tuple.pair 0
|> Expect.equal (Iddict.singleton i |> Tuple.mapSecond Ok)
)
]
insert : Test
insert =
describe "insert"
[ fuzz2 (fuzzer Fuzz.int)
Fuzz.int
"Add something"
(\d i ->
case Iddict.insert i d of
( key, dict ) ->
dict
|> Iddict.get key
|> Expect.equal (Just i)
)
, fuzz2 (fuzzer Fuzz.int)
Fuzz.int
"Never isEmpty"
(\d i ->
Iddict.insert i d
|> Tuple.second
|> Iddict.isEmpty
|> Expect.equal False
)
, fuzz2 (fuzzer Fuzz.int)
Fuzz.int
"New key"
(\d i ->
case Iddict.insert i d of
( key, dict ) ->
dict
|> Iddict.remove key
|> Iddict.insert i
|> (\( newKey, _ ) ->
Expect.notEqual key newKey
)
)
, fuzz2 (fuzzer Fuzz.int)
Fuzz.int
"New dict"
(\d i ->
case Iddict.insert i d of
( key, dict ) ->
dict
|> Iddict.remove key
|> Iddict.insert i
|> (\( _, newDict ) ->
Expect.notEqual dict newDict
)
)
, fuzz2 (fuzzer Fuzz.int)
Fuzz.int
"Inserted value is member"
(\d i ->
case Iddict.insert i d of
( key, dict ) ->
dict
|> Iddict.member key
|> Expect.equal True
)
, fuzz2 (fuzzer Fuzz.int)
Fuzz.int
"Get inserted value"
(\d i ->
case Iddict.insert i d of
( key, dict ) ->
dict
|> Iddict.get key
|> Expect.equal (Just i)
)
, fuzz2 (fuzzer Fuzz.int)
Fuzz.int
"size = size + 1"
(\d i ->
case Iddict.insert i d of
( _, dict ) ->
Expect.equal
(Iddict.size dict)
(Iddict.size d + 1)
)
]