Compare commits
No commits in common. "8f83e6a41c72e563b70ca09354d55bf61385b47e" and "709d60805630216bb708d58e46b7886b7efdfe58" have entirely different histories.
8f83e6a41c
...
709d608056
26
elm.json
26
elm.json
|
@ -3,18 +3,38 @@
|
||||||
"name": "noordstar/elm-matrix-sdk-beta",
|
"name": "noordstar/elm-matrix-sdk-beta",
|
||||||
"summary": "Matrix SDK for instant communication. Unstable beta version for testing only.",
|
"summary": "Matrix SDK for instant communication. Unstable beta version for testing only.",
|
||||||
"license": "EUPL-1.1",
|
"license": "EUPL-1.1",
|
||||||
"version": "3.0.0",
|
"version": "2.1.2",
|
||||||
"exposed-modules": [
|
"exposed-modules": [
|
||||||
|
"Internal.Config.Default",
|
||||||
|
"Internal.Config.Leaks",
|
||||||
|
"Internal.Config.Log",
|
||||||
|
"Internal.Config.Phantom",
|
||||||
|
"Internal.Config.Text",
|
||||||
|
"Internal.Filter.Timeline",
|
||||||
|
"Internal.Tools.DecodeExtra",
|
||||||
|
"Internal.Tools.EncodeExtra",
|
||||||
|
"Internal.Tools.Hashdict",
|
||||||
|
"Internal.Tools.Iddict",
|
||||||
|
"Internal.Tools.Json",
|
||||||
|
"Internal.Tools.Mashdict",
|
||||||
|
"Internal.Tools.Timestamp",
|
||||||
|
"Internal.Tools.VersionControl",
|
||||||
|
"Internal.Values.Context",
|
||||||
|
"Internal.Values.Envelope",
|
||||||
|
"Internal.Values.Event",
|
||||||
|
"Internal.Values.Settings",
|
||||||
|
"Internal.Values.StateManager",
|
||||||
|
"Internal.Values.Timeline",
|
||||||
|
"Internal.Values.Vault",
|
||||||
"Matrix",
|
"Matrix",
|
||||||
"Matrix.Event",
|
"Matrix.Event",
|
||||||
"Matrix.Settings",
|
"Matrix.Settings",
|
||||||
"Matrix.User"
|
"Types"
|
||||||
],
|
],
|
||||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||||
"dependencies": {
|
"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/json": "1.0.0 <= v < 2.0.0",
|
||||||
"elm/parser": "1.0.0 <= v < 2.0.0",
|
|
||||||
"elm/time": "1.0.0 <= v < 2.0.0",
|
"elm/time": "1.0.0 <= v < 2.0.0",
|
||||||
"micahhahn/elm-safe-recursion": "2.0.0 <= v < 3.0.0",
|
"micahhahn/elm-safe-recursion": "2.0.0 <= v < 3.0.0",
|
||||||
"miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0"
|
"miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0"
|
||||||
|
|
|
@ -23,7 +23,7 @@ will assume until overriden by the user.
|
||||||
-}
|
-}
|
||||||
currentVersion : String
|
currentVersion : String
|
||||||
currentVersion =
|
currentVersion =
|
||||||
"beta 3.0.0"
|
"beta 2.1.2"
|
||||||
|
|
||||||
|
|
||||||
{-| The default device name that is being communicated with the Matrix API.
|
{-| The default device name that is being communicated with the Matrix API.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module Internal.Config.Text exposing
|
module Internal.Config.Text exposing
|
||||||
( docs, failures, fields, mappings, logs, parses
|
( docs, failures, fields, mappings, logs
|
||||||
, accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid
|
, accessTokenFoundLocally, accessTokenExpired, accessTokenInvalid
|
||||||
, versionsFoundLocally, versionsReceived, versionsFailedToDecode
|
, versionsFoundLocally, versionsReceived, versionsFailedToDecode
|
||||||
, unsupportedVersionForEndpoint
|
, unsupportedVersionForEndpoint
|
||||||
|
@ -27,7 +27,7 @@ You should only do this if you know what you're doing.
|
||||||
|
|
||||||
## Type documentation
|
## Type documentation
|
||||||
|
|
||||||
@docs docs, failures, fields, mappings, logs, parses
|
@docs docs, failures, fields, mappings, logs
|
||||||
|
|
||||||
|
|
||||||
## API Authentication
|
## API Authentication
|
||||||
|
@ -515,28 +515,6 @@ mappings =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Logs for issues that might be found while parsing strings into meaningful data.
|
|
||||||
-}
|
|
||||||
parses :
|
|
||||||
{ historicalUserId : String -> String
|
|
||||||
, reservedIPs :
|
|
||||||
{ ipv6Toipv4 : String
|
|
||||||
, multicast : String
|
|
||||||
, futureUse : String
|
|
||||||
, unspecified : String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parses =
|
|
||||||
{ historicalUserId = \name -> "Found a historical username `" ++ name ++ "`."
|
|
||||||
, reservedIPs =
|
|
||||||
{ ipv6Toipv4 = "Detected a reserved ip address that is formerly used as an IPv6 to IPv4 relay. It is unlikely that this IP Address is real."
|
|
||||||
, multicast = "Detected a reserved ip address that is used for multicasting. It is unlikely that this IP Address is real."
|
|
||||||
, futureUse = "Detected a reserves ip address that is reserved for future use. It is unlikely that this IP Address is real if you're running a recent version of the Elm SDK."
|
|
||||||
, unspecified = "This is an unspecified ip address. It is unlikely that this IP Address is real and someone might try to break something."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{-| The Matrix homeserver can specify how it wishes to communicate, and the Elm
|
{-| 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,
|
SDK aims to communicate accordingly. This may fail in some scenarios, however,
|
||||||
in which case it will throw this error.
|
in which case it will throw this error.
|
||||||
|
|
|
@ -48,7 +48,6 @@ for interacting with the Matrix API.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
import Internal.Grammar.UserId as U
|
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
|
@ -58,7 +57,7 @@ import Set exposing (Set)
|
||||||
{-| Placeholder Event type so the real Event doesn't need to be imported.
|
{-| Placeholder Event type so the real Event doesn't need to be imported.
|
||||||
-}
|
-}
|
||||||
type alias Event a =
|
type alias Event a =
|
||||||
{ a | eventType : String, sender : U.UserID }
|
{ a | eventType : String, sender : String }
|
||||||
|
|
||||||
|
|
||||||
{-| The Timeline Filter filters events out of a timeline, guaranteeing that only
|
{-| The Timeline Filter filters events out of a timeline, guaranteeing that only
|
||||||
|
@ -247,7 +246,7 @@ match (Filter f) { eventType, sender } =
|
||||||
let
|
let
|
||||||
mentionedSender : Bool
|
mentionedSender : Bool
|
||||||
mentionedSender =
|
mentionedSender =
|
||||||
Set.member (U.toString sender) f.senders
|
Set.member sender f.senders
|
||||||
|
|
||||||
mentionedType : Bool
|
mentionedType : Bool
|
||||||
mentionedType =
|
mentionedType =
|
||||||
|
|
|
@ -1,279 +0,0 @@
|
||||||
module Internal.Grammar.ServerName exposing
|
|
||||||
( ServerName, toString, fromString
|
|
||||||
, serverNameParser
|
|
||||||
, HostName(..)
|
|
||||||
)
|
|
||||||
|
|
||||||
{-|
|
|
||||||
|
|
||||||
|
|
||||||
# Server name
|
|
||||||
|
|
||||||
A homeserver is uniquely identified by its server name. The server name
|
|
||||||
represents the address at which the homeserver in question can be reached by
|
|
||||||
other homeservers.
|
|
||||||
|
|
||||||
@docs ServerName, toString, fromString
|
|
||||||
|
|
||||||
|
|
||||||
## Parser
|
|
||||||
|
|
||||||
@docs serverNameParser
|
|
||||||
|
|
||||||
|
|
||||||
## Debug
|
|
||||||
|
|
||||||
@docs HostName
|
|
||||||
|
|
||||||
-}
|
|
||||||
|
|
||||||
import Internal.Tools.ParserExtra as PE
|
|
||||||
import Parser as P exposing ((|.), (|=), Parser)
|
|
||||||
|
|
||||||
|
|
||||||
{-| The hostname is the location where the server can be found.
|
|
||||||
|
|
||||||
Notice how the Matrix spec specifies that the hostname can either be a DNS name,
|
|
||||||
an IPv4Address or an IPv6Address. Since the IPv4Address is compatible with the
|
|
||||||
specification of DNS names, however, and RFC1123 (section 2.1) does not require
|
|
||||||
a client to distinguish them, we treat IPv4Addresses like DNS names.
|
|
||||||
|
|
||||||
-}
|
|
||||||
type HostName
|
|
||||||
= DNS String
|
|
||||||
| IPv6 IPv6Address
|
|
||||||
|
|
||||||
|
|
||||||
{-| The IPv6Address is represented by a list of items BEFORE and AFTER the
|
|
||||||
double colons (::).
|
|
||||||
-}
|
|
||||||
type alias IPv6Address =
|
|
||||||
{ front : List String, back : List String }
|
|
||||||
|
|
||||||
|
|
||||||
{-| The server name is a combination of a hostname and an optional port.
|
|
||||||
-}
|
|
||||||
type alias ServerName =
|
|
||||||
{ host : HostName, port_ : Maybe Int }
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parser for the DNS name record. The Matrix spec bases its grammar on the
|
|
||||||
standard for internet host names, as specified by RFC1123, section 2.1, with an
|
|
||||||
extension IPv6 literals.
|
|
||||||
|
|
||||||
[RFC-1123 §2.2]
|
|
||||||
|
|
||||||
The syntax of a legal Internet host name was specified in RFC-952
|
|
||||||
[DNS:4]. One aspect of host name syntax is hereby changed: the
|
|
||||||
restriction on the first character is relaxed to allow either a
|
|
||||||
letter or a digit. Host software MUST support this more liberal
|
|
||||||
syntax.
|
|
||||||
|
|
||||||
Host software MUST handle host names of up to 63 characters and
|
|
||||||
SHOULD handle host names of up to 255 characters.
|
|
||||||
|
|
||||||
[RFC-952 §Assumptions-1]
|
|
||||||
|
|
||||||
A "name" (Net, Host, Gateway, or Domain name) is a text string up
|
|
||||||
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
|
|
||||||
sign (-), and period (.). Note that periods are only allowed when
|
|
||||||
they serve to delimit components of "domain style names". (See
|
|
||||||
RFC-921, "Domain Name System Implementation Schedule", for
|
|
||||||
background).
|
|
||||||
|
|
||||||
-}
|
|
||||||
dnsNameParser : Parser String
|
|
||||||
dnsNameParser =
|
|
||||||
P.chompIf Char.isAlphaNum
|
|
||||||
|. P.chompWhile (\c -> Char.isAlphaNum c || c == '-' || c == '.')
|
|
||||||
|> P.getChompedString
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert a string to a server name.
|
|
||||||
-}
|
|
||||||
fromString : String -> Maybe ServerName
|
|
||||||
fromString s =
|
|
||||||
P.run (serverNameParser |. P.end) s
|
|
||||||
|> (\out ->
|
|
||||||
case out of
|
|
||||||
Ok _ ->
|
|
||||||
out
|
|
||||||
|
|
||||||
Err e ->
|
|
||||||
Debug.log "No parse" e
|
|
||||||
|> always (Debug.log "original" s)
|
|
||||||
|> always out
|
|
||||||
)
|
|
||||||
|> Result.toMaybe
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse a Hostname.
|
|
||||||
-}
|
|
||||||
hostnameParser : Parser HostName
|
|
||||||
hostnameParser =
|
|
||||||
P.oneOf
|
|
||||||
[ P.succeed IPv6
|
|
||||||
|. P.symbol "["
|
|
||||||
|= ipv6Parser
|
|
||||||
|. P.symbol "]"
|
|
||||||
, P.succeed DNS
|
|
||||||
|= dnsNameParser
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse all values to the left of the double colon (::)
|
|
||||||
-}
|
|
||||||
ipv6LeftParser : Parser (List String)
|
|
||||||
ipv6LeftParser =
|
|
||||||
P.oneOf
|
|
||||||
[ P.succeed []
|
|
||||||
|. P.symbol ":"
|
|
||||||
, P.succeed (|>)
|
|
||||||
|= PE.times 1 7 (ipv6NumParser |. P.symbol ":")
|
|
||||||
|= P.oneOf
|
|
||||||
[ P.succeed (\bottom tail -> tail ++ [ bottom ])
|
|
||||||
|= ipv6NumParser
|
|
||||||
, P.succeed identity
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse an ordinary IPv6 number
|
|
||||||
-}
|
|
||||||
ipv6NumParser : Parser String
|
|
||||||
ipv6NumParser =
|
|
||||||
P.chompIf Char.isHexDigit
|
|
||||||
|> P.getChompedString
|
|
||||||
|> PE.times 1 4
|
|
||||||
|> P.map String.concat
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse an IPv6 Address
|
|
||||||
-}
|
|
||||||
ipv6Parser : Parser IPv6Address
|
|
||||||
ipv6Parser =
|
|
||||||
ipv6LeftParser
|
|
||||||
|> P.andThen
|
|
||||||
(\front ->
|
|
||||||
if List.length front < 8 then
|
|
||||||
P.succeed (IPv6Address front)
|
|
||||||
|= ipv6RightParser (8 - 1 - List.length front)
|
|
||||||
-- The -1 is because :: implies one or more zeroes
|
|
||||||
|
|
||||||
else
|
|
||||||
P.succeed (IPv6Address front [])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse all values to the right of the double colon (::)
|
|
||||||
-}
|
|
||||||
ipv6RightParser : Int -> Parser (List String)
|
|
||||||
ipv6RightParser n =
|
|
||||||
if n > 0 then
|
|
||||||
P.succeed identity
|
|
||||||
|. P.symbol ":"
|
|
||||||
|= P.oneOf
|
|
||||||
[ P.succeed (::)
|
|
||||||
|= ipv6NumParser
|
|
||||||
|= PE.times 0
|
|
||||||
(n - 1)
|
|
||||||
(P.succeed identity
|
|
||||||
|. P.symbol ":"
|
|
||||||
|= ipv6NumParser
|
|
||||||
)
|
|
||||||
, P.succeed []
|
|
||||||
]
|
|
||||||
|
|
||||||
else
|
|
||||||
P.succeed []
|
|
||||||
|. P.symbol ":"
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert an IPv6 address to a readable string format
|
|
||||||
-}
|
|
||||||
ipv6ToString : IPv6Address -> String
|
|
||||||
ipv6ToString { front, back } =
|
|
||||||
(if List.length front == 8 then
|
|
||||||
front
|
|
||||||
|
|
||||||
else if List.length back == 8 then
|
|
||||||
back
|
|
||||||
|
|
||||||
else
|
|
||||||
List.concat [ front, [ "" ], back ]
|
|
||||||
)
|
|
||||||
|> List.intersperse ":"
|
|
||||||
|> String.concat
|
|
||||||
|
|
||||||
|
|
||||||
portParser : Parser Int
|
|
||||||
portParser =
|
|
||||||
P.chompIf Char.isDigit
|
|
||||||
|. P.chompWhile Char.isDigit
|
|
||||||
|> P.getChompedString
|
|
||||||
|> P.andThen
|
|
||||||
(\v ->
|
|
||||||
case String.toInt v of
|
|
||||||
Just i ->
|
|
||||||
if 0 <= i && i <= 2 ^ 16 - 1 then
|
|
||||||
P.succeed i
|
|
||||||
|
|
||||||
else
|
|
||||||
P.problem ("Port out of range: " ++ v)
|
|
||||||
|
|
||||||
Nothing ->
|
|
||||||
P.problem "Not a port number"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse a server name. Generally used by other identifiers that have a server
|
|
||||||
name as one of its parts.
|
|
||||||
-}
|
|
||||||
serverNameParser : Parser ServerName
|
|
||||||
serverNameParser =
|
|
||||||
P.succeed ServerName
|
|
||||||
|= hostnameParser
|
|
||||||
|= P.oneOf
|
|
||||||
[ P.succeed Just
|
|
||||||
|. P.symbol ":"
|
|
||||||
|= portParser
|
|
||||||
, P.succeed Nothing
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert a parsed server name back to a string.
|
|
||||||
-}
|
|
||||||
toString : ServerName -> String
|
|
||||||
toString { host, port_ } =
|
|
||||||
let
|
|
||||||
hostString : String
|
|
||||||
hostString =
|
|
||||||
case host of
|
|
||||||
DNS name ->
|
|
||||||
name
|
|
||||||
|
|
||||||
IPv6 { front, back } ->
|
|
||||||
(if List.length front == 8 then
|
|
||||||
List.intersperse ":" front
|
|
||||||
|
|
||||||
else if List.length back == 8 then
|
|
||||||
List.intersperse ":" back
|
|
||||||
|
|
||||||
else
|
|
||||||
List.concat
|
|
||||||
[ List.intersperse ":" front
|
|
||||||
, [ "::" ]
|
|
||||||
, List.intersperse ":" back
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|> String.concat
|
|
||||||
|> (\i -> "[" ++ i ++ "]")
|
|
||||||
|
|
||||||
portString : String
|
|
||||||
portString =
|
|
||||||
port_
|
|
||||||
|> Maybe.map String.fromInt
|
|
||||||
|> Maybe.map ((++) ":")
|
|
||||||
|> Maybe.withDefault ""
|
|
||||||
in
|
|
||||||
hostString ++ portString
|
|
|
@ -1,128 +0,0 @@
|
||||||
module Internal.Grammar.UserId exposing
|
|
||||||
( UserID, toString, fromString
|
|
||||||
, userIdParser, isHistorical
|
|
||||||
)
|
|
||||||
|
|
||||||
{-|
|
|
||||||
|
|
||||||
|
|
||||||
# User ids
|
|
||||||
|
|
||||||
Users within Matrix are uniquely identified by their Matrix user ID. The user
|
|
||||||
ID is namespaced to the homeserver which allocated the account and has the form:
|
|
||||||
|
|
||||||
@localpart:domain
|
|
||||||
|
|
||||||
The localpart of a user ID is an opaque identifier for that user. It MUST NOT
|
|
||||||
be empty, and MUST contain only the characters a-z, 0-9, ., \_, =, -, /, and +.
|
|
||||||
|
|
||||||
The domain of a user ID is the server name of the homeserver which allocated
|
|
||||||
the account.
|
|
||||||
|
|
||||||
The length of a user ID, including the @ sigil and the domain, MUST NOT exceed
|
|
||||||
255 characters.
|
|
||||||
|
|
||||||
The complete grammar for a legal user ID is:
|
|
||||||
|
|
||||||
user_id = "@" user_id_localpart ":" server_name
|
|
||||||
user_id_localpart = 1*user_id_char
|
|
||||||
user_id_char = DIGIT
|
|
||||||
/ %x61-7A ; a-z
|
|
||||||
/ "-" / "." / "=" / "_" / "/" / "+"
|
|
||||||
|
|
||||||
Older versions of this specification were more tolerant of the characters
|
|
||||||
permitted in user ID localparts. There are currently active users whose user
|
|
||||||
IDs do not conform to the permitted character set, and a number of rooms whose
|
|
||||||
history includes events with a sender which does not conform. In order to
|
|
||||||
handle these rooms successfully, clients and servers MUST accept user IDs with
|
|
||||||
localparts from the expanded character set:
|
|
||||||
|
|
||||||
extended_user_id_char = %x21-39 / %x3B-7E ; all ASCII printing chars except :
|
|
||||||
|
|
||||||
|
|
||||||
## User ID
|
|
||||||
|
|
||||||
@docs UserID, toString, fromString
|
|
||||||
|
|
||||||
|
|
||||||
## Extra
|
|
||||||
|
|
||||||
@docs userIdParser, isHistorical
|
|
||||||
|
|
||||||
-}
|
|
||||||
|
|
||||||
import Internal.Grammar.ServerName as ServerName exposing (ServerName)
|
|
||||||
import Internal.Tools.ParserExtra as PE
|
|
||||||
import Parser as P exposing ((|.), (|=), Parser)
|
|
||||||
|
|
||||||
|
|
||||||
{-| The User ID type defining a user.
|
|
||||||
-}
|
|
||||||
type alias UserID =
|
|
||||||
{ localpart : String, domain : ServerName }
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert a Matrix User ID back into its uniquely identifying string.
|
|
||||||
-}
|
|
||||||
fromString : String -> Maybe UserID
|
|
||||||
fromString =
|
|
||||||
P.run (userIdParser |. P.end) >> Result.toMaybe
|
|
||||||
|
|
||||||
|
|
||||||
{-| Return a boolean on whether a Matrix user has a historical user ID.
|
|
||||||
Since this user ID is not SUPPOSED to be legal but clients are nevertheless
|
|
||||||
forced to support them due to backwards compatibility, clients may occasionally
|
|
||||||
attempt to break the rules in an attempt to find undefined behaviour.
|
|
||||||
|
|
||||||
As a result, an explicit method to spot historical users is added to the SDK.
|
|
||||||
|
|
||||||
-}
|
|
||||||
isHistorical : UserID -> Bool
|
|
||||||
isHistorical { localpart } =
|
|
||||||
String.any
|
|
||||||
(\c ->
|
|
||||||
let
|
|
||||||
i : Int
|
|
||||||
i =
|
|
||||||
Char.toCode c
|
|
||||||
in
|
|
||||||
not ((0x61 <= i && i <= 0x7A) || Char.isAlpha c)
|
|
||||||
)
|
|
||||||
localpart
|
|
||||||
|
|
||||||
|
|
||||||
localpartParser : Parser String
|
|
||||||
localpartParser =
|
|
||||||
P.chompIf validHistoricalUsernameChar
|
|
||||||
|> P.getChompedString
|
|
||||||
|> PE.times 1 255
|
|
||||||
|> P.map String.concat
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert a parsed User ID to a string.
|
|
||||||
-}
|
|
||||||
toString : UserID -> String
|
|
||||||
toString { localpart, domain } =
|
|
||||||
String.concat [ "@", localpart, ":", ServerName.toString domain ]
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse a UserID from a string.
|
|
||||||
-}
|
|
||||||
userIdParser : Parser UserID
|
|
||||||
userIdParser =
|
|
||||||
P.succeed UserID
|
|
||||||
|. P.symbol "@"
|
|
||||||
|= localpartParser
|
|
||||||
|. P.symbol ":"
|
|
||||||
|= ServerName.serverNameParser
|
|
||||||
|> PE.maxLength 255
|
|
||||||
|
|
||||||
|
|
||||||
validHistoricalUsernameChar : Char -> Bool
|
|
||||||
validHistoricalUsernameChar c =
|
|
||||||
let
|
|
||||||
i : Int
|
|
||||||
i =
|
|
||||||
Char.toCode c
|
|
||||||
in
|
|
||||||
(0x21 <= i && i <= 0x39) || (0x3B <= i && i <= 0x7E)
|
|
|
@ -4,7 +4,7 @@ module Internal.Tools.Json exposing
|
||||||
, succeed, fail, andThen, lazy, map
|
, succeed, fail, andThen, lazy, map
|
||||||
, Docs(..), RequiredField(..), toDocs
|
, Docs(..), RequiredField(..), toDocs
|
||||||
, list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
|
, list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
|
||||||
, Field, field, parser
|
, Field, field
|
||||||
, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11
|
, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ This section creates objects that can be (re)used in the library's JSON
|
||||||
specification. For this, the user needs to construct fields for the object
|
specification. For this, the user needs to construct fields for the object
|
||||||
first.
|
first.
|
||||||
|
|
||||||
@docs Field, field, parser
|
@docs Field, field
|
||||||
|
|
||||||
Once all fields are constructed, the user can create JSON objects.
|
Once all fields are constructed, the user can create JSON objects.
|
||||||
|
|
||||||
|
@ -74,7 +74,6 @@ import Internal.Tools.DecodeExtra as D
|
||||||
import Internal.Tools.EncodeExtra as E
|
import Internal.Tools.EncodeExtra as E
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
import Parser as P
|
|
||||||
import Set exposing (Set)
|
import Set exposing (Set)
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,7 +158,6 @@ type Docs
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
| DocsOptional Docs
|
| DocsOptional Docs
|
||||||
| DocsParser String
|
|
||||||
| DocsRiskyMap (Descriptive { content : Docs, failure : List String })
|
| DocsRiskyMap (Descriptive { content : Docs, failure : List String })
|
||||||
| DocsSet Docs
|
| DocsSet Docs
|
||||||
| DocsString
|
| DocsString
|
||||||
|
@ -1154,27 +1152,6 @@ object11 { name, description, init } fa fb fc fd fe ff fg fh fi fj fk =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Define a parser that converts a string into a custom Elm type.
|
|
||||||
-}
|
|
||||||
parser : { name : String, p : P.Parser ( a, List Log ), toString : a -> String } -> Coder a
|
|
||||||
parser { name, p, toString } =
|
|
||||||
Coder
|
|
||||||
{ encoder = toString >> E.string
|
|
||||||
, decoder =
|
|
||||||
D.string
|
|
||||||
|> D.andThen
|
|
||||||
(\v ->
|
|
||||||
case P.run p v of
|
|
||||||
Err _ ->
|
|
||||||
D.fail ("Failed to parse " ++ name ++ "!")
|
|
||||||
|
|
||||||
Ok o ->
|
|
||||||
D.succeed o
|
|
||||||
)
|
|
||||||
, docs = DocsParser name
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{-| Define a set.
|
{-| Define a set.
|
||||||
-}
|
-}
|
||||||
set : Coder comparable -> Coder (Set comparable)
|
set : Coder comparable -> Coder (Set comparable)
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
module Internal.Tools.ParserExtra exposing (..)
|
|
||||||
|
|
||||||
import Parser as P exposing ((|.), (|=), Parser)
|
|
||||||
|
|
||||||
|
|
||||||
zeroOrMore : Parser a -> Parser (List a)
|
|
||||||
zeroOrMore parser =
|
|
||||||
P.loop []
|
|
||||||
(\tail ->
|
|
||||||
P.oneOf
|
|
||||||
[ P.succeed (\head -> P.Loop (head :: tail))
|
|
||||||
|= parser
|
|
||||||
, P.succeed (P.Done (List.reverse tail))
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
oneOrMore : Parser a -> Parser (List a)
|
|
||||||
oneOrMore parser =
|
|
||||||
P.succeed (::)
|
|
||||||
|= parser
|
|
||||||
|= zeroOrMore parser
|
|
||||||
|
|
||||||
|
|
||||||
atLeast : Int -> Parser a -> Parser (List a)
|
|
||||||
atLeast n parser =
|
|
||||||
P.loop []
|
|
||||||
(\tail ->
|
|
||||||
if List.length tail < n then
|
|
||||||
P.succeed (\head -> P.Loop (head :: tail))
|
|
||||||
|= parser
|
|
||||||
|
|
||||||
else
|
|
||||||
P.oneOf
|
|
||||||
[ P.succeed (\head -> P.Loop (head :: tail))
|
|
||||||
|= parser
|
|
||||||
, P.succeed (P.Done (List.reverse tail))
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
atMost : Int -> Parser a -> Parser (List a)
|
|
||||||
atMost n parser =
|
|
||||||
P.loop []
|
|
||||||
(\tail ->
|
|
||||||
if List.length tail < n then
|
|
||||||
P.oneOf
|
|
||||||
[ P.succeed (\head -> P.Loop (head :: tail))
|
|
||||||
|= parser
|
|
||||||
, P.succeed (P.Done (List.reverse tail))
|
|
||||||
]
|
|
||||||
|
|
||||||
else
|
|
||||||
P.succeed (P.Done (List.reverse tail))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
times : Int -> Int -> Parser a -> Parser (List a)
|
|
||||||
times inf sup parser =
|
|
||||||
let
|
|
||||||
low : Int
|
|
||||||
low =
|
|
||||||
max 0 (min inf sup)
|
|
||||||
|
|
||||||
high : Int
|
|
||||||
high =
|
|
||||||
max 0 sup
|
|
||||||
in
|
|
||||||
P.loop []
|
|
||||||
(\tail ->
|
|
||||||
if List.length tail < low then
|
|
||||||
P.succeed (\head -> P.Loop (head :: tail))
|
|
||||||
|= parser
|
|
||||||
|
|
||||||
else if List.length tail < high then
|
|
||||||
P.oneOf
|
|
||||||
[ P.succeed (\head -> P.Loop (head :: tail))
|
|
||||||
|= parser
|
|
||||||
, P.succeed (P.Done (List.reverse tail))
|
|
||||||
]
|
|
||||||
|
|
||||||
else
|
|
||||||
P.succeed (P.Done (List.reverse tail))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
exactly : Int -> Parser a -> Parser (List a)
|
|
||||||
exactly n =
|
|
||||||
times n n
|
|
||||||
|
|
||||||
|
|
||||||
maxLength : Int -> Parser a -> Parser a
|
|
||||||
maxLength n parser =
|
|
||||||
P.succeed
|
|
||||||
(\start value end ->
|
|
||||||
if abs (end - start) > n then
|
|
||||||
P.problem "Parsed too much text!"
|
|
||||||
|
|
||||||
else
|
|
||||||
P.succeed value
|
|
||||||
)
|
|
||||||
|= P.getOffset
|
|
||||||
|= parser
|
|
||||||
|= P.getOffset
|
|
||||||
|> P.andThen identity
|
|
|
@ -35,7 +35,6 @@ of a room.
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
||||||
import Internal.Values.User as User exposing (User)
|
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ type alias Event =
|
||||||
, eventId : String
|
, eventId : String
|
||||||
, originServerTs : Timestamp
|
, originServerTs : Timestamp
|
||||||
, roomId : String
|
, roomId : String
|
||||||
, sender : User
|
, sender : String
|
||||||
, stateKey : Maybe String
|
, stateKey : Maybe String
|
||||||
, eventType : String
|
, eventType : String
|
||||||
, unsigned : Maybe UnsignedData
|
, unsigned : Maybe UnsignedData
|
||||||
|
@ -113,7 +112,7 @@ coder =
|
||||||
{ fieldName = "sender"
|
{ fieldName = "sender"
|
||||||
, toField = .sender
|
, toField = .sender
|
||||||
, description = Text.fields.event.sender
|
, description = Text.fields.event.sender
|
||||||
, coder = User.coder
|
, coder = Json.string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.value
|
(Json.field.optional.value
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
module Internal.Values.User exposing
|
|
||||||
( User, toString, fromString
|
|
||||||
, localpart, domain
|
|
||||||
, coder
|
|
||||||
)
|
|
||||||
|
|
||||||
{-| The Matrix user is uniquely identified by their identifier. This User type
|
|
||||||
helps identify and safely handle these strings to transform them into meaningful
|
|
||||||
data types.
|
|
||||||
|
|
||||||
|
|
||||||
## User
|
|
||||||
|
|
||||||
@docs User, toString, fromString
|
|
||||||
|
|
||||||
|
|
||||||
## Divide
|
|
||||||
|
|
||||||
Matrix users are identified by their unique ID. In the Matrix API, this is a
|
|
||||||
string that looks as follows:
|
|
||||||
|
|
||||||
@alice:example.org
|
|
||||||
\---/ \---------/
|
|
||||||
| |
|
|
||||||
| |
|
|
||||||
localpart domain
|
|
||||||
|
|
||||||
Since the username is safely parsed, one can get these parts of the username.
|
|
||||||
|
|
||||||
@docs localpart, domain
|
|
||||||
|
|
||||||
|
|
||||||
## JSON
|
|
||||||
|
|
||||||
@docs coder
|
|
||||||
|
|
||||||
-}
|
|
||||||
|
|
||||||
import Internal.Config.Log as Log exposing (log)
|
|
||||||
import Internal.Grammar.ServerName as ServerName
|
|
||||||
import Internal.Grammar.UserId as UserId
|
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
import Parser as P
|
|
||||||
|
|
||||||
|
|
||||||
{-| The Matrix user represents a user across multiple Matrix rooms.
|
|
||||||
-}
|
|
||||||
type alias User =
|
|
||||||
UserId.UserID
|
|
||||||
|
|
||||||
|
|
||||||
{-| Define a method to encode/decode Matrix users.
|
|
||||||
-}
|
|
||||||
coder : Json.Coder User
|
|
||||||
coder =
|
|
||||||
Json.parser
|
|
||||||
{ name = "Username"
|
|
||||||
, p =
|
|
||||||
P.andThen
|
|
||||||
(\name ->
|
|
||||||
P.succeed
|
|
||||||
( name
|
|
||||||
, if UserId.isHistorical name then
|
|
||||||
[ log.warn "Historical user found"
|
|
||||||
]
|
|
||||||
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
UserId.userIdParser
|
|
||||||
, toString = UserId.toString
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{-| The domain represents the Matrix homeserver controlling this user. It also
|
|
||||||
offers other Matrix homeservers an indication of where to look if you wish to
|
|
||||||
send a message to this user.
|
|
||||||
-}
|
|
||||||
domain : User -> String
|
|
||||||
domain =
|
|
||||||
.domain >> ServerName.toString
|
|
||||||
|
|
||||||
|
|
||||||
{-| Parse a string and convert it into a User, if formatted properly.
|
|
||||||
-}
|
|
||||||
fromString : String -> Maybe User
|
|
||||||
fromString =
|
|
||||||
UserId.fromString
|
|
||||||
|
|
||||||
|
|
||||||
{-| The localpart is similar to a username, in the sense that every user has
|
|
||||||
their own localpart. The localpart is not unique across multiple servers,
|
|
||||||
however! There can be a user @alice:example.com and a user @alice:example.org in
|
|
||||||
a room at the same time.
|
|
||||||
-}
|
|
||||||
localpart : User -> String
|
|
||||||
localpart =
|
|
||||||
.localpart
|
|
||||||
|
|
||||||
|
|
||||||
{-| Convert a user into its unique identifier string value.
|
|
||||||
-}
|
|
||||||
toString : User -> String
|
|
||||||
toString =
|
|
||||||
UserId.toString
|
|
|
@ -122,10 +122,9 @@ roomId (Event event) =
|
||||||
|
|
||||||
{-| Determine the fully-qualified ID of the user who sent an event.
|
{-| Determine the fully-qualified ID of the user who sent an event.
|
||||||
-}
|
-}
|
||||||
sender : Event -> Types.User
|
sender : Event -> String
|
||||||
sender (Event event) =
|
sender (Event event) =
|
||||||
Envelope.map .sender event
|
Envelope.extract .sender event
|
||||||
|> Types.User
|
|
||||||
|
|
||||||
|
|
||||||
{-| Determine an event's state key.
|
{-| Determine an event's state key.
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
module Matrix.User exposing
|
|
||||||
( User, toString
|
|
||||||
, localpart, domain
|
|
||||||
, get
|
|
||||||
)
|
|
||||||
|
|
||||||
{-| Matrix users are identified by their unique ID. In the Matrix API, this is a
|
|
||||||
string that looks as follows:
|
|
||||||
|
|
||||||
@alice:example.org
|
|
||||||
\---/ \---------/
|
|
||||||
| |
|
|
||||||
| |
|
|
||||||
localpart domain
|
|
||||||
|
|
||||||
Since it is very easy to abuse Matrix user IDs to sneak in arbitrary values,
|
|
||||||
the Elm SDK parses them and makes sure they are safe. As a result, you might
|
|
||||||
need this module to get the right information from a user!
|
|
||||||
|
|
||||||
|
|
||||||
## User
|
|
||||||
|
|
||||||
@docs User, toString
|
|
||||||
|
|
||||||
|
|
||||||
## Info
|
|
||||||
|
|
||||||
Sometimes, you are more interested in the username itself. These functions can
|
|
||||||
help you decipher, disambiguate and categorize users based on their username.
|
|
||||||
|
|
||||||
@docs localpart, domain
|
|
||||||
|
|
||||||
|
|
||||||
## Manipulate
|
|
||||||
|
|
||||||
@docs get
|
|
||||||
|
|
||||||
-}
|
|
||||||
|
|
||||||
import Internal.Values.Envelope as Envelope
|
|
||||||
import Internal.Values.User as Internal
|
|
||||||
import Types exposing (User(..))
|
|
||||||
|
|
||||||
|
|
||||||
{-| The User type represents a Matrix user.
|
|
||||||
|
|
||||||
It contains information like:
|
|
||||||
|
|
||||||
- Their username on Matrix
|
|
||||||
- The server that hosts their account
|
|
||||||
- Access tokens needed to talk to the server
|
|
||||||
|
|
||||||
It does **NOT** contain information like:
|
|
||||||
|
|
||||||
- Their nickname
|
|
||||||
- Their profile picture
|
|
||||||
- Your private room with them
|
|
||||||
|
|
||||||
You can get all that information by looking it up in the [Vault](Matrix#Vault).
|
|
||||||
|
|
||||||
**Note:** Please do not store this user type as a variable in your model! You
|
|
||||||
should always maintain a single source of truth in Elm, and the User type
|
|
||||||
contains various credentials and API tokens that might expire if you don't
|
|
||||||
update them from the Vault.
|
|
||||||
|
|
||||||
If you need to remember specific users, you can best compare their identifying
|
|
||||||
string using [toString](Matrix-User#toString) or you can use
|
|
||||||
[get](Matrix-User#get) with the Vault to get the user type.
|
|
||||||
|
|
||||||
-}
|
|
||||||
type alias User =
|
|
||||||
Types.User
|
|
||||||
|
|
||||||
|
|
||||||
{-| The domain is the name of the server that the user connects to. Server names
|
|
||||||
are case-sensitive, so if the strings are equal, the users are on the same
|
|
||||||
server!
|
|
||||||
|
|
||||||
As a result, you can use the user domain for:
|
|
||||||
|
|
||||||
- When multiple users in a room have the same localpart on different servers
|
|
||||||
- Finding other users from a potentially malicious homeserver
|
|
||||||
- Counting homeservers in a room
|
|
||||||
|
|
||||||
See the following examples:
|
|
||||||
|
|
||||||
domain (get vault "@alice:example.org") -- "example.org"
|
|
||||||
|
|
||||||
domain (get vault "@bob:127.0.0.1") -- "127.0.0.1"
|
|
||||||
|
|
||||||
domain (get vault "@charlie:[2001:db8::]") -- "[2001:db8::]"
|
|
||||||
|
|
||||||
-}
|
|
||||||
domain : User -> String
|
|
||||||
domain (User user) =
|
|
||||||
Envelope.extract Internal.domain user
|
|
||||||
|
|
||||||
|
|
||||||
{-| Get a specific user by their unique identifier.
|
|
||||||
|
|
||||||
The Vault is needed as an input because the `User` type also stores various
|
|
||||||
credentials needed to talk to the Matrix API.
|
|
||||||
|
|
||||||
get vault "@alice:example.org" -- Just (User "alice" "example.org")
|
|
||||||
|
|
||||||
get vault "@bob:127.0.0.1" -- Just (User "bob" "127.0.0.1")
|
|
||||||
|
|
||||||
get vault "@charlie:[2001:db8::]" -- Just (User "charlie" "2001:db8::")
|
|
||||||
|
|
||||||
get vault "@evil:#mp#ss#bl#.c#m" -- Nothing
|
|
||||||
|
|
||||||
get vault "" -- Nothing
|
|
||||||
|
|
||||||
-}
|
|
||||||
get : Types.Vault -> String -> Maybe User
|
|
||||||
get (Types.Vault vault) username =
|
|
||||||
Envelope.mapMaybe (\_ -> Internal.fromString username) vault
|
|
||||||
|> Maybe.map Types.User
|
|
||||||
|
|
||||||
|
|
||||||
{-| The localpart is the user's unique username. Every homeserver has their own
|
|
||||||
username registry, so you might occasionally find distinct users with the same
|
|
||||||
localpart.
|
|
||||||
|
|
||||||
The localpart is often used as a user's name in a room if they haven't set up
|
|
||||||
a custom name.
|
|
||||||
|
|
||||||
See the following examples:
|
|
||||||
|
|
||||||
localpart (get vault "@alice:example.org") -- "alice"
|
|
||||||
|
|
||||||
localpart (get vault "@bob:127.0.0.1") -- "bob"
|
|
||||||
|
|
||||||
localpart (get vault "@charlie:[2001:db8::]") -- "charlie"
|
|
||||||
|
|
||||||
-}
|
|
||||||
localpart : User -> String
|
|
||||||
localpart (User user) =
|
|
||||||
Envelope.extract Internal.localpart user
|
|
||||||
|
|
||||||
|
|
||||||
{-| Get the uniquely identifying string for this user. Since the strings are
|
|
||||||
case-sensitive, you can run a simple string comparison to compare usernames.
|
|
||||||
-}
|
|
||||||
toString : User -> String
|
|
||||||
toString (User user) =
|
|
||||||
Envelope.extract Internal.toString user
|
|
|
@ -1,4 +1,4 @@
|
||||||
module Types exposing (Vault(..), Event(..), User(..))
|
module Types exposing (Vault(..), Event(..))
|
||||||
|
|
||||||
{-| The Elm SDK uses a lot of records and values that are easy to manipulate.
|
{-| 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)
|
Yet, the [Elm design guidelines](https://package.elm-lang.org/help/design-guidelines#keep-tags-and-record-constructors-secret)
|
||||||
|
@ -12,13 +12,12 @@ access their content directly.
|
||||||
The opaque types are placed in a central module so all exposed modules can
|
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.
|
safely access all exposed data types without risking to create circular imports.
|
||||||
|
|
||||||
@docs Vault, Event, User
|
@docs Vault, Event
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import Internal.Values.Envelope as Envelope
|
import Internal.Values.Envelope as Envelope
|
||||||
import Internal.Values.Event as Event
|
import Internal.Values.Event as Event
|
||||||
import Internal.Values.User as User
|
|
||||||
import Internal.Values.Vault as Vault
|
import Internal.Values.Vault as Vault
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,12 +27,6 @@ type Event
|
||||||
= Event (Envelope.Envelope Event.Event)
|
= Event (Envelope.Envelope Event.Event)
|
||||||
|
|
||||||
|
|
||||||
{-| Opaque type for Matrix User
|
|
||||||
-}
|
|
||||||
type User
|
|
||||||
= User (Envelope.Envelope User.User)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Opaque type for Matrix Vault
|
{-| Opaque type for Matrix Vault
|
||||||
-}
|
-}
|
||||||
type Vault
|
type Vault
|
||||||
|
|
|
@ -3,7 +3,6 @@ module Test.Filter.Timeline exposing (..)
|
||||||
import Expect
|
import Expect
|
||||||
import Fuzz exposing (Fuzzer)
|
import Fuzz exposing (Fuzzer)
|
||||||
import Internal.Filter.Timeline as Filter exposing (Filter)
|
import Internal.Filter.Timeline as Filter exposing (Filter)
|
||||||
import Internal.Grammar.UserId as U
|
|
||||||
import Internal.Values.Event as Event
|
import Internal.Values.Event as Event
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
|
@ -87,7 +86,7 @@ suite =
|
||||||
"Only event sender filter matches"
|
"Only event sender filter matches"
|
||||||
(\event ->
|
(\event ->
|
||||||
event
|
event
|
||||||
|> Filter.match (Filter.onlySenders [ U.toString event.sender ])
|
|> Filter.match (Filter.onlySenders [ event.sender ])
|
||||||
|> Expect.equal True
|
|> Expect.equal True
|
||||||
)
|
)
|
||||||
, fuzz TestEvent.fuzzer
|
, fuzz TestEvent.fuzzer
|
||||||
|
@ -101,7 +100,7 @@ suite =
|
||||||
"Not event sender filter doesn't match"
|
"Not event sender filter doesn't match"
|
||||||
(\event ->
|
(\event ->
|
||||||
event
|
event
|
||||||
|> Filter.match (Filter.allSendersExcept [ U.toString event.sender ])
|
|> Filter.match (Filter.allSendersExcept [ event.sender ])
|
||||||
|> Expect.equal False
|
|> Expect.equal False
|
||||||
)
|
)
|
||||||
, fuzz2 TestEvent.fuzzer
|
, fuzz2 TestEvent.fuzzer
|
||||||
|
@ -110,7 +109,7 @@ suite =
|
||||||
(\event senders ->
|
(\event senders ->
|
||||||
event
|
event
|
||||||
|> Filter.match (Filter.onlySenders senders)
|
|> Filter.match (Filter.onlySenders senders)
|
||||||
|> Expect.equal (List.member (U.toString event.sender) senders)
|
|> Expect.equal (List.member event.sender senders)
|
||||||
)
|
)
|
||||||
, fuzz2 TestEvent.fuzzer
|
, fuzz2 TestEvent.fuzzer
|
||||||
(Fuzz.list Fuzz.string)
|
(Fuzz.list Fuzz.string)
|
||||||
|
@ -126,7 +125,7 @@ suite =
|
||||||
(\event senders ->
|
(\event senders ->
|
||||||
event
|
event
|
||||||
|> Filter.match (Filter.allSendersExcept senders)
|
|> Filter.match (Filter.allSendersExcept senders)
|
||||||
|> Expect.notEqual (List.member (U.toString event.sender) senders)
|
|> Expect.notEqual (List.member event.sender senders)
|
||||||
)
|
)
|
||||||
, fuzz2 TestEvent.fuzzer
|
, fuzz2 TestEvent.fuzzer
|
||||||
(Fuzz.list Fuzz.string)
|
(Fuzz.list Fuzz.string)
|
||||||
|
@ -303,7 +302,7 @@ suite =
|
||||||
l2 =
|
l2 =
|
||||||
List.filter
|
List.filter
|
||||||
(\e ->
|
(\e ->
|
||||||
List.member (U.toString e.sender) senders
|
List.member e.sender senders
|
||||||
&& List.member e.eventType types
|
&& List.member e.eventType types
|
||||||
)
|
)
|
||||||
events
|
events
|
||||||
|
@ -337,8 +336,8 @@ suite =
|
||||||
l2 =
|
l2 =
|
||||||
List.filter
|
List.filter
|
||||||
(\e ->
|
(\e ->
|
||||||
List.member (U.toString e.sender) senders
|
List.member e.sender senders
|
||||||
&& (not <| List.member (U.toString e.sender) types)
|
&& (not <| List.member e.eventType types)
|
||||||
)
|
)
|
||||||
events
|
events
|
||||||
in
|
in
|
||||||
|
@ -371,7 +370,7 @@ suite =
|
||||||
l2 =
|
l2 =
|
||||||
List.filter
|
List.filter
|
||||||
(\e ->
|
(\e ->
|
||||||
(not <| List.member (U.toString e.sender) senders)
|
(not <| List.member e.sender senders)
|
||||||
&& List.member e.eventType types
|
&& List.member e.eventType types
|
||||||
)
|
)
|
||||||
events
|
events
|
||||||
|
@ -405,7 +404,7 @@ suite =
|
||||||
l2 =
|
l2 =
|
||||||
List.filter
|
List.filter
|
||||||
(\e ->
|
(\e ->
|
||||||
(not <| List.member (U.toString e.sender) senders)
|
(not <| List.member e.sender senders)
|
||||||
&& (not <| List.member e.eventType types)
|
&& (not <| List.member e.eventType types)
|
||||||
)
|
)
|
||||||
events
|
events
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
module Test.Grammar.ServerName exposing (..)
|
|
||||||
|
|
||||||
import Expect
|
|
||||||
import Fuzz exposing (Fuzzer)
|
|
||||||
import Internal.Grammar.ServerName as SN
|
|
||||||
import Test exposing (..)
|
|
||||||
|
|
||||||
|
|
||||||
dnsFuzzer : Fuzzer String
|
|
||||||
dnsFuzzer =
|
|
||||||
Fuzz.map2
|
|
||||||
(\head tail ->
|
|
||||||
String.fromList (head :: tail)
|
|
||||||
)
|
|
||||||
("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
||||||
|> String.toList
|
|
||||||
|> Fuzz.oneOfValues
|
|
||||||
)
|
|
||||||
("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-."
|
|
||||||
|> String.toList
|
|
||||||
|> Fuzz.oneOfValues
|
|
||||||
|> Fuzz.listOfLengthBetween 0 (255 - 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
hostnameFuzzer : Fuzzer String
|
|
||||||
hostnameFuzzer =
|
|
||||||
Fuzz.oneOf
|
|
||||||
[ dnsFuzzer
|
|
||||||
, ipv4Fuzzer
|
|
||||||
, Fuzz.map (\x -> "[" ++ x ++ "]") ipv6Fuzzer
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
ipv4Fuzzer : Fuzzer String
|
|
||||||
ipv4Fuzzer =
|
|
||||||
Fuzz.intRange 0 255
|
|
||||||
|> Fuzz.listOfLength 4
|
|
||||||
|> Fuzz.map
|
|
||||||
(List.map String.fromInt
|
|
||||||
>> List.intersperse "."
|
|
||||||
>> String.concat
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ipv6Fuzzer : Fuzzer String
|
|
||||||
ipv6Fuzzer =
|
|
||||||
let
|
|
||||||
num : Fuzzer String
|
|
||||||
num =
|
|
||||||
"0123456789abcdefABCDEF"
|
|
||||||
|> String.toList
|
|
||||||
|> Fuzz.oneOfValues
|
|
||||||
|> Fuzz.listOfLength 4
|
|
||||||
|> Fuzz.map String.fromList
|
|
||||||
in
|
|
||||||
Fuzz.oneOf
|
|
||||||
[ Fuzz.listOfLength 8 num
|
|
||||||
|> Fuzz.map (List.intersperse ":")
|
|
||||||
|> Fuzz.map String.concat
|
|
||||||
, Fuzz.listOfLengthBetween 0 7 num
|
|
||||||
|> Fuzz.andThen
|
|
||||||
(\front ->
|
|
||||||
num
|
|
||||||
|> Fuzz.listOfLengthBetween 0 (8 - 1 - List.length front)
|
|
||||||
|> Fuzz.map
|
|
||||||
(\back ->
|
|
||||||
[ front
|
|
||||||
|> List.intersperse ":"
|
|
||||||
, [ "::" ]
|
|
||||||
, back
|
|
||||||
|> List.intersperse ":"
|
|
||||||
]
|
|
||||||
|> List.concat
|
|
||||||
|> String.concat
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
portFuzzer : Fuzzer String
|
|
||||||
portFuzzer =
|
|
||||||
Fuzz.oneOf
|
|
||||||
[ Fuzz.constant ""
|
|
||||||
, Fuzz.intRange 0 65535
|
|
||||||
|> Fuzz.map (\p -> ":" ++ String.fromInt p)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
serverNameFuzzer : Fuzzer String
|
|
||||||
serverNameFuzzer =
|
|
||||||
Fuzz.map2 (++) hostnameFuzzer portFuzzer
|
|
||||||
|
|
||||||
|
|
||||||
suite : Test
|
|
||||||
suite =
|
|
||||||
describe "Server name tests"
|
|
||||||
[ describe "Checking correct values"
|
|
||||||
[ fuzz serverNameFuzzer
|
|
||||||
"Correct server names validate"
|
|
||||||
(\server ->
|
|
||||||
SN.fromString server
|
|
||||||
|> Maybe.map SN.toString
|
|
||||||
|> Expect.equal (Just server)
|
|
||||||
)
|
|
||||||
, test "Checking spec examples"
|
|
||||||
(\() ->
|
|
||||||
let
|
|
||||||
examples : List String
|
|
||||||
examples =
|
|
||||||
[ "matrix.org"
|
|
||||||
, "matrix.org:8888"
|
|
||||||
, "1.2.3.4"
|
|
||||||
, "1.2.3.4:1234"
|
|
||||||
, "[1234:5678::abcd]"
|
|
||||||
, "[1234:5678::abcd]:5678"
|
|
||||||
]
|
|
||||||
in
|
|
||||||
examples
|
|
||||||
|> List.map SN.fromString
|
|
||||||
|> List.map ((/=) Nothing)
|
|
||||||
|> Expect.equalLists
|
|
||||||
(List.repeat (List.length examples) True)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
|
@ -1,159 +0,0 @@
|
||||||
module Test.Grammar.UserId exposing (..)
|
|
||||||
|
|
||||||
import Expect
|
|
||||||
import Fuzz exposing (Fuzzer)
|
|
||||||
import Internal.Grammar.ServerName as SN
|
|
||||||
import Internal.Grammar.UserId as U
|
|
||||||
import Test exposing (..)
|
|
||||||
import Test.Grammar.ServerName as ServerName
|
|
||||||
|
|
||||||
|
|
||||||
modernUserCharFuzzer : Fuzzer Char
|
|
||||||
modernUserCharFuzzer =
|
|
||||||
Fuzz.oneOf
|
|
||||||
[ Fuzz.intRange 0x61 0x7A
|
|
||||||
|> Fuzz.map Char.fromCode
|
|
||||||
, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
|> String.toList
|
|
||||||
|> Fuzz.oneOfValues
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
historicalUserCharFuzzer : Fuzzer Char
|
|
||||||
historicalUserCharFuzzer =
|
|
||||||
[ ( 0x21, 0x39 ), ( 0x3B, 0x7E ) ]
|
|
||||||
|> List.map (\( low, high ) -> Fuzz.intRange low high)
|
|
||||||
|> Fuzz.oneOf
|
|
||||||
|> Fuzz.map Char.fromCode
|
|
||||||
|
|
||||||
|
|
||||||
modernUserFuzzer : Fuzzer String
|
|
||||||
modernUserFuzzer =
|
|
||||||
Fuzz.map2
|
|
||||||
(\localpart domain ->
|
|
||||||
let
|
|
||||||
maxLocalSize : Int
|
|
||||||
maxLocalSize =
|
|
||||||
255 - String.length domain - 2
|
|
||||||
in
|
|
||||||
localpart
|
|
||||||
|> List.take maxLocalSize
|
|
||||||
|> String.fromList
|
|
||||||
|> (\l -> "@" ++ l ++ ":" ++ domain)
|
|
||||||
)
|
|
||||||
(Fuzz.listOfLengthBetween 1 255 modernUserCharFuzzer)
|
|
||||||
(ServerName.serverNameFuzzer
|
|
||||||
|> Fuzz.filter
|
|
||||||
(\name ->
|
|
||||||
String.length name < 255 - 2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
historicalUserFuzzer : Fuzzer String
|
|
||||||
historicalUserFuzzer =
|
|
||||||
Fuzz.map2
|
|
||||||
(\localpart domain ->
|
|
||||||
let
|
|
||||||
maxLocalSize : Int
|
|
||||||
maxLocalSize =
|
|
||||||
255 - String.length domain - 2
|
|
||||||
in
|
|
||||||
localpart
|
|
||||||
|> List.take maxLocalSize
|
|
||||||
|> String.fromList
|
|
||||||
|> (\l -> "@" ++ l ++ ":" ++ domain)
|
|
||||||
)
|
|
||||||
(Fuzz.listOfLengthBetween 1 255 historicalUserCharFuzzer)
|
|
||||||
(ServerName.serverNameFuzzer
|
|
||||||
|> Fuzz.filter
|
|
||||||
(\name ->
|
|
||||||
String.length name < 255 - 2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
userFuzzer : Fuzzer String
|
|
||||||
userFuzzer =
|
|
||||||
Fuzz.oneOf [ modernUserFuzzer, historicalUserFuzzer ]
|
|
||||||
|
|
||||||
|
|
||||||
fullUserFuzzer : Fuzzer U.UserID
|
|
||||||
fullUserFuzzer =
|
|
||||||
userFuzzer
|
|
||||||
|> Fuzz.map U.fromString
|
|
||||||
|> Fuzz.map (Maybe.withDefault { localpart = "a", domain = { host = SN.DNS "a", port_ = Nothing } })
|
|
||||||
|
|
||||||
|
|
||||||
suite : Test
|
|
||||||
suite =
|
|
||||||
describe "UserId"
|
|
||||||
[ describe "Size"
|
|
||||||
[ fuzz ServerName.serverNameFuzzer
|
|
||||||
"Username cannot be length 0"
|
|
||||||
(\domain ->
|
|
||||||
"@"
|
|
||||||
++ ":"
|
|
||||||
++ domain
|
|
||||||
|> U.fromString
|
|
||||||
|> Expect.equal Nothing
|
|
||||||
)
|
|
||||||
, fuzz2 (Fuzz.listOfLengthBetween 1 255 historicalUserCharFuzzer)
|
|
||||||
ServerName.serverNameFuzzer
|
|
||||||
"Username length cannot exceed 255"
|
|
||||||
(\localpart domain ->
|
|
||||||
let
|
|
||||||
username : String
|
|
||||||
username =
|
|
||||||
"@"
|
|
||||||
++ String.fromList localpart
|
|
||||||
++ ":"
|
|
||||||
++ domain
|
|
||||||
in
|
|
||||||
Expect.equal
|
|
||||||
(U.fromString username == Nothing)
|
|
||||||
(String.length username > 255)
|
|
||||||
)
|
|
||||||
, fuzz modernUserFuzzer
|
|
||||||
"Modern fuzzer has appropriate size"
|
|
||||||
(String.length >> Expect.lessThan 256)
|
|
||||||
, fuzz historicalUserFuzzer
|
|
||||||
"Historical fuzzer has appropriate size"
|
|
||||||
(String.length >> Expect.lessThan 256)
|
|
||||||
, fuzz userFuzzer
|
|
||||||
"User fuzzers have appropriate size"
|
|
||||||
(String.length >> Expect.lessThan 256)
|
|
||||||
]
|
|
||||||
, describe "From string evaluation"
|
|
||||||
[ fuzz userFuzzer
|
|
||||||
"fromString always returns a value on fuzzer"
|
|
||||||
(U.fromString >> Expect.notEqual Nothing)
|
|
||||||
, fuzz userFuzzer
|
|
||||||
"fromString -> toString returns the same value"
|
|
||||||
(\username ->
|
|
||||||
username
|
|
||||||
|> U.fromString
|
|
||||||
|> Maybe.map U.toString
|
|
||||||
|> Expect.equal (Just username)
|
|
||||||
)
|
|
||||||
|
|
||||||
-- Not always True
|
|
||||||
-- TODO: Define a fitting fuzzer for this test
|
|
||||||
-- , fuzz historicalUserFuzzer
|
|
||||||
-- "Historical users are historical"
|
|
||||||
-- (\username ->
|
|
||||||
-- username
|
|
||||||
-- |> U.fromString
|
|
||||||
-- |> Maybe.map U.isHistorical
|
|
||||||
-- |> Expect.equal (Just True)
|
|
||||||
-- )
|
|
||||||
, fuzz modernUserFuzzer
|
|
||||||
"Modern users are not historical"
|
|
||||||
(\username ->
|
|
||||||
username
|
|
||||||
|> U.fromString
|
|
||||||
|> Maybe.map U.isHistorical
|
|
||||||
|> Expect.equal (Just False)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
|
@ -115,7 +115,7 @@ suite =
|
||||||
(\event ->
|
(\event ->
|
||||||
Hashdict.singleton .eventId event
|
Hashdict.singleton .eventId event
|
||||||
|> Hashdict.remove event
|
|> Hashdict.remove event
|
||||||
|> Hashdict.isEqual (Hashdict.empty .roomId)
|
|> Hashdict.isEqual (Hashdict.empty .sender)
|
||||||
|> Expect.equal True
|
|> Expect.equal True
|
||||||
)
|
)
|
||||||
, fuzz TestEvent.fuzzer
|
, fuzz TestEvent.fuzzer
|
||||||
|
@ -123,7 +123,7 @@ suite =
|
||||||
(\event ->
|
(\event ->
|
||||||
Hashdict.singleton .eventId event
|
Hashdict.singleton .eventId event
|
||||||
|> Hashdict.removeKey event.eventId
|
|> Hashdict.removeKey event.eventId
|
||||||
|> Hashdict.isEqual (Hashdict.empty .roomId)
|
|> Hashdict.isEqual (Hashdict.empty .sender)
|
||||||
|> Expect.equal True
|
|> Expect.equal True
|
||||||
)
|
)
|
||||||
, fuzz TestEvent.fuzzer
|
, fuzz TestEvent.fuzzer
|
||||||
|
@ -168,8 +168,8 @@ suite =
|
||||||
|> Json.encode (Hashdict.coder .eventId Event.coder)
|
|> Json.encode (Hashdict.coder .eventId Event.coder)
|
||||||
|> E.encode indent
|
|> E.encode indent
|
||||||
|> D.decodeString (Json.decode <| Hashdict.coder .eventId Event.coder)
|
|> D.decodeString (Json.decode <| Hashdict.coder .eventId Event.coder)
|
||||||
|> Result.map (Tuple.first >> Hashdict.toList)
|
|> Result.map (Tuple.mapFirst Hashdict.toList)
|
||||||
|> Expect.equal (Ok (Hashdict.toList hashdict))
|
|> Expect.equal (Ok ( Hashdict.toList hashdict, [] ))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -198,8 +198,8 @@ suite =
|
||||||
|> Json.encode (Mashdict.coder .stateKey Event.coder)
|
|> Json.encode (Mashdict.coder .stateKey Event.coder)
|
||||||
|> E.encode indent
|
|> E.encode indent
|
||||||
|> D.decodeString (Json.decode <| Mashdict.coder .stateKey Event.coder)
|
|> D.decodeString (Json.decode <| Mashdict.coder .stateKey Event.coder)
|
||||||
|> Result.map (Tuple.first >> Mashdict.toList)
|
|> Result.map (Tuple.mapFirst Mashdict.toList)
|
||||||
|> Expect.equal (Ok (Mashdict.toList hashdict))
|
|> Expect.equal (Ok ( Mashdict.toList hashdict, [] ))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,7 +5,6 @@ import Fuzz exposing (Fuzzer)
|
||||||
import Internal.Values.Event as Event exposing (Event)
|
import Internal.Values.Event as Event exposing (Event)
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
import Test exposing (..)
|
import Test exposing (..)
|
||||||
import Test.Grammar.UserId as UserId
|
|
||||||
import Test.Tools.Timestamp as TestTimestamp
|
import Test.Tools.Timestamp as TestTimestamp
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ fuzzer =
|
||||||
Fuzz.string
|
Fuzz.string
|
||||||
TestTimestamp.fuzzer
|
TestTimestamp.fuzzer
|
||||||
Fuzz.string
|
Fuzz.string
|
||||||
UserId.fullUserFuzzer
|
Fuzz.string
|
||||||
(Fuzz.maybe Fuzz.string)
|
(Fuzz.maybe Fuzz.string)
|
||||||
Fuzz.string
|
Fuzz.string
|
||||||
(Fuzz.maybe unsignedDataFuzzer)
|
(Fuzz.maybe unsignedDataFuzzer)
|
||||||
|
|
Loading…
Reference in New Issue