Compare commits

...

7 Commits

Author SHA1 Message Date
Bram 1d3ceb9b2d Fix typos 2023-12-18 17:30:52 +01:00
Bram d9d5760928 Reorder Envelope functions alphabetically 2023-12-18 17:20:43 +01:00
Bram 5bd95699d2 Add function for Envelope 2023-12-18 17:19:12 +01:00
Bram 447a18ab04 Update Internal.Config.Text docs 2023-12-18 17:10:04 +01:00
Bram 87a5919921 Include Context in Envelope 2023-12-18 14:27:22 +01:00
Bram 7254fcfaa4 Add Context type 2023-12-18 14:26:57 +01:00
Bram b3479cf2c9 Add leaking values 2023-12-18 05:51:24 +01:00
6 changed files with 339 additions and 22 deletions

View File

@ -8,6 +8,7 @@
"Matrix",
"Matrix.Settings",
"Internal.Config.Default",
"Internal.Config.Leaks",
"Internal.Config.Text",
"Internal.Tools.Decode",
"Internal.Tools.Encode",
@ -15,6 +16,7 @@
"Internal.Tools.Iddict",
"Internal.Tools.Timestamp",
"Internal.Tools.VersionControl",
"Internal.Values.Context",
"Internal.Values.Envelope",
"Internal.Values.Vault",
"Types"

View File

@ -0,0 +1,60 @@
module Internal.Config.Leaks exposing (accessToken, baseUrl, transaction, versions)
{-|
# Leaks module
The Elm compiler is quite picky when it comes to handling edge cases, which may
occasionally result in requiring us to insert values in impossible states.
This module offers placeholders for those times. The placeholder values are
intentionally called "leaks", because they should be used carefully: a wrongful
implementation might cause unexpected behaviour, vulnerabilities or even
security risks!
You should not use this module unless you know what you're doing. That is:
- By exclusively using leaking values in opaque types so a user cannot
accidentally reach an impossible state
- By exclusively using leaking values in cases where the compiler is the only
reason that the leaking value needs to be used
- By exclusively using leaking values if there is no way to circumvent the
compiler with a reasonable method.
One such example would be to turn an `Maybe Int` into an `Int` if you already
know 100% sure that the value isn't `Nothing`.
Just 5 |> Maybe.withDefault Leaks.number
@docs accessToken, baseUrl, transaction, versions
-}
{-| Placeholder access token.
-}
accessToken : String
accessToken =
"elm-sdk-placeholder-access-token-leaks"
{-| Placeholder base URL.
-}
baseUrl : String
baseUrl =
"elm-sdk-placeholder-baseurl-leaks.example.org"
{-| Placeholder transaction id.
-}
transaction : String
transaction =
"elm-sdk-placeholder-transaction-leaks"
{-| Placeholder versions list.
-}
versions : List String
versions =
[ "elm-sdk-placeholder-versions-leaks" ]

View File

