commit
						0bca889d41
					
				|  | @ -2,3 +2,5 @@ | |||
| elm-stuff | ||||
| # elm-repl generated files | ||||
| repl-temp-* | ||||
| # VScode settings | ||||
| .vscode/ | ||||
|  |  | |||
|  | @ -32,3 +32,8 @@ elm install noordstar/elm-matrix-sdk-beta | |||
| Keep in mind that the beta versions are intended to develop rapidly. You should | ||||
| not expect the versions to remain reliable for years! If you need a stable | ||||
| version, please wait around for a full version. | ||||
| 
 | ||||
| ## Contribute | ||||
| 
 | ||||
| If you wish to contribute, please read the | ||||
| [contribution guide](docs/CONTRIBUTING.md). | ||||
|  |  | |||
|  | @ -0,0 +1,71 @@ | |||
| # Contributing to elm-matrix-sdk-beta | ||||
| 
 | ||||
| Welcome to the elm-matrix-sdk-beta repository! We appreciate your interest in | ||||
| contributing. Please take a moment to review the following guidelines. | ||||
| 
 | ||||
| ## Table of Contents | ||||
| 
 | ||||
| 1. [How to Contribute](#how-to-contribute) | ||||
| 2. [Bug Reports](#bug-reports) | ||||
| 3. [Code Contributions](#code-contributions) | ||||
| 4. [Documentation Improvements](#documentation-improvements) | ||||
| 5. [Feedback and Tips](#feedback-and-tips) | ||||
| 6. [Development Environment](#development-environment) | ||||
| 7. [Pull Requests](#pull-requests) | ||||
| 8. [Communication](#communication) | ||||
| 9. [License](#license) | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| ## How to Contribute | ||||
| 
 | ||||
| We welcome various forms of contributions, including bug reports, code | ||||
| contributions through pull requests from forks, suggestions for documentation | ||||
| improvement, and helpful tips and feedback based on user experience. | ||||
| 
 | ||||
| ## Bug Reports | ||||
| 
 | ||||
| When reporting bugs, please provide as much detail as possible, including steps | ||||
| to reproduce, expected behavior, actual behavior, and details about your | ||||
| environment. | ||||
| 
 | ||||
| ## Code Contributions | ||||
| 
 | ||||
| 1. Fork the repository. | ||||
| 2. Create a new branch from the `develop` branch. | ||||
| 3. Write your code and commit changes. | ||||
| 4. Push your branch to your fork. | ||||
| 5. Submit a pull request to the `develop` branch. | ||||
| 
 | ||||
| ## Documentation Improvements | ||||
| 
 | ||||
| Feel free to suggest improvements to the documentation. Ensure that your | ||||
| suggestions are clear and concise. | ||||
| 
 | ||||
| ## Feedback and Tips | ||||
| 
 | ||||
| We appreciate feedback, tips, and suggestions based on user experience. Share | ||||
| your thoughts to help us enhance the project. | ||||
| 
 | ||||
| ## Development Environment | ||||
| 
 | ||||
| To set up your development environment: | ||||
| 
 | ||||
| 1. Install Elm. | ||||
| 2. Use `elm-format` to format your Elm code. | ||||
| 3. Run `elm make --docs=docs.json` to generate documentation. | ||||
| 4. View documentation using an Elm documentation viewer (e.g., [elm-doc-preview](https://elm-doc-preview.netlify.app/)). | ||||
| 5. Expose modules in `elm.json` for documentation. | ||||
| 
 | ||||
| ## Pull Requests | ||||
| 
 | ||||
| Create a fork, write your code, and submit a pull request to the `develop` branch. | ||||
| 
 | ||||
| ## Communication | ||||
| 
 | ||||
| - Mastodon: [@elm_matrix_sdk@social.noordstar.me](https://social.noordstar.me/@elm_matrix_sdk) | ||||
| - Matrix: [#elm-sdk:matrix.org](https://matrix.to/#/#elm-sdk:matrix.org) | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| This project is licensed under the [EUPL-v1.2](LICENSE). Please review the license file for more details. | ||||
|  | @ -0,0 +1,29 @@ | |||
| # Before merging to main | ||||
| 
 | ||||
| ⚠️ **Hold up!** Before you merge that pull request, make sure to follow this checklist! | ||||
| 
 | ||||
| ## Any branch to `develop` | ||||
| 
 | ||||
| If you wish to merge your branch to the `develop` branch, make sure to follow this checklist: | ||||
| 
 | ||||
| - [ ] Run `elm-format` to ensure the correct formatting of the Elm files. | ||||
| - [ ] Use `elm-doc-preview` to verify whether the documentation is up to standards. | ||||
| 
 | ||||
| ## The `develop` branch to `main` | ||||
| 
 | ||||
| The `develop` branch is the only branch that's allowed to merge to `main`. Once the branch merges to `main`, that indicates a new release on the Elm registry. | ||||
| 
 | ||||
| Before that is being done, however, the following tasks should be done: | ||||
| 
 | ||||
| - [ ] Run `elm-format` to ensure the correct formatting of the Elm files. | ||||
| - [ ] Use `elm-doc-preview` to verify whether the documentation is up to standards. | ||||
| - [ ] Remove exposed modules from `elm.json` that do not need to be exposed modules in the release. | ||||
| - [ ] Run `elm bump` to update the library's version number | ||||
| - [ ] Update the version name in the [default values config file](../src/Internal/Config/Default.elm). | ||||
| 
 | ||||
| ## Any branch to any other branch | ||||
| 
 | ||||
| There are no limitations to merging other branches towards one another, although it is important to keep in mind that: | ||||
| 
 | ||||
| - Contributors are advised to merge the `develop` branch into their branches regularly to avoid any merge conflicts. | ||||
| - Merging with branches that haven't been accepted (yet) might result in your branch ending up with code that will not be accepted. | ||||
							
								
								
									
										16
									
								
								elm.json
								
								
								
								
							
							
						
						
									
										16
									
								
								elm.json
								
								
								
								
							|  | @ -3,11 +3,19 @@ | |||
|     "name": "noordstar/elm-matrix-sdk-beta", | ||||
|     "summary": "Matrix SDK for instant communication. Unstable beta version for testing only.", | ||||
|     "license": "EUPL-1.1", | ||||
|     "version": "1.0.0", | ||||
|     "version": "2.0.0", | ||||
|     "exposed-modules": [ | ||||
|         "Matrix", | ||||
|         "Matrix.Settings" | ||||
|     ], | ||||
|     "elm-version": "0.19.0 <= v < 0.20.0", | ||||
|     "exposed-modules": [ "Matrix" ], | ||||
|     "dependencies": { | ||||
|         "elm/core": "1.0.0 <= v < 2.0.0" | ||||
|         "elm/core": "1.0.0 <= v < 2.0.0", | ||||
|         "elm/json": "1.0.0 <= v < 2.0.0", | ||||
|         "elm/time": "1.0.0 <= v < 2.0.0", | ||||
|         "miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0" | ||||
|     }, | ||||
|     "test-dependencies": {} | ||||
|     "test-dependencies": { | ||||
|         "elm-explorations/test": "2.1.2 <= v < 3.0.0" | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,54 @@ | |||
| module Internal.Config.Default exposing | ||||
|     ( currentVersion, deviceName | ||||
|     , syncTime | ||||
|     ) | ||||
| 
 | ||||
| {-| This module hosts all default settings and configurations that the Vault | ||||
| will assume until overriden by the user. | ||||
| 
 | ||||
| 
 | ||||
| ## Version management | ||||
| 
 | ||||
| @docs currentVersion, deviceName | ||||
| 
 | ||||
| 
 | ||||
| ## Communication config | ||||
| 
 | ||||
| @docs syncTime | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| 
 | ||||
| {-| The version that is being communicated to the user | ||||
| -} | ||||
| currentVersion : String | ||||
| currentVersion = | ||||
|     "beta 2.0.0" | ||||
| 
 | ||||
| 
 | ||||
| {-| The default device name that is being communicated with the Matrix API. | ||||
| 
 | ||||
| This is mostly useful for users who are logged in with multiple sessions. | ||||
| 
 | ||||
| -} | ||||
| deviceName : String | ||||
| deviceName = | ||||
|     "Elm SDK (" ++ currentVersion ++ ")" | ||||
| 
 | ||||
| 
 | ||||
| {-| Whenever the Matrix API has nothing new to report, the Elm SDK is kept on | ||||
| hold until something new happens. The `syncTime` indicates a timeout to how long | ||||
| the Elm SDK tolerates being held on hold. | ||||
| 
 | ||||
|   - ↗️ A high value is good because it significantly reduces traffic between the | ||||
|     user and the homeserver. | ||||
|   - ↘️ A low value is good because it reduces the risk of | ||||
|     the connection ending abruptly or unexpectedly. | ||||
| 
 | ||||
| Nowadays, most libraries use 30 seconds as the standard, as does the Elm SDK. | ||||
| The value is in miliseconds, so it is set at 30,000. | ||||
| 
 | ||||
| -} | ||||
| syncTime : Int | ||||
| syncTime = | ||||
|     30 * 1000 | ||||
|  | @ -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" ] | ||||
|  | @ -0,0 +1,116 @@ | |||
| module Internal.Config.Text exposing | ||||
|     ( versionsFoundLocally, versionsReceived, versionsFailedToDecode | ||||
|     , accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid | ||||
|     , unsupportedVersionForEndpoint | ||||
|     ) | ||||
| 
 | ||||
| {-| Throughout the Elm SDK, there are lots of pieces of text being used for | ||||
| various purposes. Some of these are: | ||||
| 
 | ||||
|   - 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 | ||||
| 
 | ||||
| Messages sent as API logs while the Elm SDK is figuring out how modern the | ||||
| homeserver is and how it can best communicate. | ||||
| 
 | ||||
| @docs versionsFoundLocally, versionsReceived, versionsFailedToDecode | ||||
| 
 | ||||
| 
 | ||||
| ## API Authentication | ||||
| 
 | ||||
| Messages sent as API logs during the authentication phase of the API | ||||
| interaction. | ||||
| 
 | ||||
| @docs accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid | ||||
| 
 | ||||
| offers room for translation, re-wording and refactors. | ||||
| 
 | ||||
| 
 | ||||
| ## API miscellaneous messages | ||||
| 
 | ||||
| Messages sent as API logs during communication with the API. | ||||
| 
 | ||||
| @docs unsupportedVersionForEndpoint | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| 
 | ||||
| {-| Logs when the Matrix API returns that an access token is no longer valid. | ||||
| -} | ||||
| accessTokenExpired : String | ||||
| accessTokenExpired = | ||||
|     "Matrix API reports access token as no longer valid" | ||||
| 
 | ||||
| 
 | ||||
| {-| Logs when the Vault has an access token that is still (locally) considered | ||||
| valid. | ||||
| -} | ||||
| accessTokenFoundLocally : String | ||||
| accessTokenFoundLocally = | ||||
|     "Found locally cached access token" | ||||
| 
 | ||||
| 
 | ||||
| {-| Logs when the Matrix API rejects an access token without explicitly | ||||
| mentioning a reason. | ||||
| -} | ||||
| accessTokenInvalid : String | ||||
| accessTokenInvalid = | ||||
|     "Matrix API rejected access token as invalid" | ||||
| 
 | ||||
| 
 | ||||
| {-| The Matrix homeserver can specify how it wishes to communicate, and the Elm | ||||
| SDK aims to communicate accordingly. This may fail in some scenarios, however, | ||||
| in which case it will throw this error. | ||||
| 
 | ||||
| Most of the time, the error is caused by one of two options: | ||||
| 
 | ||||
| 1.  The homeserver is very archaic and does not (yet) support API endpoints that | ||||
|     are nowadays considered mature. | ||||
| 
 | ||||
| 2.  The homeserver is much more modern than the Elm SDK and either uses | ||||
|     exclusively API endpoints that the Elm SDK doesn't (yet) support, or it uses | ||||
|     spec versions that aren't considered "official" Matrix spec versions and | ||||
|     were designed by a third party. | ||||
| 
 | ||||
| -} | ||||
| unsupportedVersionForEndpoint : String | ||||
| unsupportedVersionForEndpoint = | ||||
|     "This Matrix homeserver and the Elm SDK do not share a common spec version for this endpoint" | ||||
| 
 | ||||
| 
 | ||||
| {-| Occasionally, the Matrix homeserver fails to communicate how it is best | ||||
| communicated with. Most of the time, this means that the homeserver is somehow | ||||
| unreachable or some gateway error has occured. | ||||
| -} | ||||
| versionsFailedToDecode : String | ||||
| versionsFailedToDecode = | ||||
|     "Matrix API returned an invalid version list" | ||||
| 
 | ||||
| 
 | ||||
| {-| 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. | ||||
| -} | ||||
| versionsReceived : String | ||||
| versionsReceived = | ||||
|     "Matrix API returned a version list" | ||||
|  | @ -0,0 +1,155 @@ | |||
| module Internal.Tools.Decode exposing | ||||
|     ( opField, opFieldWithDefault | ||||
|     , map9, map10, map11 | ||||
|     ) | ||||
| 
 | ||||
| {-| | ||||
| 
 | ||||
| 
 | ||||
| # Decode module | ||||
| 
 | ||||
| This module contains helper functions that help decode JSON. | ||||
| 
 | ||||
| 
 | ||||
| ## Optional field decoders | ||||
| 
 | ||||
| @docs opField, opFieldWithDefault | ||||
| 
 | ||||
| 
 | ||||
| ## Extended map functions | ||||
| 
 | ||||
| @docs map9, map10, map11 | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Json.Decode as D | ||||
| 
 | ||||
| 
 | ||||
| {-| Add an optional field decoder. If the field exists, the decoder will fail | ||||
| if the field doesn't decode properly. | ||||
| 
 | ||||
| This decoder standard out from `D.maybe <| D.field fieldName decoder` because | ||||
| that will decode into a `Nothing` if the `decoder` fails. This function will | ||||
| only decode into a `Nothing` if the field doesn't exist, and will fail if | ||||
| `decoder` fails. | ||||
| 
 | ||||
| The function also returns Nothing if the field exists but it is null. | ||||
| 
 | ||||
| -} | ||||
| opField : String -> D.Decoder a -> D.Decoder (Maybe a) | ||||
| opField fieldName decoder = | ||||
|     D.value | ||||
|         |> D.field fieldName | ||||
|         |> D.maybe | ||||
|         |> D.andThen | ||||
|             (\v -> | ||||
|                 case v of | ||||
|                     Just _ -> | ||||
|                         D.oneOf | ||||
|                             [ D.null Nothing | ||||
|                             , D.map Just decoder | ||||
|                             ] | ||||
|                             |> D.field fieldName | ||||
| 
 | ||||
|                     Nothing -> | ||||
|                         D.succeed Nothing | ||||
|             ) | ||||
| 
 | ||||
| 
 | ||||
| {-| Add an optional field decoder. If the field is not given, the decoder will | ||||
| return a default value. If the field exists, the decoder will fail if the field | ||||
| doesn't decode properly. | ||||
| -} | ||||
| opFieldWithDefault : String -> a -> D.Decoder a -> D.Decoder a | ||||
| opFieldWithDefault fieldName default decoder = | ||||
|     opField fieldName decoder |> D.map (Maybe.withDefault default) | ||||
| 
 | ||||
| 
 | ||||
| {-| Try 9 decoders and combine the result. | ||||
| -} | ||||
| map9 : | ||||
|     (a -> b -> c -> d -> e -> f -> g -> h -> i -> value) | ||||
|     -> D.Decoder a | ||||
|     -> D.Decoder b | ||||
|     -> D.Decoder c | ||||
|     -> D.Decoder d | ||||
|     -> D.Decoder e | ||||
|     -> D.Decoder f | ||||
|     -> D.Decoder g | ||||
|     -> D.Decoder h | ||||
|     -> D.Decoder i | ||||
|     -> D.Decoder value | ||||
| map9 func da db dc dd de df dg dh di = | ||||
|     D.map8 | ||||
|         (\a b c d e f g ( h, i ) -> | ||||
|             func a b c d e f g h i | ||||
|         ) | ||||
|         da | ||||
|         db | ||||
|         dc | ||||
|         dd | ||||
|         de | ||||
|         df | ||||
|         dg | ||||
|         (D.map2 Tuple.pair dh di) | ||||
| 
 | ||||
| 
 | ||||
| {-| Try 10 decoders and combine the result. | ||||
| -} | ||||
| map10 : | ||||
|     (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> value) | ||||
|     -> D.Decoder a | ||||
|     -> D.Decoder b | ||||
|     -> D.Decoder c | ||||
|     -> D.Decoder d | ||||
|     -> D.Decoder e | ||||
|     -> D.Decoder f | ||||
|     -> D.Decoder g | ||||
|     -> D.Decoder h | ||||
|     -> D.Decoder i | ||||
|     -> D.Decoder j | ||||
|     -> D.Decoder value | ||||
| map10 func da db dc dd de df dg dh di dj = | ||||
|     D.map8 | ||||
|         (\a b c d e f ( g, h ) ( i, j ) -> | ||||
|             func a b c d e f g h i j | ||||
|         ) | ||||
|         da | ||||
|         db | ||||
|         dc | ||||
|         dd | ||||
|         de | ||||
|         df | ||||
|         (D.map2 Tuple.pair dg dh) | ||||
|         (D.map2 Tuple.pair di dj) | ||||
| 
 | ||||
| 
 | ||||
| {-| Try 11 decoders and combine the result. | ||||
| -} | ||||
| map11 : | ||||
|     (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> value) | ||||
|     -> D.Decoder a | ||||
|     -> D.Decoder b | ||||
|     -> D.Decoder c | ||||
|     -> D.Decoder d | ||||
|     -> D.Decoder e | ||||
|     -> D.Decoder f | ||||
|     -> D.Decoder g | ||||
|     -> D.Decoder h | ||||
|     -> D.Decoder i | ||||
|     -> D.Decoder j | ||||
|     -> D.Decoder k | ||||
|     -> D.Decoder value | ||||
| map11 func da db dc dd de df dg dh di dj dk = | ||||
|     D.map8 | ||||
|         (\a b c d e ( f, g ) ( h, i ) ( j, k ) -> | ||||
|             func a b c d e f g h i j k | ||||
|         ) | ||||
|         da | ||||
|         db | ||||
|         dc | ||||
|         dd | ||||
|         de | ||||
|         (D.map2 Tuple.pair df dg) | ||||
|         (D.map2 Tuple.pair dh di) | ||||
|         (D.map2 Tuple.pair dj dk) | ||||
|  | @ -0,0 +1,52 @@ | |||
| module Internal.Tools.Encode exposing (maybeObject) | ||||
| 
 | ||||
| {-| | ||||
| 
 | ||||
| 
 | ||||
| # Encode module | ||||
| 
 | ||||
| This module contains helper functions that help decode JSON. | ||||
| 
 | ||||
| 
 | ||||
| # Optional body object | ||||
| 
 | ||||
| @docs maybeObject | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Json.Encode as E | ||||
| 
 | ||||
| 
 | ||||
| {-| Create a body object based on optionally provided values. | ||||
| 
 | ||||
| In other words, the following two variables create the same JSON value: | ||||
| 
 | ||||
|     value1 : Json.Encode.Value | ||||
|     value1 = | ||||
|         maybeObject | ||||
|             [ ( "name", Just (Json.Encode.string "Alice") ) | ||||
|             , ( "age", Nothing ) | ||||
|             , ( "height", Just (Json.Encode.float 1.61) ) | ||||
|             , ( "weight", Nothing ) | ||||
|             ] | ||||
| 
 | ||||
|     value2 : Json.Encode.Value | ||||
|     value2 = | ||||
|         Json.Encode.object | ||||
|             [ ( "name", Json.Encode.string "Alice" ) | ||||
|             , ( "height", Json.Encode.float 1.61 ) | ||||
|             ] | ||||
| 
 | ||||
| -} | ||||
| maybeObject : List ( String, Maybe E.Value ) -> E.Value | ||||
| maybeObject = | ||||
|     List.filterMap | ||||
|         (\( name, value ) -> | ||||
|             case value of | ||||
|                 Just v -> | ||||
|                     Just ( name, v ) | ||||
| 
 | ||||
|                 _ -> | ||||
|                     Nothing | ||||
|         ) | ||||
|         >> E.object | ||||
|  | @ -0,0 +1,266 @@ | |||
| module Internal.Tools.Hashdict exposing | ||||
|     ( Hashdict | ||||
|     , empty, singleton, insert, remove, removeKey | ||||
|     , isEmpty, member, memberKey, get, size | ||||
|     , keys, values, toList, fromList | ||||
|     , rehash, union | ||||
|     , encode, decoder, softDecoder | ||||
|     ) | ||||
| 
 | ||||
| {-| This module abstracts the `Dict` type with one function that assigns a | ||||
| unique identifier for each value based on a function that assigns each value. | ||||
| 
 | ||||
| This allows you to store values based on an externally defined identifier. | ||||
| 
 | ||||
| 
 | ||||
| ## Dictionaries | ||||
| 
 | ||||
| @docs Hashdict | ||||
| 
 | ||||
| 
 | ||||
| ## Build | ||||
| 
 | ||||
| @docs empty, singleton, insert, remove, removeKey | ||||
| 
 | ||||
| 
 | ||||
| ## Query | ||||
| 
 | ||||
| @docs isEmpty, member, memberKey, get, size | ||||
| 
 | ||||
| 
 | ||||
| ## Lists | ||||
| 
 | ||||
| @docs keys, values, toList, fromList | ||||
| 
 | ||||
| 
 | ||||
| ## Transform | ||||
| 
 | ||||
| @docs rehash, union | ||||
| 
 | ||||
| 
 | ||||
| ## JSON coders | ||||
| 
 | ||||
| @docs encode, decoder, softDecoder | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import FastDict as Dict exposing (Dict) | ||||
| import Json.Decode as D | ||||
| import Json.Encode as E | ||||
| 
 | ||||
| 
 | ||||
| {-| A dictionary of keys and values where each key is defined by its value. For | ||||
| example, this can be useful when every user is identifiable by their username: | ||||
| 
 | ||||
|     import Hashdict exposing (Hashdict) | ||||
| 
 | ||||
|     users : Hashdict User | ||||
|     users = | ||||
|         Hashdict.fromList .name | ||||
|             [ User "Alice" 28 1.65 | ||||
|             , User "Bob" 19 1.82 | ||||
|             , User "Chuck" 33 1.75 | ||||
|             ] | ||||
| 
 | ||||
|     type alias User = | ||||
|         { name : String | ||||
|         , age : Int | ||||
|         , height : Float | ||||
|         } | ||||
| 
 | ||||
| In the example listed above, the users are stored by their username, which means | ||||
| that all you need to know is the value "Alice" to retrieve all the information | ||||
| about them. Additionally, you do not need to specify a key to insert the values. | ||||
| 
 | ||||
| -} | ||||
| type Hashdict a | ||||
|     = Hashdict | ||||
|         { hash : a -> String | ||||
|         , values : Dict String a | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| 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 | ||||
| as expected, the decoder will fail to decode the hashdict. | ||||
| -} | ||||
| decoder : (a -> String) -> D.Decoder a -> D.Decoder (Hashdict a) | ||||
| decoder f xDecoder = | ||||
|     D.keyValuePairs xDecoder | ||||
|         |> 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. | ||||
| -} | ||||
| empty : (a -> String) -> Hashdict a | ||||
| empty hash = | ||||
|     Hashdict { hash = hash, values = Dict.empty } | ||||
| 
 | ||||
| 
 | ||||
| {-| Encode a Hashdict into a JSON value. Keep in mind that an Elm function | ||||
| cannot be universally converted to JSON, so it is up to you to preserve that | ||||
| hash function! | ||||
| -} | ||||
| encode : (a -> E.Value) -> Hashdict a -> E.Value | ||||
| encode encodeX (Hashdict h) = | ||||
|     h.values | ||||
|         |> Dict.toList | ||||
|         |> List.map (Tuple.mapSecond encodeX) | ||||
|         |> E.object | ||||
| 
 | ||||
| 
 | ||||
| {-| Convert an association list into a hashdict. | ||||
| -} | ||||
| fromList : (a -> String) -> List a -> Hashdict a | ||||
| fromList hash xs = | ||||
|     Hashdict | ||||
|         { hash = hash | ||||
|         , values = | ||||
|             xs | ||||
|                 |> List.map (\x -> ( hash x, x )) | ||||
|                 |> Dict.fromList | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| Get the value associated with a hash. If the hash is not found, return | ||||
| `Nothing`. This is useful when you are not sure if a hash will be in the | ||||
| hashdict. | ||||
| -} | ||||
| get : String -> Hashdict a -> Maybe a | ||||
| get k (Hashdict h) = | ||||
|     Dict.get k h.values | ||||
| 
 | ||||
| 
 | ||||
| {-| Insert a value into a hashdict. The key is automatically generated by the | ||||
| hash function. If the function generates a collision, it replaces the existing | ||||
| value in the hashdict. | ||||
| -} | ||||
| insert : a -> Hashdict a -> Hashdict a | ||||
| insert v (Hashdict h) = | ||||
|     Hashdict { h | values = Dict.insert (h.hash v) v h.values } | ||||
| 
 | ||||
| 
 | ||||
| {-| Determine if a hashdict is empty. | ||||
| -} | ||||
| isEmpty : Hashdict a -> Bool | ||||
| isEmpty (Hashdict h) = | ||||
|     Dict.isEmpty h.values | ||||
| 
 | ||||
| 
 | ||||
| {-| Get all of the hashes in a hashdict, sorted from lowest to highest. | ||||
| -} | ||||
| keys : Hashdict a -> List String | ||||
| keys (Hashdict h) = | ||||
|     Dict.keys h.values | ||||
| 
 | ||||
| 
 | ||||
| {-| Determine if a value's hash is in a hashdict. | ||||
| -} | ||||
| member : a -> Hashdict a -> Bool | ||||
| member value (Hashdict h) = | ||||
|     Dict.member (h.hash value) h.values | ||||
| 
 | ||||
| 
 | ||||
| {-| Determine if a hash is in a hashdict. | ||||
| -} | ||||
| memberKey : String -> Hashdict a -> Bool | ||||
| memberKey key (Hashdict h) = | ||||
|     Dict.member key h.values | ||||
| 
 | ||||
| 
 | ||||
| {-| Remap a hashdict using a new hashing algorithm. | ||||
| -} | ||||
| rehash : (a -> String) -> Hashdict a -> Hashdict a | ||||
| rehash f (Hashdict h) = | ||||
|     Hashdict | ||||
|         { hash = f | ||||
|         , values = | ||||
|             h.values | ||||
|                 |> Dict.values | ||||
|                 |> List.map (\v -> ( f v, v )) | ||||
|                 |> Dict.fromList | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| Remove a value from a hashdict. If the value's hash is found, the key-value | ||||
| pair is removed. If the value's hash is not found, no changes are made. | ||||
| 
 | ||||
|     hdict |> Hashdict.remove (User "Alice" 19 1.82) | ||||
| 
 | ||||
| -} | ||||
| remove : a -> Hashdict a -> Hashdict a | ||||
| remove v (Hashdict h) = | ||||
|     Hashdict { h | values = Dict.remove (h.hash v) h.values } | ||||
| 
 | ||||
| 
 | ||||
| {-| Remove a key from a hashdict. If the key is not found, no changes are made. | ||||
| 
 | ||||
|     hdict |> Hashdict.removeKey "Alice" | ||||
| 
 | ||||
| -} | ||||
| removeKey : String -> Hashdict a -> Hashdict a | ||||
| removeKey k (Hashdict h) = | ||||
|     Hashdict { h | values = Dict.remove k h.values } | ||||
| 
 | ||||
| 
 | ||||
| {-| Create a hashdict with a single key-value pair. | ||||
| -} | ||||
| singleton : (a -> String) -> a -> Hashdict a | ||||
| singleton f v = | ||||
|     empty f |> insert v | ||||
| 
 | ||||
| 
 | ||||
| {-| Determine the number of values in a hashdict. | ||||
| -} | ||||
| size : Hashdict a -> Int | ||||
| size (Hashdict h) = | ||||
|     Dict.size h.values | ||||
| 
 | ||||
| 
 | ||||
| {-| Decode a hashdict from a JSON value. If you cannot deduce the originally | ||||
| 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. | ||||
| -} | ||||
| softDecoder : (a -> String) -> D.Decoder a -> D.Decoder (Hashdict a) | ||||
| softDecoder f xDecoder = | ||||
|     D.keyValuePairs xDecoder | ||||
|         |> D.map (List.map Tuple.second >> fromList f) | ||||
| 
 | ||||
| 
 | ||||
| {-| Convert a hashdict into an association list of key-value pairs, sorted by | ||||
| keys. | ||||
| -} | ||||
| toList : Hashdict a -> List ( String, a ) | ||||
| toList (Hashdict h) = | ||||
|     Dict.toList h.values | ||||
| 
 | ||||
| 
 | ||||
| {-| Combine two hashdicts under the hash function of the first. If there is a | ||||
| collision, preference is given to the first hashdict. | ||||
| -} | ||||
| union : Hashdict a -> Hashdict a -> Hashdict a | ||||
| union (Hashdict h1) hd2 = | ||||
|     case rehash h1.hash hd2 of | ||||
|         Hashdict h2 -> | ||||
|             Hashdict | ||||
|                 { hash = h1.hash | ||||
|                 , values = Dict.union h1.values h2.values | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
| {-| Get all values stored in the hashdict, in the order of their keys. | ||||
| -} | ||||
| values : Hashdict a -> List a | ||||
| values (Hashdict h) = | ||||
|     Dict.values h.values | ||||
|  | @ -0,0 +1,197 @@ | |||
| module Internal.Tools.Iddict exposing | ||||
|     ( Iddict | ||||
|     , empty, singleton, insert, map, remove | ||||
|     , isEmpty, member, get, size | ||||
|     , keys, values | ||||
|     , 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 encode, decoder | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import FastDict as Dict exposing (Dict) | ||||
| import Json.Decode as D | ||||
| import Json.Encode as E | ||||
| 
 | ||||
| 
 | ||||
| {-| The Iddict data type. | ||||
| -} | ||||
| type Iddict a | ||||
|     = Iddict | ||||
|         { cursor : Int | ||||
|         , dict : Dict Int a | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| Decode an id-dict from a JSON value. | ||||
| -} | ||||
| decoder : D.Decoder a -> D.Decoder (Iddict a) | ||||
| decoder xDecoder = | ||||
|     D.map2 | ||||
|         (\c pairs -> | ||||
|             let | ||||
|                 dict : Dict Int a | ||||
|                 dict = | ||||
|                     pairs | ||||
|                         |> List.filterMap | ||||
|                             (\( k, v ) -> | ||||
|                                 k | ||||
|                                     |> String.toInt | ||||
|                                     |> Maybe.map (\n -> ( n, v )) | ||||
|                             ) | ||||
|                         |> Dict.fromList | ||||
|             in | ||||
|             Iddict | ||||
|                 { cursor = | ||||
|                     Dict.keys dict | ||||
|                         -- Larger than all values in the list | ||||
|                         |> List.map ((+) 1) | ||||
|                         |> List.maximum | ||||
|                         |> Maybe.withDefault 0 | ||||
|                         |> max (Dict.size dict) | ||||
|                         -- At least the dict size | ||||
|                         |> max c | ||||
| 
 | ||||
|                 -- At least the given value | ||||
|                 , dict = dict | ||||
|                 } | ||||
|         ) | ||||
|         (D.field "cursor" D.int) | ||||
|         (D.field "dict" <| D.keyValuePairs xDecoder) | ||||
| 
 | ||||
| 
 | ||||
| {-| Create an empty id-dict. | ||||
| -} | ||||
| empty : Iddict a | ||||
| empty = | ||||
|     Iddict | ||||
|         { cursor = 0 | ||||
|         , dict = Dict.empty | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| Encode an id-dict to a JSON value. | ||||
| -} | ||||
| encode : (a -> E.Value) -> Iddict a -> E.Value | ||||
| encode encodeX (Iddict d) = | ||||
|     E.object | ||||
|         [ ( "cursor", E.int d.cursor ) | ||||
|         , ( "dict" | ||||
|           , d.dict | ||||
|                 |> Dict.toCoreDict | ||||
|                 |> E.dict String.fromInt encodeX | ||||
|           ) | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
| {-| 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 | ||||
|  | @ -0,0 +1,43 @@ | |||
| module Internal.Tools.Timestamp exposing | ||||
|     ( Timestamp | ||||
|     , encode, decoder | ||||
|     ) | ||||
| 
 | ||||
| {-| 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. | ||||
| 
 | ||||
| 
 | ||||
| ## Timestamp | ||||
| 
 | ||||
| @docs Timestamp | ||||
| 
 | ||||
| 
 | ||||
| ## JSON coders | ||||
| 
 | ||||
| @docs encode, decoder | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Json.Decode as D | ||||
| import Json.Encode as E | ||||
| import Time | ||||
| 
 | ||||
| 
 | ||||
| {-| The Timestamp data type representing a moment in time. | ||||
| -} | ||||
| type alias Timestamp = | ||||
|     Time.Posix | ||||
| 
 | ||||
| 
 | ||||
| {-| Encode a timestamp into a JSON value. | ||||
| -} | ||||
| encode : Timestamp -> E.Value | ||||
| encode = | ||||
|     Time.posixToMillis >> E.int | ||||
| 
 | ||||
| 
 | ||||
| {-| Decode a timestamp from a JSON value. | ||||
| -} | ||||
| decoder : D.Decoder Timestamp | ||||
| decoder = | ||||
|     D.map Time.millisToPosix D.int | ||||
|  | @ -0,0 +1,365 @@ | |||
| module Internal.Tools.VersionControl exposing | ||||
|     ( VersionControl, withBottomLayer | ||||
|     , sameForVersion, MiddleLayer, addMiddleLayer | ||||
|     , isSupported, toDict, fromVersion, mostRecentFromVersionList, fromVersionList | ||||
|     ) | ||||
| 
 | ||||
| {-| | ||||
| 
 | ||||
| 
 | ||||
| # Version Control module | ||||
| 
 | ||||
| This module helps you maintain different functions based on their version. | ||||
| 
 | ||||
| Not every Matrix homeserver is the same. Some keep up with the latest Matrix | ||||
| specifications, while others stay behind because they have to support legacy | ||||
| projects who do not support new API endpoints (yet). The Elm SDK aims to support | ||||
| as many homeserver versions as possible - at the same time. | ||||
| 
 | ||||
| Support for legacy versions can be difficult! The Elm SDK expects one way of | ||||
| getting information, and translating every Matrix spec(ification) version to it | ||||
| can take time. But what if a new Matrix spec version adds a new feature? Do we | ||||
| need to re-translate every single version to accomodate any future updates? | ||||
| 
 | ||||
| The VersionControl helps define different API rules for different spec versions | ||||
| in an easy way. The VersionControl module puts all the versions in a linear | ||||
| timeline. (Because, you know, updates are usually newer versions of older | ||||
| versions.) This way, you can define different behaviour while still having only | ||||
| one input, one output. | ||||
| 
 | ||||
| The module can be best described as a layered version type. | ||||
| 
 | ||||
|     |----------------------------------------------| | ||||
|     | VersionControl                               | | ||||
|     |                     input            output  | | ||||
|     |                       |                ^     | | ||||
|     |---------------------- | -------------- | ----| | ||||
|                             |                | | ||||
|     |---------------------- | -------------- | ----| | ||||
|     | MiddleLayer v3        |                |     | | ||||
|     |                       [---> current ---]     | | ||||
|     |                       |                |     | | ||||
|     |                    downcast          upcast  | | ||||
|     |                       |                ^     | | ||||
|     |---------------------- | -------------- | ----| | ||||
|                             |                | | ||||
|     |---------------------- | -------------- | ----| | ||||
|     | MiddleLayer v2        |                |     | | ||||
|     |                       [---> current ---]     | | ||||
|     |                       |                |     | | ||||
|     |                    downcast          upcast  | | ||||
|     |                       |                ^     | | ||||
|     |---------------------- | -------------- | ----| | ||||
|                             |                | | ||||
|     |---------------------- | -------------- | ----| | ||||
|     | BottomLayer v1        |                |     | | ||||
|     |                       \---> current ---/     | | ||||
|     |                                              | | ||||
|     |----------------------------------------------| | ||||
| 
 | ||||
| This method means you only need to write one downcast, one current and one | ||||
| upcast whenever you introduce a new version. In other words, you can instantly | ||||
| update all functions without having to write every version! | ||||
| 
 | ||||
| The VersionControl keeps tracks the version order. This way, you can either get | ||||
| the VersionControl type to render the function for the most recent supported | ||||
| version, or you can choose for yourself which version you prefer to use. | ||||
| 
 | ||||
| 
 | ||||
| ## Building a VersionControl | ||||
| 
 | ||||
| To build a VersionControl type, one must start with the bottom layer and start | ||||
| building up to newer versions with middle layers. | ||||
| 
 | ||||
| 
 | ||||
| ### Create | ||||
| 
 | ||||
| @docs VersionControl, withBottomLayer | ||||
| 
 | ||||
| 
 | ||||
| ### Expand | ||||
| 
 | ||||
| @docs sameForVersion, MiddleLayer, addMiddleLayer | ||||
| 
 | ||||
| 
 | ||||
| ## Getting functions | ||||
| 
 | ||||
| Once you've successfully built the VersionControl type, there's a variety of | ||||
| ways in which you can find an appropriate function. | ||||
| 
 | ||||
| @docs isSupported, toDict, fromVersion, mostRecentFromVersionList, fromVersionList | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Dict exposing (Dict) | ||||
| 
 | ||||
| 
 | ||||
| {-| The VersionControl layer is the layer on top that keeps track of all the | ||||
| available versions. It is usually defined with a bottom layer and a few layers | ||||
| on top. | ||||
| -} | ||||
| type VersionControl input output | ||||
|     = VersionControl | ||||
|         { latestVersion : input -> output | ||||
|         , order : List String | ||||
|         , versions : Dict String (input -> output) | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| The middle layer is placed between a VersionControl and a BottomLayer to | ||||
| support a new function for a new version. The abbreviations stand for the | ||||
| following: | ||||
| 
 | ||||
|   - `cin` means **current in**. It is the Middle Layer's input. | ||||
| 
 | ||||
|   - `cout` means **current out**. It is the Middle Layer's output. | ||||
| 
 | ||||
|   - `din` means **downcast in**. It is the Bottom Layer's input. | ||||
| 
 | ||||
|   - `dout` means **downcast out**. It is the Bottom Layer's output. | ||||
| 
 | ||||
| As a result, we have the following model to explain the MiddleLayer: | ||||
| 
 | ||||
|     |----------------------------------------------| | ||||
|     | VersionControl                               | | ||||
|     |                     input            output  | | ||||
|     |                       |                ^     | | ||||
|     |---------------------- | -------------- | ----| | ||||
|                            [cin]           [cout] | ||||
|     |---------------------- | -------------- | ----| | ||||
|     | MiddleLayer           |                |     | | ||||
|     |                       [---> current ---]     | | ||||
|     |                       |                |     | | ||||
|     |                    downcast          upcast  | | ||||
|     |                       |                ^     | | ||||
|     |---------------------- | -------------- | ----| | ||||
|                           [din]            [dout] | ||||
|     |---------------------- | -------------- | ----| | ||||
|     | BottomLayer           |                |     | | ||||
|     |                       \---> current ---/     | | ||||
|     |                                              | | ||||
|     |----------------------------------------------| | ||||
| 
 | ||||
| To sew a MiddleLayer type, we need the `downcast` and `upcast` functions to | ||||
| translate the `cin` and `cout` to meaningful values `din` and `dout` for the | ||||
| BottomLayer function. | ||||
| 
 | ||||
| Usually, this means transforming the data. For example, say our BottomLayer | ||||
| still has an old version where people had just one name, and our MiddleLayer | ||||
| version has two fields: a first and last name. | ||||
| 
 | ||||
|     type alias NewUser = | ||||
|         { firstName : String, lastName : String, age : Int } | ||||
| 
 | ||||
|     type alias OldUser = | ||||
|         { name : String, age : Int } | ||||
| 
 | ||||
| An appropriate downcasting function could then something like the following: | ||||
| 
 | ||||
|     downcast : NewUser -> OldUser | ||||
|     downcast user = | ||||
|         { name = user.firstName ++ " " ++ user.lastName, age = user.age } | ||||
| 
 | ||||
| -} | ||||
| type alias MiddleLayer cin cout din dout = | ||||
|     { current : cin -> cout | ||||
|     , downcast : cin -> din | ||||
|     , upcast : dout -> cout | ||||
|     , version : String | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| {-| Add a MiddleLayer to the VersionControl, effectively updating all old | ||||
| functions with a downcast and upcast to deal with the inputs and outputs of all | ||||
| functions at the same time. | ||||
| 
 | ||||
| For example, using the `NewUser` and `OldUser` types, one could create the | ||||
| following example to get the user's names: | ||||
| 
 | ||||
|     vc : VersionControl NewUser String | ||||
|     vc = | ||||
|         withBottomLayer | ||||
|             { current = .name | ||||
|             , version = "v1" | ||||
|             } | ||||
|             |> sameForVersion "v2" | ||||
|             |> sameForVersion "v3" | ||||
|             |> sameForVersion "v4" | ||||
|             |> sameForVersion "v5" | ||||
|             |> sameForVersion "v6" | ||||
|             |> addMiddleLayer | ||||
|                 { downcast = \user -> { name = user.firstName ++ " " ++ user.lastName, age = user.age } | ||||
|                 , current = \user -> user.firstName ++ " " ++ user.lastName | ||||
|                 , upcast = identity | ||||
|                 , version = "v7" | ||||
|                 } | ||||
| 
 | ||||
| Effectively, even though versions `v1` through `v6` still require an `OldUser` | ||||
| type as an input, all functions have now been updated to the new standard of | ||||
| getting a `NewUser` as an input thanks to the `downcast` function. | ||||
| 
 | ||||
| -} | ||||
| addMiddleLayer : MiddleLayer cin cout din dout -> VersionControl din dout -> VersionControl cin cout | ||||
| addMiddleLayer { current, downcast, upcast, version } (VersionControl d) = | ||||
|     VersionControl | ||||
|         { latestVersion = current | ||||
|         , order = version :: d.order | ||||
|         , versions = | ||||
|             d.versions | ||||
|                 |> Dict.map (\_ f -> downcast >> f >> upcast) | ||||
|                 |> Dict.insert version current | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| Get the function that corresponds with a given version. Returns `Nothing` if | ||||
| the version has never been inserted into the VersionControl type. | ||||
| -} | ||||
| fromVersion : String -> VersionControl a b -> Maybe (a -> b) | ||||
| fromVersion version (VersionControl { versions }) = | ||||
|     Dict.get version versions | ||||
| 
 | ||||
| 
 | ||||
| {-| Provided a list of versions, this function will provide a list of compatible versions to you in your preferred order. | ||||
| 
 | ||||
| If you just care about getting the most recent function, you will be better off using `mostRecentFromVersionList`, | ||||
| but this function can help if you care about knowing which Matrix spec version you're using. | ||||
| 
 | ||||
| -} | ||||
| fromVersionList : List String -> VersionControl a b -> List ( String, a -> b ) | ||||
| fromVersionList versionList vc = | ||||
|     List.filterMap | ||||
|         (\version -> | ||||
|             vc | ||||
|                 |> fromVersion version | ||||
|                 |> Maybe.map (\f -> ( version, f )) | ||||
|         ) | ||||
|         versionList | ||||
| 
 | ||||
| 
 | ||||
| {-| Determine if a version is supported by the VersionControl. | ||||
| 
 | ||||
|     vc : VersionControl NewUser String | ||||
|     vc = | ||||
|         withBottomLayer | ||||
|             { current = .name | ||||
|             , version = "v1" | ||||
|             } | ||||
|             |> sameForVersion "v2" | ||||
|             |> sameForVersion "v3" | ||||
|             |> sameForVersion "v4" | ||||
| 
 | ||||
|     isSupported "v3" vc -- True | ||||
|     isSupported "v9" vc -- False | ||||
| 
 | ||||
| -} | ||||
| isSupported : String -> VersionControl a b -> Bool | ||||
| isSupported version (VersionControl d) = | ||||
|     Dict.member version d.versions | ||||
| 
 | ||||
| 
 | ||||
| {-| Get the most recent event based on a list of versions. Returns `Nothing` if | ||||
| the list is empty, or if none of the versions are supported. | ||||
| 
 | ||||
|     vc : VersionControl a b | ||||
|     vc = | ||||
|       withBottomLayer | ||||
|         { current = foo | ||||
|         , version = "v1" | ||||
|         } | ||||
|         |> sameForVersion "v2" | ||||
|         |> sameForVersion "v3" | ||||
|         |> sameForVersion "v4" | ||||
|         |> sameForVersion "v5" | ||||
|         |> sameForVersion "v6" | ||||
| 
 | ||||
|     -- This returns the function for v6 because that is the most recent version | ||||
|     -- in the provided version list | ||||
|     mostRecentFromVersionList [ "v5", "v3", "v7", "v6", "v8" ] vc | ||||
| 
 | ||||
| -} | ||||
| mostRecentFromVersionList : List String -> VersionControl a b -> Maybe (a -> b) | ||||
| mostRecentFromVersionList versionList ((VersionControl { order }) as vc) = | ||||
|     order | ||||
|         |> List.filter (\o -> List.member o versionList) | ||||
|         |> List.filterMap (\v -> fromVersion v vc) | ||||
|         |> List.head | ||||
| 
 | ||||
| 
 | ||||
| {-| Not every version overhauls every interaction. For this reason, many version | ||||
| functions are identical to their previous functions. | ||||
| 
 | ||||
| This function adds a new version to the VersionControl and tells it that the | ||||
| version uses the same function as the previous version. | ||||
| 
 | ||||
|     vc : VersionControl User String | ||||
|     vc = | ||||
|         withBottomLayer | ||||
|             { current = .name | ||||
|             , version = "v1" | ||||
|             } | ||||
|             |> sameForVersion "v2" | ||||
|             |> sameForVersion "v3" | ||||
|             |> sameForVersion "v4" | ||||
|             |> sameForVersion "v5" | ||||
|             |> sameForVersion "v6" | ||||
| 
 | ||||
| The example above lists the function `.name` for versions `v1` through `v6`. | ||||
| 
 | ||||
| -} | ||||
| sameForVersion : String -> VersionControl a b -> VersionControl a b | ||||
| sameForVersion version (VersionControl data) = | ||||
|     VersionControl | ||||
|         { data | ||||
|             | order = version :: data.order | ||||
|             , versions = Dict.insert version data.latestVersion data.versions | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| Get a dict of all available functions. | ||||
| 
 | ||||
| 
 | ||||
|     vc : VersionControl NewUser String | ||||
|     vc = | ||||
|         withBottomLayer | ||||
|             { current = .name | ||||
|             , version = "v1" | ||||
|             } | ||||
|             |> sameForVersion "v2" | ||||
|             |> sameForVersion "v3" | ||||
|             |> sameForVersion "v4" | ||||
|             |> toDict | ||||
| 
 | ||||
|     -- Dict.fromList | ||||
|     --     [ ( "v1", <internal> ) | ||||
|     --     , ( "v2", <internal> ) | ||||
|     --     , ( "v3", <internal> ) | ||||
|     --     , ( "v4", <internal> ) | ||||
|     --     ] | ||||
| 
 | ||||
| -} | ||||
| toDict : VersionControl a b -> Dict String (a -> b) | ||||
| toDict (VersionControl d) = | ||||
|     d.versions | ||||
| 
 | ||||
| 
 | ||||
| {-| You cannot create an empty VersionControl layer, you must always start with a BottomLayer | ||||
| and then stack MiddleLayer types on top until you've reached the version that you're happy with. | ||||
| 
 | ||||
|     vc : VersionControl User String | ||||
|     vc = | ||||
|         withBottomLayer | ||||
|             { current = .name | ||||
|             , version = "v1" | ||||
|             } | ||||
| 
 | ||||
|     type alias User = | ||||
|         { name : String, age : Int } | ||||
| 
 | ||||
| -} | ||||
| withBottomLayer : { current : input -> output, version : String } -> VersionControl input output | ||||
| withBottomLayer { current, version } = | ||||
|     VersionControl | ||||
|         { latestVersion = current | ||||
|         , order = List.singleton version | ||||
|         , versions = Dict.singleton version current | ||||
|         } | ||||
|  | @ -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 } | ||||
|  | @ -0,0 +1,301 @@ | |||
| module Internal.Values.Envelope exposing | ||||
|     ( Envelope, init | ||||
|     , map, mapMaybe, mapList | ||||
|     , Settings, mapSettings, extractSettings | ||||
|     , mapContext | ||||
|     , getContent, extract | ||||
|     , 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 encode, decoder | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
| {-| 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 Envelope a | ||||
|     = Envelope | ||||
|         { content : a | ||||
|         , context : Context | ||||
|         , settings : Settings | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| Custom settings that can be manipulated by the user. These serve as a | ||||
| configuration for how the Elm SDK should behave. | ||||
| 
 | ||||
| Custom settings are always part of the Envelope, allowing all functions to | ||||
| behave under the user's preferred settings. | ||||
| 
 | ||||
| -} | ||||
| type alias Settings = | ||||
|     { currentVersion : String | ||||
|     , deviceName : String | ||||
|     , syncTime : Int | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| {-| Decode an enveloped type from a JSON value. The decoder also imports any | ||||
| potential tokens, values and settings included in the JSON. | ||||
| -} | ||||
| decoder : D.Decoder a -> D.Decoder (Envelope a) | ||||
| decoder xDecoder = | ||||
|     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) | ||||
| 
 | ||||
| 
 | ||||
| {-| Decode settings from a JSON value. | ||||
| -} | ||||
| decoderSettings : D.Decoder Settings | ||||
| decoderSettings = | ||||
|     D.map3 Settings | ||||
|         (D.opFieldWithDefault "currentVersion" Default.currentVersion D.string) | ||||
|         (D.opFieldWithDefault "deviceName" Default.deviceName D.string) | ||||
|         (D.opFieldWithDefault "syncTime" Default.syncTime D.int) | ||||
| 
 | ||||
| 
 | ||||
| {-| Encode an enveloped type into a JSON value. The function encodes all | ||||
| non-standard settings, tokens and values. | ||||
| -} | ||||
| 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 ) | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
| {-| Encode the settings into a JSON value. | ||||
| -} | ||||
| encodeSettings : Settings -> E.Value | ||||
| encodeSettings settings = | ||||
|     let | ||||
|         differentFrom : b -> b -> Maybe b | ||||
|         differentFrom defaultValue currentValue = | ||||
|             if currentValue == defaultValue then | ||||
|                 Nothing | ||||
| 
 | ||||
|             else | ||||
|                 Just currentValue | ||||
|     in | ||||
|     E.maybeObject | ||||
|         [ ( "currentVersion" | ||||
|           , settings.currentVersion | ||||
|                 |> differentFrom Default.currentVersion | ||||
|                 |> Maybe.map E.string | ||||
|           ) | ||||
|         , ( "deviceName" | ||||
|           , settings.deviceName | ||||
|                 |> differentFrom Default.deviceName | ||||
|                 |> Maybe.map E.string | ||||
|           ) | ||||
|         , ( "syncTime" | ||||
|           , settings.syncTime | ||||
|                 |> differentFrom Default.syncTime | ||||
|                 |> Maybe.map E.int | ||||
|           ) | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
| {-| 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 (Envelope 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 (Envelope 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 = | ||||
|     Envelope | ||||
|         { content = x | ||||
|         , context = Context.init | ||||
|         , settings = | ||||
|             { currentVersion = Default.currentVersion | ||||
|             , deviceName = Default.deviceName | ||||
|             , syncTime = Default.syncTime | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| {-| 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 (Envelope data) = | ||||
|     Envelope | ||||
|         { 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 (Envelope data) = | ||||
|     Envelope | ||||
|         { content = data.content | ||||
|         , 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. | ||||
| 
 | ||||
|     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 (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 | ||||
|         (\content -> map (always content) (Envelope data)) | ||||
|         data.content | ||||
|  | @ -0,0 +1,15 @@ | |||
| module Internal.Values.Vault exposing (Vault) | ||||
| 
 | ||||
| {-| This module hosts the Vault module. | ||||
| 
 | ||||
| @docs Vault | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Internal.Values.Envelope as Envelope | ||||
| 
 | ||||
| 
 | ||||
| {-| This is the Vault type. | ||||
| -} | ||||
| type alias Vault = | ||||
|     Envelope.Envelope {} | ||||
|  | @ -1,5 +1,9 @@ | |||
| module Matrix exposing (Vault) | ||||
| {-| # Matrix SDK | ||||
| 
 | ||||
| {-| | ||||
| 
 | ||||
| 
 | ||||
| # Matrix SDK | ||||
| 
 | ||||
| This first version forms a mere basis from which we will create iterative builds | ||||
| that slowly improve the codebase. | ||||
|  | @ -8,13 +12,20 @@ It is generally quite unusual to regularly publish iterative beta versions on | |||
| the public registry, but it is also generally quite unusual to exclusively | ||||
| support a monolithic public registry. (: | ||||
| 
 | ||||
| 
 | ||||
| ## Vault | ||||
| 
 | ||||
| @docs Vault | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Types | ||||
| 
 | ||||
| 
 | ||||
| {-| The Vault type stores all relevant information about the Matrix API. | ||||
| 
 | ||||
| It currently supports no functionality and it will just stay here - for fun. | ||||
| 
 | ||||
| -} | ||||
| type Vault = Vault | ||||
| type alias Vault = | ||||
|     Types.Vault | ||||
|  |  | |||
|  | @ -0,0 +1,71 @@ | |||
| module Matrix.Settings exposing | ||||
|     ( getDeviceName, setDeviceName | ||||
|     , getSyncTime, setSyncTime | ||||
|     ) | ||||
| 
 | ||||
| {-| The Matrix Vault has lots of configurable variables that you rarely want to | ||||
| interact with. Usually, you configure these variables only when creating a new | ||||
| Vault, or when a user explicitly changes one of their preferred settings. | ||||
| 
 | ||||
| 
 | ||||
| ## Device name | ||||
| 
 | ||||
| The default device name that is being communicated with the Matrix API. | ||||
| 
 | ||||
| This is mostly useful for users who are logged in with multiple sessions. They | ||||
| will see device names like "Element for Android" or "Element on iOS". For the | ||||
| Elm SDK, they will by default see the Elm SDK with its version included. If you | ||||
| are writing a custom client, however, you are free to change this to something | ||||
| more meaningful to the user. | ||||
| 
 | ||||
| @docs getDeviceName, setDeviceName | ||||
| 
 | ||||
| 
 | ||||
| ## Sync time | ||||
| 
 | ||||
| Whenever the Matrix API has nothing new to report, the Elm SDK is kept on | ||||
| hold until something new happens. The `syncTime` indicates a timeout to how long | ||||
| the Elm SDK tolerates being held on hold. | ||||
| 
 | ||||
|   - ↗️ A high value is good because it significantly reduces traffic between the | ||||
|     user and the homeserver. | ||||
|   - ↘️ A low value is good because it reduces the risk of | ||||
|     the connection ending abruptly or unexpectedly. | ||||
| 
 | ||||
| Nowadays, most libraries use 30 seconds as the standard, as does the Elm SDK. | ||||
| The value is in miliseconds, so it is set at 30,000. | ||||
| 
 | ||||
| @docs getSyncTime, setSyncTime | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Internal.Values.Envelope as Envelope | ||||
| import Types exposing (Vault(..)) | ||||
| 
 | ||||
| 
 | ||||
| {-| Determine the device name. | ||||
| -} | ||||
| getDeviceName : Vault -> String | ||||
| getDeviceName (Vault vault) = | ||||
|     Envelope.extractSettings .deviceName vault | ||||
| 
 | ||||
| 
 | ||||
| {-| Override the device name. | ||||
| -} | ||||
| setDeviceName : String -> Vault -> Vault | ||||
| setDeviceName name (Vault vault) = | ||||
|     Vault <| Envelope.mapSettings (\s -> { s | deviceName = name }) vault | ||||
| 
 | ||||
| 
 | ||||
| {-| Determine the sync timeout value. | ||||
| -} | ||||
| getSyncTime : Vault -> Int | ||||
| getSyncTime (Vault vault) = | ||||
|     Envelope.extractSettings .syncTime vault | ||||
| 
 | ||||
| 
 | ||||
| {-| Override the sync timeout value. | ||||
| -} | ||||
| setSyncTime : Int -> Vault -> Vault | ||||
| setSyncTime time (Vault vault) = | ||||
|     Vault <| Envelope.mapSettings (\s -> { s | syncTime = time }) vault | ||||
|  | @ -0,0 +1,25 @@ | |||
| module Types exposing (Vault(..)) | ||||
| 
 | ||||
| {-| 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) | ||||
| highly recommend using opaque types in order to avoid breaking everyone's code | ||||
| in a future major release. | ||||
| 
 | ||||
| This module forms as a protective layer between the internal modules and the | ||||
| exposed modules, hiding all exposed types behind opaque types so the user cannot | ||||
| 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 | ||||
| 
 | ||||
| -} | ||||
| 
 | ||||
| import Internal.Values.Vault as Vault | ||||
| 
 | ||||
| 
 | ||||
| {-| Opaque type for Matrix Vault | ||||
| -} | ||||
| type Vault | ||||
|     = Vault Vault.Vault | ||||
|  | @ -0,0 +1,143 @@ | |||
| module Context exposing (..) | ||||
| 
 | ||||
| import Expect | ||||
| import Fuzz exposing (Fuzzer) | ||||
| import Internal.Config.Leaks as Leaks | ||||
| import Internal.Values.Context as Context exposing (Context) | ||||
| import Json.Decode as D | ||||
| import Json.Encode as E | ||||
| import Test exposing (..) | ||||
| 
 | ||||
| 
 | ||||
| fuzzer : Fuzzer Context | ||||
| fuzzer = | ||||
|     let | ||||
|         maybeString : Fuzzer (Maybe String) | ||||
|         maybeString = | ||||
|             Fuzz.maybe Fuzz.string | ||||
|     in | ||||
|     Fuzz.map7 Context | ||||
|         maybeString | ||||
|         maybeString | ||||
|         maybeString | ||||
|         maybeString | ||||
|         maybeString | ||||
|         maybeString | ||||
|         (Fuzz.maybe <| Fuzz.list Fuzz.string) | ||||
| 
 | ||||
| 
 | ||||
| {-| If a leak is spotted, make sure to change the leaking value and then test | ||||
| with the same seed to ensure it is not a (tiny) coincidence and a leak is in | ||||
| fact coming through. | ||||
| -} | ||||
| leaks : Test | ||||
| leaks = | ||||
|     describe "No leaks allowed" | ||||
|         [ fuzz2 fuzzer | ||||
|             Fuzz.string | ||||
|             "Access token" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setAccessToken value | ||||
|                     |> Context.getAccessToken | ||||
|                     |> Expect.notEqual Leaks.accessToken | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             Fuzz.string | ||||
|             "Base URL" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setBaseUrl value | ||||
|                     |> Context.getBaseUrl | ||||
|                     |> Expect.notEqual Leaks.baseUrl | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             Fuzz.string | ||||
|             "Transaction" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setTransaction value | ||||
|                     |> Context.getTransaction | ||||
|                     |> Expect.notEqual Leaks.transaction | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             (Fuzz.list Fuzz.string) | ||||
|             "Versions" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setVersions value | ||||
|                     |> Context.getVersions | ||||
|                     |> Expect.notEqual Leaks.versions | ||||
|             ) | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
| apiContext : Test | ||||
| apiContext = | ||||
|     describe "Verify writing info" | ||||
|         [ fuzz2 fuzzer | ||||
|             Fuzz.string | ||||
|             "Access token" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setAccessToken value | ||||
|                     |> Context.getAccessToken | ||||
|                     |> Expect.equal value | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             Fuzz.string | ||||
|             "Base URL" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setBaseUrl value | ||||
|                     |> Context.getBaseUrl | ||||
|                     |> Expect.equal value | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             Fuzz.string | ||||
|             "Transaction" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setTransaction value | ||||
|                     |> Context.getTransaction | ||||
|                     |> Expect.equal value | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             (Fuzz.list Fuzz.string) | ||||
|             "Versions" | ||||
|             (\context value -> | ||||
|                 context | ||||
|                     |> Context.apiFormat | ||||
|                     |> Context.setVersions value | ||||
|                     |> Context.getVersions | ||||
|                     |> Expect.equal value | ||||
|             ) | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
| json : Test | ||||
| json = | ||||
|     describe "JSON encode + JSON decode" | ||||
|         [ test "Empty is {}" | ||||
|             (Context.init | ||||
|                 |> Context.encode | ||||
|                 |> E.encode 0 | ||||
|                 |> Expect.equal "{}" | ||||
|                 |> always | ||||
|             ) | ||||
|         , fuzz fuzzer | ||||
|             "JSON recode" | ||||
|             (\context -> | ||||
|                 context | ||||
|                     |> Context.encode | ||||
|                     |> D.decodeValue Context.decoder | ||||
|                     |> Expect.equal (Ok context) | ||||
|             ) | ||||
|         ] | ||||
|  | @ -0,0 +1,275 @@ | |||
| module Iddict exposing (..) | ||||
| 
 | ||||
| import Expect | ||||
| import Fuzz exposing (Fuzzer) | ||||
| import Internal.Tools.Iddict as Iddict exposing (Iddict) | ||||
| 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 identity | ||||
|                 |> D.decodeValue (Iddict.decoder D.value) | ||||
|                 |> Expect.equal (Ok Iddict.empty) | ||||
|                 |> always | ||||
|             ) | ||||
|         , test "JSON encode" | ||||
|             (Iddict.empty | ||||
|                 |> Iddict.encode identity | ||||
|                 |> E.encode 0 | ||||
|                 |> Expect.equal "{\"cursor\":0,\"dict\":{}}" | ||||
|                 |> always | ||||
|             ) | ||||
|         , test "JSON decode" | ||||
|             ("{\"cursor\":0,\"dict\":{}}" | ||||
|                 |> D.decodeString (Iddict.decoder D.value) | ||||
|                 |> 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 E.int | ||||
|                     |> D.decodeValue (Iddict.decoder D.int) | ||||
|                     |> Expect.equal (Ok single) | ||||
|             ) | ||||
|         , fuzz Fuzz.int | ||||
|             "JSON encode" | ||||
|             (\i -> | ||||
|                 Iddict.singleton i | ||||
|                     |> Tuple.second | ||||
|                     |> Iddict.encode E.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 D.int) | ||||
|                     |> 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) | ||||
|             ) | ||||
|         ] | ||||
|  | @ -0,0 +1,53 @@ | |||
| module Vault exposing (..) | ||||
| 
 | ||||
| import Expect | ||||
| import Fuzz exposing (Fuzzer) | ||||
| import Internal.Config.Default as Default | ||||
| import Internal.Values.Envelope as Envelope | ||||
| import Matrix | ||||
| import Matrix.Settings | ||||
| import Test exposing (..) | ||||
| import Types | ||||
| 
 | ||||
| 
 | ||||
| fuzzer : Fuzzer Matrix.Vault | ||||
| fuzzer = | ||||
|     Fuzz.constant <| Types.Vault <| Envelope.init {} | ||||
| 
 | ||||
| 
 | ||||
| settings : Test | ||||
| settings = | ||||
|     describe "Edit settings" | ||||
|         [ fuzz fuzzer | ||||
|             "Default device name" | ||||
|             (\vault -> | ||||
|                 vault | ||||
|                     |> Matrix.Settings.getDeviceName | ||||
|                     |> Expect.equal Default.deviceName | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             Fuzz.string | ||||
|             "Set device name" | ||||
|             (\vault name -> | ||||
|                 vault | ||||
|                     |> Matrix.Settings.setDeviceName name | ||||
|                     |> Matrix.Settings.getDeviceName | ||||
|                     |> Expect.equal name | ||||
|             ) | ||||
|         , fuzz fuzzer | ||||
|             "Default sync time" | ||||
|             (\vault -> | ||||
|                 vault | ||||
|                     |> Matrix.Settings.getSyncTime | ||||
|                     |> Expect.equal Default.syncTime | ||||
|             ) | ||||
|         , fuzz2 fuzzer | ||||
|             Fuzz.int | ||||
|             "Set sync time" | ||||
|             (\vault sync -> | ||||
|                 vault | ||||
|                     |> Matrix.Settings.setSyncTime sync | ||||
|                     |> Matrix.Settings.getSyncTime | ||||
|                     |> Expect.equal sync | ||||
|             ) | ||||
|         ] | ||||
		Loading…
	
		Reference in New Issue