Add status code errors

pull/1/head
Bram van den Heuvel 2023-04-18 14:55:11 +02:00
parent 3f4508d07c
commit 4aaabe3a0a
13 changed files with 221 additions and 30 deletions

View File

@ -1,6 +1,7 @@
module Internal.Api.Ban.Api exposing (..)
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Json.Decode as D
@ -26,6 +27,7 @@ banV1 { reason, roomId, userId } =
, R.replaceInUrl "roomId" roomId
, R.bodyOpString "reason" reason
, R.bodyString "user_id" userId
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.banNotAllowed })
]
>> R.toTask (D.map (always ()) D.value)
@ -38,5 +40,6 @@ banV2 { reason, roomId, userId } =
, R.replaceInUrl "roomId" roomId
, R.bodyOpString "reason" reason
, R.bodyString "user_id" userId
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.banNotAllowed })
]
>> R.toTask (D.map (always ()) D.value)

View File

@ -2,6 +2,7 @@ module Internal.Api.GetEvent.Api exposing (..)
import Internal.Api.GetEvent.V1.SpecObjects as SO1
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context as Context exposing (Context)
import Internal.Tools.Exceptions as X
import Task exposing (Task)
@ -24,5 +25,6 @@ getEventInputV1 data context =
[ R.accessToken
, R.replaceInUrl "eventId" (Context.getSentEvent context)
, R.replaceInUrl "roomId" data.roomId
, R.onStatusCode 404 (X.M_NOT_FOUND { error = Just SE.eventNotFound })
]
|> R.toTask SO1.clientEventDecoder

View File

@ -5,6 +5,7 @@ import Internal.Api.GetMessages.V2.SpecObjects as SO2
import Internal.Api.GetMessages.V3.SpecObjects as SO3
import Internal.Api.GetMessages.V4.SpecObjects as SO4
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Internal.Tools.SpecEnums as Enums
@ -75,6 +76,7 @@ getMessagesV1 { direction, from, limit, roomId } =
, R.queryString "dir" (Enums.fromEventOrder direction)
, R.queryString "from" f
, R.queryOpInt "limit" limit
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO1.messagesResponseDecoder
@ -94,6 +96,7 @@ getMessagesV2 { direction, from, limit, roomId, to } =
, R.queryString "from" f
, R.queryOpInt "limit" limit
, R.queryOpString "to" to
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO1.messagesResponseDecoder
@ -114,6 +117,7 @@ getMessagesV3 { direction, filter, from, limit, roomId, to } =
, R.queryOpInt "limit" limit
, R.queryOpString "filter" filter
, R.queryOpString "to" to
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO1.messagesResponseDecoder
@ -134,6 +138,7 @@ getMessagesV4 { direction, filter, from, limit, roomId, to } =
, R.queryOpInt "limit" limit
, R.queryOpString "filter" filter
, R.queryOpString "to" to
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO2.messagesResponseDecoder
@ -154,6 +159,7 @@ getMessagesV5 { direction, filter, from, limit, roomId, to } =
, R.queryOpInt "limit" limit
, R.queryOpString "filter" filter
, R.queryOpString "to" to
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO3.messagesResponseDecoder
@ -174,6 +180,7 @@ getMessagesV6 { direction, filter, from, limit, roomId, to } =
, R.queryOpInt "limit" limit
, R.queryOpString "filter" filter
, R.queryOpString "to" to
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO3.messagesResponseDecoder
@ -194,6 +201,7 @@ getMessagesV7 { direction, filter, from, limit, roomId, to } =
, R.queryOpInt "limit" limit
, R.queryOpString "filter" filter
, R.queryOpString "to" to
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO4.messagesResponseDecoder
@ -212,5 +220,6 @@ getMessagesV8 { direction, filter, from, limit, roomId, to } =
, R.queryOpInt "limit" limit
, R.queryOpString "filter" filter
, R.queryOpString "to" to
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO4.messagesResponseDecoder

View File

