From e3e765503f08388aff478ea59758d13f47ac2522 Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 9 May 2024 18:39:17 +0200 Subject: [PATCH] Add initial VaultUpdate design --- src/Internal/Config/Text.elm | 19 +++++++++ src/Internal/Tools/ParserExtra.elm | 39 ++++++++++++++++- src/Internal/Values/Envelope.elm | 37 ++++++++++++++++ src/Internal/Values/Room.elm | 30 +++++++++++++ src/Internal/Values/Vault.elm | 68 ++++++++++++++++++++++++++++-- src/Matrix.elm | 33 ++++++++++++++- src/Types.elm | 10 ++++- 7 files changed, 228 insertions(+), 8 deletions(-) diff --git a/src/Internal/Config/Text.elm b/src/Internal/Config/Text.elm index f5cd1c6..2affe44 100644 --- a/src/Internal/Config/Text.elm +++ b/src/Internal/Config/Text.elm @@ -126,6 +126,7 @@ docs : , timeline : TypeDocs , timelineFilter : TypeDocs , unsigned : TypeDocs + , vault : TypeDocs } docs = { context = @@ -216,6 +217,12 @@ docs = , "This information is often supportive but not necessary to the context." ] } + , vault = + { name = "Vault" + , description = + [ "Main type storing all relevant information from the Matrix API." + ] + } } @@ -309,6 +316,10 @@ fields : , redactedBecause : Desc , transactionId : Desc } + , vault : + { accountData : Desc + , rooms : Desc + } } fields = { context = @@ -483,6 +494,14 @@ fields = [ "The client-supplied transaction ID, for example, provided via PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}, if the client being given the event is the same one which sent it." ] } + , vault = + { accountData = + [ "The account's global private data." + ] + , rooms = + [ "Directory of joined rooms that the user is a member of." + ] + } } diff --git a/src/Internal/Tools/ParserExtra.elm b/src/Internal/Tools/ParserExtra.elm index 2c456c7..7e35d76 100644 --- a/src/Internal/Tools/ParserExtra.elm +++ b/src/Internal/Tools/ParserExtra.elm @@ -1,8 +1,21 @@ -module Internal.Tools.ParserExtra exposing (..) +module Internal.Tools.ParserExtra exposing (zeroOrMore, oneOrMore, exactly, atLeast, atMost, times, maxLength) + +{-| + + +# Extra parsers + +To help the Elm SDK with parsing complex text values, this modules offers a few functions. + +@docs zeroOrMore, oneOrMore, exactly, atLeast, atMost, times, maxLength + +-} import Parser as P exposing ((|.), (|=), Parser) +{-| Parses an item zero or more times. The result is combined into a list. +-} zeroOrMore : Parser a -> Parser (List a) zeroOrMore parser = P.loop [] @@ -15,6 +28,9 @@ zeroOrMore parser = ) +{-| Parses an item at least once, but up to any number of times. +The result is combined into a list. +-} oneOrMore : Parser a -> Parser (List a) oneOrMore parser = P.succeed (::) @@ -22,6 +38,9 @@ oneOrMore parser = |= zeroOrMore parser +{-| Parses an item at least a given number of times, but up to any number. +The result is combined into a list. +-} atLeast : Int -> Parser a -> Parser (List a) atLeast n parser = P.loop [] @@ -39,6 +58,10 @@ atLeast n parser = ) +{-| Parses an item any number of times (can be zero), but does not exceed a +given number of times. +The result is combined into a list. +-} atMost : Int -> Parser a -> Parser (List a) atMost n parser = P.loop [] @@ -55,6 +78,10 @@ atMost n parser = ) +{-| Parses an item a given number of times, ranging from the given minimum up +to the given maximum. +The result is combined into a list. +-} times : Int -> Int -> Parser a -> Parser (List a) times inf sup parser = let @@ -84,11 +111,21 @@ times inf sup parser = ) +{-| Repeat pasing an item an exact number of times. +The result is combined into a list. +-} exactly : Int -> Parser a -> Parser (List a) exactly n = times n n +{-| After having parsed the item, make sure that the parsed text has not +exceeded a given length. If so, the parser fails. + +This modification can be useful if a text has a maximum length requirement - +for example, usernames on Matrix cannot have a length of over 255 characters. + +-} maxLength : Int -> Parser a -> Parser a maxLength n parser = P.succeed diff --git a/src/Internal/Values/Envelope.elm b/src/Internal/Values/Envelope.elm index e8cb64e..e83452d 100644 --- a/src/Internal/Values/Envelope.elm +++ b/src/Internal/Values/Envelope.elm @@ -4,6 +4,7 @@ module Internal.Values.Envelope exposing , Settings, mapSettings, extractSettings , mapContext , getContent, extract + , EnvelopeUpdate(..), update , coder, encode, decoder ) @@ -36,6 +37,11 @@ settings that can be adjusted manually. @docs getContent, extract +## Update + +@docs EnvelopeUpdate, update + + ## JSON coders @docs coder, encode, decoder @@ -60,6 +66,16 @@ type alias Envelope a = } +{-| The Envelope update type helps update either the envelope or a content type. +-} +type EnvelopeUpdate a + = ContentUpdate a + | More (List (EnvelopeUpdate a)) + | SetAccessToken String + | SetRefreshToken String + | SetVersions (List String) + + {-| Settings value from [Internal.Values.Settings](Internal-Values-Settings#Settings). Can be used to manipulate the Matrix Vault. @@ -260,3 +276,24 @@ toMaybe data = Maybe.map (\content -> map (always content) data) data.content + + +{-| Updates the Envelope with a given EnvelopeUpdate value. +-} +update : (au -> a -> a) -> EnvelopeUpdate au -> Envelope a -> Envelope a +update updateContent eu ({ context } as data) = + case eu of + ContentUpdate v -> + { data | content = updateContent v data.content } + + More items -> + List.foldl (update updateContent) data items + + SetAccessToken a -> + { data | context = { context | accessToken = Just a } } + + SetRefreshToken r -> + { data | context = { context | refreshToken = Just r } } + + SetVersions vs -> + { data | context = { context | versions = Just vs } } diff --git a/src/Internal/Values/Room.elm b/src/Internal/Values/Room.elm index 86b933e..f2902bf 100644 --- a/src/Internal/Values/Room.elm +++ b/src/Internal/Values/Room.elm @@ -1,5 +1,6 @@ module Internal.Values.Room exposing ( Room, init + , RoomUpdate, update , Batch, addBatch, addSync, addEvents, mostRecentEvents , getAccountData, setAccountData , coder, encode, decode @@ -25,6 +26,11 @@ room state reflect the homeserver state of the room. @docs Room, init +## Update + +@docs RoomUpdate, update + + ## Timeline @docs Batch, addBatch, addSync, addEvents, mostRecentEvents @@ -71,6 +77,15 @@ type alias Room = } +{-| The RoomUpdate type explains how to update a room based on new information +from the Matrix API. +-} +type RoomUpdate + = AddSync Batch + | More (List RoomUpdate) + | SetAccountData String Json.Value + + {-| Add new events to the Room's event directory + Room's timeline. -} addEventsToTimeline : (Timeline.Batch -> Timeline -> Timeline) -> Batch -> Room -> Room @@ -223,3 +238,18 @@ mostRecentEvents room = setAccountData : String -> Json.Value -> Room -> Room setAccountData key value room = { room | accountData = Dict.insert key value room.accountData } + + +{-| Update the Room based on given instructions. +-} +update : RoomUpdate -> Room -> Room +update ru room = + case ru of + AddSync batch -> + addSync batch room + + More items -> + List.foldl update room items + + SetAccountData key value -> + setAccountData key value room diff --git a/src/Internal/Values/Vault.elm b/src/Internal/Values/Vault.elm index 2059aef..c3d534f 100644 --- a/src/Internal/Values/Vault.elm +++ b/src/Internal/Values/Vault.elm @@ -1,7 +1,8 @@ module Internal.Values.Vault exposing - ( fromRoomId, mapRoom, updateRoom + ( Vault + , VaultUpdate(..), update + , fromRoomId, mapRoom, updateRoom , getAccountData, setAccountData - , Vault ) {-| This module hosts the Vault module. The Vault is the data type storing all @@ -9,6 +10,13 @@ credentials, all user information and all other information that the user can receive from the Matrix API. +## Vault type + +@docs Vault + +To update the Vault, one uses VaultUpdate types. + +@docs VaultUpdate, update ## Rooms @@ -25,9 +33,10 @@ Rooms are environments where people can have a conversation with each other. -} import FastDict as Dict exposing (Dict) +import Internal.Config.Text as Text import Internal.Tools.Hashdict as Hashdict exposing (Hashdict) import Internal.Tools.Json as Json -import Internal.Values.Room exposing (Room) +import Internal.Values.Room as Room exposing (Room) {-| This is the Vault type. @@ -38,6 +47,39 @@ type alias Vault = } +{-| The VaultUpdate type is a type that instructs the Vault to update itself +based on new information provided by the Matrix API. +-} +type VaultUpdate + = CreateRoomIfNotExists String + | MapRoom String Room.RoomUpdate + | More (List VaultUpdate) + | SetAccountData String Json.Value + + +coder : Json.Coder Vault +coder = + Json.object2 + { name = Text.docs.vault.name + , description = Text.docs.vault.description + , init = Vault + } + (Json.field.required + { fieldName = "accountData" + , toField = .accountData + , description = Text.fields.vault.accountData + , coder = Json.fastDict Json.value + } + ) + (Json.field.required + { fieldName = "rooms" + , toField = .rooms + , description = Text.fields.vault.rooms + , coder = Hashdict.coder .roomId Room.coder + } + ) + + {-| Get a given room by its room id. -} fromRoomId : String -> Vault -> Maybe Room @@ -72,3 +114,23 @@ setAccountData key value vault = updateRoom : String -> (Maybe Room -> Maybe Room) -> Vault -> Vault updateRoom roomId f vault = { vault | rooms = Hashdict.update roomId f vault.rooms } + + +{-| Update the Vault using a VaultUpdate type. +-} +update : VaultUpdate -> Vault -> Vault +update vu vault = + case vu of + CreateRoomIfNotExists roomId -> + updateRoom roomId + (Maybe.withDefault (Room.init roomId) >> Maybe.Just) + vault + + MapRoom roomId ru -> + mapRoom roomId (Room.update ru) vault + + More items -> + List.foldl update vault items + + SetAccountData key value -> + setAccountData key value vault diff --git a/src/Matrix.elm b/src/Matrix.elm index 5cca1d6..bbd07a3 100644 --- a/src/Matrix.elm +++ b/src/Matrix.elm @@ -1,4 +1,7 @@ -module Matrix exposing (Vault) +module Matrix exposing + ( Vault + , VaultUpdate, update + ) {-| @@ -17,9 +20,16 @@ support a monolithic public registry. (: @docs Vault + +## Keeping the Vault up-to-date + +@docs VaultUpdate, update + -} -import Types +import Internal.Values.Envelope as Envelope +import Internal.Values.Vault as Internal +import Types exposing (Vault(..), VaultUpdate(..)) {-| The Vault type stores all relevant information about the Matrix API. @@ -30,3 +40,22 @@ the latest information about an account. -} type alias Vault = Types.Vault + + +{-| The VaultUpdate type is the central type that keeps the Vault up-to-date. +-} +type alias VaultUpdate = + Types.VaultUpdate + + +{-| Using new VaultUpdate information, update the Vault accordingly. + +This allows us to change our perception of the Matrix environment: has anyone +sent a new message? Did someone send us an invite for a new room? + +-} +update : VaultUpdate -> Vault -> Vault +update (VaultUpdate vu) (Vault vault) = + vault + |> Envelope.update Internal.update vu + |> Vault diff --git a/src/Types.elm b/src/Types.elm index c202354..b461611 100644 --- a/src/Types.elm +++ b/src/Types.elm @@ -1,4 +1,4 @@ -module Types exposing (Vault(..), Event(..), Room(..), User(..)) +module Types exposing (Vault(..), Event(..), Room(..), User(..), VaultUpdate(..)) {-| The Elm SDK uses a lot of records and values that are easy to manipulate. Yet, the [Elm design guidelines](https://package.elm-lang.org/help/design-guidelines#keep-tags-and-record-constructors-secret) @@ -12,7 +12,7 @@ access their content directly. The opaque types are placed in a central module so all exposed modules can safely access all exposed data types without risking to create circular imports. -@docs Vault, Event, Room, User +@docs Vault, Event, Room, User, VaultUpdate -} @@ -45,3 +45,9 @@ type User -} type Vault = Vault (Envelope.Envelope Vault.Vault) + + +{-| Opaque type for Matrix VaultUpdate +-} +type VaultUpdate + = VaultUpdate (Envelope.EnvelopeUpdate Vault.VaultUpdate)