elm-matrix-sdk-beta/src/Internal/Values/Envelope.elm

263 lines
6.1 KiB
Elm

module Internal.Values.Envelope exposing
( Envelope, init
, map, mapMaybe, mapList
, Settings, mapSettings, extractSettings
, mapContext
, getContent, extract
, coder, encode, decoder
)
{-| The Envelope module wraps existing data types with lots of values and
settings that can be adjusted manually.
## Create
@docs Envelope, init
## Manipulate
@docs map, mapMaybe, mapList
## Settings
@docs Settings, mapSettings, extractSettings
## Context
@docs mapContext
## Extract
@docs getContent, extract
## JSON coders
@docs coder, encode, decoder
-}
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
import Internal.Values.Context as Context exposing (Context)
import Internal.Values.Settings as Settings
{-| There are lots of different data types in the Elm SDK, and many of them
need the same values. The Envelope type wraps settings, tokens and values around
each data type so they can all enjoy those values without needing to explicitly
define them in their type.
-}
type alias Envelope a =
{ content : a
, context : Context
, settings : Settings
}
{-| Settings value from
[Internal.Values.Settings](Internal-Values-Settings#Settings). Can be used to
manipulate the Matrix Vault.
-}
type alias Settings =
Settings.Settings
{-| Define how an Envelope can be encoded to and decoded from a JSON object.
-}
coder : Json.Coder a -> Json.Coder (Envelope a)
coder c1 =
Json.object3
{ name = Text.docs.envelope.name
, description = Text.docs.envelope.description
, init = Envelope
}
(Json.field.required
{ fieldName = "content"
, toField = .content
, description = Text.fields.envelope.content
, coder = c1
}
)
(Json.field.required
{ fieldName = "context"
, toField = .context
, description = Text.fields.envelope.context
, coder = Context.coder
}
)
(Json.field.optional.withDefault
{ fieldName = "settings"
, toField = .settings
, description = Text.fields.envelope.settings
, coder = Settings.coder
, default = Tuple.pair Settings.init []
, defaultToString = always "<Default settings>"
}
)
{-| Decode an enveloped type from a JSON value. The decoder also imports any
potential tokens, values and settings included in the JSON.
-}
decoder : Json.Coder a -> Json.Decoder (Envelope a)
decoder c1 =
Json.decode (coder c1)
{-| Encode an enveloped type into a JSON value. The function encodes all
non-standard settings, tokens and values.
-}
encode : Json.Coder a -> Json.Encoder (Envelope a)
encode c1 =
Json.encode (coder c1)
{-| Map a function, then get its content. This is useful for getting information
from a data type inside an Envelope.
type alias User =
{ name : String, age : Int }
getName : Envelope User -> String
getName =
Envelope.extract .name
-}
extract : (a -> b) -> Envelope a -> b
extract f data =
f data.content
{-| Map a function on the settings, effectively getting data that way.
This can be helpful if you have a UI that displays custom settings to a user.
-}
extractSettings : (Settings -> b) -> Envelope a -> b
extractSettings f data =
f data.settings
{-| Get the original item that is stored inside an Envelope.
Make sure that you're only using this if you're interested in the actual value!
If you'd like to get the content, run a function on it, and put it back in an
Envelope, consider using [map](#map) instead.
-}
getContent : Envelope a -> a
getContent =
extract identity
{-| Create a new enveloped data type. All settings are set to default values
from the [Internal.Config.Default](Internal-Config-Default) module.
-}
init : a -> Envelope a
init x =
{ content = x
, context = Context.init
, settings = Settings.init
}
{-| Map a function on the content of the Envelope.
type alias User =
{ name : String, age : Int }
getName : Envelope User -> Envelope String
getName =
Envelope.map .name
-}
map : (a -> b) -> Envelope a -> Envelope b
map f data =
{ content = f data.content
, context = data.context
, settings = data.settings
}
{-| Update the Context in the Envelope.
-}
mapContext : (Context -> Context) -> Envelope a -> Envelope a
mapContext f data =
{ data | context = f data.context }
{-| 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.
type alias User =
{ name : String, age : Int }
type alias UserDatabase =
List User
getFirstUser : Envelope UserDatabase -> Maybe (Envelope User)
getFirstUser envelope =
mapMaybe List.head envelope
-}
mapMaybe : (a -> Maybe b) -> Envelope a -> Maybe (Envelope b)
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 data =
{ data | settings = f data.settings }
toList : Envelope (List a) -> List (Envelope a)
toList data =
List.map
(\content -> map (always content) data)
data.content
toMaybe : Envelope (Maybe a) -> Maybe (Envelope a)
toMaybe data =
Maybe.map
(\content -> map (always content) data)
data.content