@ -1,6 +1,7 @@
module Internal.Api.Invite.Api exposing (..)
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Json.Decode as D
@ -31,6 +32,7 @@ inviteV1 { roomId, userId } =
[ R.accessToken
, R.replaceInUrl "roomId" roomId
, R.bodyString "user_id" userId
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.inviteNotAllowed })
]
>> R.toTask (D.map (always ()) D.value)
@ -43,5 +45,8 @@ inviteV2 { reason, roomId, userId } =
, R.replaceInUrl "roomId" roomId
, R.bodyString "user_id" userId
, R.bodyOpString "reason" reason
, R.onStatusCode 400 (X.M_INVALID_PARAM { error = Just SE.invalidRequest })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.inviteNotAllowed })
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask (D.map (always ()) D.value)

View File

@ -1,6 +1,7 @@
module Internal.Api.JoinRoomById.Api exposing (..)
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context, VBA)
import Internal.Tools.Exceptions as X
import Json.Decode as D
@ -25,6 +26,8 @@ joinRoomByIdV1 { roomId } =
>> R.withAttributes
[ R.accessToken
, R.replaceInUrl "roomId" roomId
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.joinNotAllowed })
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask (D.map (\r -> { roomId = r }) (D.field "room_id" D.string))
@ -36,5 +39,7 @@ joinRoomByIdV2 { roomId, reason } =
[ R.accessToken
, R.replaceInUrl "roomId" roomId
, R.bodyOpString "reason" reason
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.joinNotAllowed })
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask (D.map (\r -> { roomId = r }) (D.field "room_id" D.string))

View File

@ -2,6 +2,7 @@ module Internal.Api.JoinedMembers.Api exposing (..)
import Internal.Api.JoinedMembers.V1.SpecObjects as SO1
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Task exposing (Task)
@ -22,6 +23,7 @@ joinedMembersV1 { roomId } =
>> R.withAttributes
[ R.accessToken
, R.replaceInUrl "roomId" roomId
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO1.roomMemberListDecoder
@ -32,5 +34,6 @@ joinedMembersV2 { roomId } =
>> R.withAttributes
[ R.accessToken
, R.replaceInUrl "roomId" roomId
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.notInRoom })
]
>> R.toTask SO1.roomMemberListDecoder

View File