@ -7,12 +7,20 @@ module Internal.Config.Text exposing
{-| Throughout the Elm SDK, there are lots of pieces of text being used for
various purposes. Some of these are:
- To log on what is happening during an API call.
- To log what is happening during an API call.
- To fail with custom decoder errors.
- To describe custom values in a human readable format.
All magic values of text are gathered in this module, to form a monolithic
source of text. This allows people to learn more about the Elm SDK, and it
offers room for future translations.
Optionally, developers can even consider taking the values of some of these
variables to interpret them automatically when they appear as logs on the other
side. This could be used to automatically detect when the Vault is failing to
authenticate, for example, so that a new login screen can be shown. **WARNING:**
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.
## API Versions
@ -94,14 +102,14 @@ versionsFailedToDecode =
"Matrix API returned an invalid version list"
{-| Logs when the Vault remembers how to communicate with the Matrix homeserver
{-| Logs when the Vault remembers how to communicate with the Matrix homeserver.
-}
versionsFoundLocally : String
versionsFoundLocally =
"Found locally cached version list"
{-| Logs when the Matrix API has returned how to best communicate with them
{-| Logs when the Matrix API has returned how to best communicate with them.
-}
versionsReceived : String
versionsReceived =

View File

@ -3,11 +3,11 @@ module Internal.Tools.Timestamp exposing
, encode, decoder
)
{-| The Timestamp module is a simplification of the Timetsamp as delivered by
{-| The Timestamp module is a simplification of the Timestamp as delivered by
elm/time. This module offers ways to work with the timestamp in meaningful ways.
## Timetstamp
## Timestamp
@docs Timestamp
@ -23,7 +23,7 @@ import Json.Encode as E
import Time
{-| The Timetstamp data type representing a moment in time.
{-| The Timestamp data type representing a moment in time.
-}
type alias Timestamp =
Time.Posix

View File

@ -0,0 +1,196 @@
module Internal.Values.Context exposing
( Context, init, encode, decoder
, APIContext, apiFormat
, setAccessToken, getAccessToken
, setBaseUrl, getBaseUrl
, setTransaction, getTransaction
, setVersions, getVersions
)
{-| The Context is the set of variables that the user (mostly) cannot control.
The Context contains tokens, values and other bits that the Vault receives from
the Matrix API.
## Context
@docs Context, init, encode, decoder
## APIContext
Once the API starts needing information, that's when we use the APIContext type
to build the right environment for the API communication to work with.
@docs APIContext, apiFormat
Once the APIContext is ready, there's helper functions for each piece of
information that can be inserted.
### Access token
@docs setAccessToken, getAccessToken
### Base URL
@docs setBaseUrl, getBaseUrl
### Transaction id
@docs setTransaction, getTransaction
### Versions
@docs setVersions, getVersions
-}
import Internal.Config.Leaks as L
import Internal.Tools.Decode as D
import Internal.Tools.Encode as E
import Json.Decode as D
import Json.Encode as E
{-| The Context type stores all the information in the Vault. This data type is
static and hence can be passed on easily.
-}
type alias Context =
{ accessToken : Maybe String
, baseUrl : Maybe String
, password : Maybe String
, refreshToken : Maybe String
, username : Maybe String
, transaction : Maybe String
, versions : Maybe (List String)
}
{-| The APIContext is a separate type that uses a phantom type to trick the
compiler into requiring values to be present. This data type is used to gather
the right variables (like an access token) before accessing the Matrix API.
-}
type APIContext ph
= APIContext
{ accessToken : String
, baseUrl : String
, context : Context
, transaction : String
, versions : List String
}
{-| Create an unformatted APIContext type.
-}
apiFormat : Context -> APIContext {}
apiFormat context =
APIContext
{ accessToken = context.accessToken |> Maybe.withDefault L.accessToken
, baseUrl = context.baseUrl |> Maybe.withDefault L.baseUrl
, context = context
, transaction = context.transaction |> Maybe.withDefault L.transaction
, versions = context.versions |> Maybe.withDefault L.versions
}
{-| Decode a Context type from a JSON value.
-}
decoder : D.Decoder Context
decoder =
D.map7 Context
(D.opField "accessToken" D.string)
(D.opField "baseUrl" D.string)
(D.opField "password" D.string)
(D.opField "refreshToken" D.string)
(D.opField "username" D.string)
(D.opField "transaction" D.string)
(D.opField "versions" (D.list D.string))
{-| Encode a Context type into a JSON value.
-}
encode : Context -> E.Value
encode context =
E.maybeObject
[ ( "accessToken", Maybe.map E.string context.accessToken )
, ( "baseUrl", Maybe.map E.string context.baseUrl )
, ( "password", Maybe.map E.string context.password )
, ( "refreshToken", Maybe.map E.string context.refreshToken )
, ( "username", Maybe.map E.string context.username )
, ( "transaction", Maybe.map E.string context.transaction )
, ( "versions", Maybe.map (E.list E.string) context.versions )
]
{-| A basic, untouched version of the Context, containing no information.
-}
init : Context
init =
{ accessToken = Nothing
, baseUrl = Nothing
, refreshToken = Nothing
, password = Nothing
, username = Nothing
, transaction = Nothing
, versions = Nothing
}
{-| Get an inserted access token.
-}
getAccessToken : APIContext { a | accessToken : () } -> String
getAccessToken (APIContext c) =
c.accessToken
{-| Insert an access token into the APIContext.
-}
setAccessToken : String -> APIContext a -> APIContext { a | accessToken : () }
setAccessToken value (APIContext c) =
APIContext { c | accessToken = value }
{-| Get an inserted base URL.
-}
getBaseUrl : APIContext { a | baseUrl : () } -> String
getBaseUrl (APIContext c) =
c.baseUrl
{-| Insert a base URL into the APIContext.
-}
setBaseUrl : String -> APIContext a -> APIContext { a | baseUrl : () }
setBaseUrl value (APIContext c) =
APIContext { c | baseUrl = value }
{-| Get an inserted transaction id.
-}
getTransaction : APIContext { a | transaction : () } -> String
getTransaction (APIContext c) =
c.transaction
{-| Insert a transaction id into the APIContext.
-}
setTransaction : String -> APIContext a -> APIContext { a | transaction : () }
setTransaction value (APIContext c) =
APIContext { c | transaction = value }
{-| Get an inserted versions list.
-}
getVersions : APIContext { a | versions : () } -> List String
getVersions (APIContext c) =
c.versions
{-| Insert a versions list into the APIContext.
-}
setVersions : List String -> APIContext a -> APIContext { a | versions : () }
setVersions value (APIContext c) =
APIContext { c | versions = value }

View File

@ -1,7 +1,8 @@
module Internal.Values.Envelope exposing
( Envelope, init
, map, mapMaybe
, map, mapMaybe, mapList
, Settings, mapSettings, extractSettings
, mapContext
, getContent, extract
, encode, decoder
)
@ -17,7 +18,7 @@ settings that can be adjusted manually.
## Manipulate
@docs map, mapMaybe
@docs map, mapMaybe, mapList
## Settings
@ -25,6 +26,11 @@ settings that can be adjusted manually.
@docs Settings, mapSettings, extractSettings
## Context
@docs mapContext
## Extract
@docs getContent, extract
@ -39,6 +45,7 @@ settings that can be adjusted manually.
import Internal.Config.Default as Default
import Internal.Tools.Decode as D
import Internal.Tools.Encode as E
import Internal.Values.Context as Context exposing (Context)
import Json.Decode as D
import Json.Encode as E
@ -51,6 +58,7 @@ define them in their type.
type Envelope a
= Envelope
{ content : a
, context : Context
, settings : Settings
}
@ -74,8 +82,9 @@ potential tokens, values and settings included in the JSON.
-}
decoder : D.Decoder a -> D.Decoder (Envelope a)
decoder xDecoder =
D.map2 (\a b -> Envelope { content = a, settings = b })
D.map3 (\a b c -> Envelope { content = a, context = b, settings = c })
(D.field "content" xDecoder)
(D.field "context" Context.decoder)
(D.field "settings" decoderSettings)
@ -96,6 +105,7 @@ encode : (a -> E.Value) -> Envelope a -> E.Value
encode encodeX (Envelope data) =
E.object
[ ( "content", encodeX data.content )
, ( "context", Context.encode data.context )
, ( "settings", encodeSettings data.settings )
, ( "version", E.string Default.currentVersion )
]
@ -178,6 +188,7 @@ init : a -> Envelope a
init x =
Envelope
{ content = x
, context = Context.init
, settings =
{ currentVersion = Default.currentVersion
, deviceName = Default.deviceName
@ -200,29 +211,42 @@ map : (a -> b) -> Envelope a -> Envelope b
map f (Envelope data) =
Envelope
{ content = f data.content
, context = data.context
, settings = data.settings
}
{-| Update the settings in the Envelope.
setDeviceName : String -> Envelope a -> Envelope a
setDeviceName name envelope =
mapSettings
(\settings ->
{ settings | deviceName = name }
)
envelope
{-| Update the Context in the Envelope.
-}
mapSettings : (Settings -> Settings) -> Envelope a -> Envelope a
mapSettings f (Envelope data) =
mapContext : (Context -> Context) -> Envelope a -> Envelope a
mapContext f (Envelope data) =
Envelope
{ content = data.content
, settings = f data.settings
, context = f data.context
, settings = data.settings
}
{-| Map the contents of a function, where the result is wrapped in a `List`
type. This can be useful when you are mapping to a list of individual values
that you would all like to see enveloped.
type alias User =
{ name : String, age : Int }
type alias Company =
{ name : String, employees : List User }
getEmployees : Envelope Company -> List (Envelope User)
getEmployees envelope =
mapList .employees envelope
-}
mapList : (a -> List b) -> Envelope a -> List (Envelope b)
mapList f =
map f >> toList
{-| Map the contents of a function, where the result is wrapped in a `Maybe`
type. This can be useful when you are not guaranteed to find the value you're
looking for.
@ -243,6 +267,33 @@ mapMaybe f =
map f >> toMaybe
{-| Update the settings in the Envelope.
setDeviceName : String -> Envelope a -> Envelope a
setDeviceName name envelope =
mapSettings
(\settings ->
{ settings | deviceName = name }
)
envelope
-}
mapSettings : (Settings -> Settings) -> Envelope a -> Envelope a
mapSettings f (Envelope data) =
Envelope
{ content = data.content
, context = data.context
, settings = f data.settings
}
toList : Envelope (List a) -> List (Envelope a)
toList (Envelope data) =
List.map
(\content -> map (always content) (Envelope data))
data.content
toMaybe : Envelope (Maybe a) -> Maybe (Envelope a)
toMaybe (Envelope data) =
Maybe.map