@ -1,6 +1,7 @@
module Internal.Api.Leave.Api exposing (..)
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Json.Decode as D
@ -25,16 +26,18 @@ leaveV1 { roomId } =
>> R.withAttributes
[ R.accessToken
, R.replaceInUrl "roomId" roomId
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask (D.map (always ()) D.value)
leaveV2 : LeaveInputV2 -> Context { a | accessToken : (), baseUrl : () } -> Task X.Error LeaveOutputV1
leaveV2 { roomId, reason } =
R.callApi "POST" "/_matrix/client/r0/rooms/{roomId}/leave"
R.callApi "POST" "/_matrix/client/v3/rooms/{roomId}/leave"
>> R.withAttributes
[ R.accessToken
, R.replaceInUrl "roomId" roomId
, R.bodyOpString "reason" reason
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask (D.map (always ()) D.value)

View File

@ -6,6 +6,7 @@ import Internal.Api.LoginWithUsernameAndPassword.V3.SpecObjects as SO3
import Internal.Api.LoginWithUsernameAndPassword.V4.SpecObjects as SO4
import Internal.Api.LoginWithUsernameAndPassword.V5.Login as SO5
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Json.Encode as E
@ -53,6 +54,7 @@ loginWithUsernameAndPasswordV1 { username, password } =
[ R.bodyString "password" password
, R.bodyString "type" "m.login.password"
, R.bodyString "user" username
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO1.loggedInResponseDecoder
@ -66,6 +68,7 @@ loginWithUsernameAndPasswordV2 { deviceId, initialDeviceDisplayName, password, u
, R.bodyString "password" password
, R.bodyOpString "device_id" deviceId
, R.bodyOpString "initial_device_display_name" initialDeviceDisplayName
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO2.loggedInResponseDecoder
@ -83,6 +86,7 @@ loginWithUsernameAndPasswordV3 { deviceId, initialDeviceDisplayName, password, u
]
|> E.object
|> R.bodyValue "identifier"
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO3.loggedInResponseDecoder
@ -100,6 +104,7 @@ loginWithUsernameAndPasswordV4 { deviceId, initialDeviceDisplayName, password, u
]
|> E.object
|> R.bodyValue "identifier"
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO4.loggedInResponseDecoder
@ -117,6 +122,7 @@ loginWithUsernameAndPasswordV5 { deviceId, initialDeviceDisplayName, password, u
]
|> E.object
|> R.bodyValue "identifier"
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO4.loggedInResponseDecoder
@ -135,21 +141,6 @@ loginWithUsernameAndPasswordV6 { deviceId, initialDeviceDisplayName, password, u
]
|> E.object
|> R.bodyValue "identifier"
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO5.loggedInResponseDecoder
-- loginWithUsernameAndPasswordV5 : LoginWithUsernameAndPasswordInputV1 -> Context { a | baseUrl : () } -> Task X.Error LoginWithUsernameAndPasswordOutputV5
-- loginWithUsernameAndPasswordV5 { username, password } =
-- R.callApi "POST" "/_matrix/client/v3/login"
-- >> R.withAttributes
-- [ [ ( "type", E.string "m.id.user" )
-- , ( "user", E.string username )
-- ]
-- |> E.object
-- |> R.bodyValue "identifier"
-- , R.bodyString "password" password
-- , R.bodyString "type" "m.login.password"
-- ]
-- >> R.toTask SO.loggedInResponseDecoder

View File

@ -1,5 +1,9 @@
module Internal.Api.Request exposing (..)
{-| The request module builds HTTP tasks that are designed around calling the Matrix API.
-}
import Dict exposing (Dict)
import Http
import Internal.Api.Helpers as Helpers
import Internal.Tools.Context as Context exposing (Context)
@ -31,6 +35,7 @@ type ContextAttr
| NoAttr
| QueryParam UrlBuilder.QueryParameter
| ReplaceInUrl String String
| StatusCodeResponse Int X.Error
| Timeout Float
| UrlPath String
@ -97,7 +102,19 @@ toTask decoder (ApiCall data) =
|> E.object
)
|> Http.jsonBody
, resolver = rawApiCallResolver (always decoder)
, resolver =
data.attributes
|> List.filterMap
(\attr ->
case attr of
StatusCodeResponse code err ->
Just ( code, err )
_ ->
Nothing
)
|> Dict.fromList
|> rawApiCallResolver decoder
, timeout =
data.attributes
|> List.filterMap
@ -260,6 +277,14 @@ fullBody value _ =
FullBody value
{-| If the Matrix API does not fit the Matrix spec but returns the following status code,
you may interpret it as the given error.
-}
onStatusCode : Int -> X.ServerError -> Attribute a
onStatusCode code err _ =
StatusCodeResponse code (X.ServerException err)
queryBool : String -> Bool -> Attribute a
queryBool key value _ =
(if value then
@ -332,8 +357,8 @@ withTransactionId =
Context.getTransactionId >> ReplaceInUrl "txnId"
rawApiCallResolver : (Int -> D.Decoder a) -> Http.Resolver X.Error a
rawApiCallResolver decoder =
rawApiCallResolver : D.Decoder a -> Dict Int X.Error -> Http.Resolver X.Error a
rawApiCallResolver decoder statusCodeErrors =
Http.stringResolver
(\response ->
case response of
@ -353,15 +378,19 @@ rawApiCallResolver decoder =
|> Err
Http.BadStatus_ metadata body ->
decodeServerResponse (decoder metadata.statusCode) body
statusCodeErrors
|> Dict.get metadata.statusCode
|> decodeServerResponse decoder body
Http.GoodStatus_ metadata body ->
decodeServerResponse (decoder metadata.statusCode) body
statusCodeErrors
|> Dict.get metadata.statusCode
|> decodeServerResponse decoder body
)
decodeServerResponse : D.Decoder a -> String -> Result X.Error a
decodeServerResponse decoder body =
decodeServerResponse : D.Decoder a -> String -> Maybe X.Error -> Result X.Error a
decodeServerResponse decoder body statusCodeError =
case D.decodeString D.value body of
Err e ->
e
@ -378,11 +407,17 @@ decodeServerResponse decoder body =
Err err ->
-- The response is not valid!
-- Check if it is a valid error type as defined in spec.
case D.decodeString X.errorCatches body of
Ok v ->
case ( D.decodeString X.errorCatches body, statusCodeError ) of
( Ok v, _ ) ->
Err (X.ServerException v)
Err _ ->
-- It does not compute as a valid spec error. Therefore,
-- we will look at its status code before ultimately giving up
-- on the validity of the response.
( Err _, Just sce ) ->
Err sce
( Err _, Nothing ) ->
err
|> D.errorToString
|> X.ServerReturnsBadJSON

View File

@ -2,6 +2,7 @@ module Internal.Api.SendStateKey.Api exposing (..)
import Internal.Api.Request as R
import Internal.Api.SendStateKey.V1.SpecObjects as SO1
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Json.Decode as D
@ -29,6 +30,8 @@ sendStateKeyV1 { content, eventType, roomId, stateKey } =
, R.replaceInUrl "roomId" roomId
, R.replaceInUrl "stateKey" stateKey
, R.fullBody content
, R.onStatusCode 400 (X.M_INVALID_PARAM { error = Just SE.invalidRequest })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.sendNotAllowed })
]
>> R.toTask SO1.eventResponseDecoder
@ -42,5 +45,7 @@ sendStateKeyV2 { content, eventType, roomId, stateKey } =
, R.replaceInUrl "roomId" roomId
, R.replaceInUrl "stateKey" stateKey
, R.fullBody content
, R.onStatusCode 400 (X.M_INVALID_PARAM { error = Just SE.invalidRequest })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.sendNotAllowed })
]
>> R.toTask SO1.eventResponseDecoder

View File

@ -1,6 +1,7 @@
module Internal.Api.SetAccountData.Api exposing (..)
import Internal.Api.Request as R
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context as Context exposing (Context)
import Internal.Tools.Exceptions as X
import Json.Decode as D
@ -33,6 +34,9 @@ setAccountDataV1 { content, eventType, roomId } context =
, R.replaceInUrl "type" eventType
, R.replaceInUrl "userId" (Context.getUserId context)
, R.fullBody content
, R.onStatusCode 400 (X.M_INVALID_PARAM { error = Just SE.invalidRequest })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.accountDataSetNotAllowed })
, R.onStatusCode 405 (X.M_BAD_JSON { error = Just SE.accountDataControlledByServer })
]
>> R.toTask (D.map (always ()) D.value)
|> (|>) context
@ -53,6 +57,9 @@ setAccountDataV2 { content, eventType, roomId } context =
, R.replaceInUrl "type" eventType
, R.replaceInUrl "userId" (Context.getUserId context)
, R.fullBody content
, R.onStatusCode 400 (X.M_INVALID_PARAM { error = Just SE.invalidRequest })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.accountDataSetNotAllowed })
, R.onStatusCode 405 (X.M_BAD_JSON { error = Just SE.accountDataControlledByServer })
]
>> R.toTask (D.map (always ()) D.value)
|> (|>) context

View File

@ -4,6 +4,7 @@ import Internal.Api.Request as R
import Internal.Api.WhoAmI.V1.SpecObjects as SO1
import Internal.Api.WhoAmI.V2.SpecObjects as SO2
import Internal.Api.WhoAmI.V3.SpecObjects as SO3
import Internal.Config.SpecErrors as SE
import Internal.Tools.Context exposing (Context)
import Internal.Tools.Exceptions as X
import Task exposing (Task)
@ -28,19 +29,34 @@ type alias WhoAmIOutputV3 =
whoAmIV1 : WhoAmIInputV1 -> Context { a | accessToken : (), baseUrl : () } -> Task X.Error WhoAmIOutputV1
whoAmIV1 _ =
R.callApi "GET" "/_matrix/client/r0/account/whoami"
>> R.withAttributes [ R.accessToken ]
>> R.withAttributes
[ R.accessToken
, R.onStatusCode 401 (X.M_UNKNOWN_TOKEN { error = Just SE.accessTokenNotRecognized, soft_logout = Nothing })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.appserviceCannotMasquerade })
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO1.whoAmIResponseDecoder
whoAmIV2 : WhoAmIInputV1 -> Context { a | accessToken : (), baseUrl : () } -> Task X.Error WhoAmIOutputV2
whoAmIV2 _ =
R.callApi "GET" "/_matrix/client/v3/account/whoami"
>> R.withAttributes [ R.accessToken ]
>> R.withAttributes
[ R.accessToken
, R.onStatusCode 401 (X.M_UNKNOWN_TOKEN { error = Just SE.accessTokenNotRecognized, soft_logout = Nothing })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.appserviceCannotMasquerade })
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO2.whoAmIResponseDecoder
whoAmIV3 : WhoAmIInputV1 -> Context { a | accessToken : (), baseUrl : () } -> Task X.Error WhoAmIOutputV3
whoAmIV3 _ =
R.callApi "GET" "/_matrix/client/v3/account/whoami"
>> R.withAttributes [ R.accessToken ]
>> R.withAttributes
[ R.accessToken
, R.onStatusCode 401 (X.M_UNKNOWN_TOKEN { error = Just SE.accessTokenNotRecognized, soft_logout = Nothing })
, R.onStatusCode 403 (X.M_FORBIDDEN { error = Just SE.appserviceCannotMasquerade })
, R.onStatusCode 429 (X.M_LIMIT_EXCEEDED { error = Just SE.ratelimited, retryAfterMs = Nothing })
]
>> R.toTask SO3.whoAmIResponseDecoder

View File

@ -0,0 +1,107 @@
module Internal.Config.SpecErrors exposing (..)
{-| Sometimes, the Matrix spec suggests to interpret an HTTP response as a certain
error, even if the server doesn't explicitly say so.
In such cases, the following constants are used to indicate that such an error has occurred.
-}
{-| A user attempts to get information but the homeserver does not recognize the access token that the user has provided.
-}
accessTokenNotRecognized : String
accessTokenNotRecognized =
"Unrecognised access token."
{-| A user attempts to change account data that is controlled by the server.
-}
accountDataControlledByServer : String
accountDataControlledByServer =
"You cannot change this account data type as it's controlled by the server."
{-| A user tries to set account data, but they are not allowed to do so.
-}
accountDataSetNotAllowed : String
accountDataSetNotAllowed =
"You are not authorized to set this account data."
{-| The appservice cannot masquerade as the user or has not registered them.
-}
appserviceCannotMasquerade : String
appserviceCannotMasquerade =
"Application service has not registered this user."
{-| The user attempts to ban another user, but they are not allowed to do so.
For example,
- The banner is not currently in the room.
- The banners power level is insufficient to ban users from the room.
-}
banNotAllowed : String
banNotAllowed =
"You do not have permission to ban someone in this room."
{-| A user tries to access to an event, but either it doesn't exist or they lack permission to read it.
-}
eventNotFound : String
eventNotFound =
"The event was not found or you do not have permission to read this event."
{-| A user made a request that is considered invalid. For example:
- The request body is malformed
- The user tried to interact with users from a homeserver that do not support the action
-}
invalidRequest : String
invalidRequest =
"The request is invalid."
{-| A user tries to invite another user to a room, but they lack the permission to do so. Example reasons for rejection are:
- The invitee has been banned from the room.
- The invitee is already a member of the room.
- The inviter is not currently in the room.
- The inviter's power level is insufficient to invite users to the room.
-}
inviteNotAllowed : String
inviteNotAllowed =
"You do not have permission to invite the user to the room."
{-| A user tries to join a room that they're not allowed to join.
-}
joinNotAllowed : String
joinNotAllowed =
"You do not have permission to join the room."
{-| A user tries to access information from a room that they don't have access to.
-}
notInRoom : String
notInRoom =
"You aren't a member of the room."
{-| A user tries to make an HTTP request, but it was ratelimited.
-}
ratelimited : String
ratelimited =
"This request was rate-limited."
{-| A user is trying to send an invite to a room, but they're not allowed to do so.
-}
sendNotAllowed : String
sendNotAllowed =
"You don't have permission to send the event into the room."