docs: bots API (#6091)

* docs: bot API commands

* generate API commands doc

* generate commands docs with parameters and responses

* add API types

* more types

* document all types (with some deviations from JSON encodings)

* rename types

* interface objects

* separator

* command syntax

* more syntax

* API events

* event types

* fix all type definitions

* pre-process types outside of rendering

* pre-process event types

* overview

* pre-process commands

* param syntax WIP

* syntax for types in command parameters

* API error response and chat event

* remove unsupported/deprecated command parameters

* reorder

* syntax for choice

* show command errors

* event descriptions

* python syntax for commands and types (#6099)

* python syntax for commands and types

* python snippets: convert numbers to string

* fixes

* update readme, enable all tests

* fix operators test

* update plans

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
Evgeny
2025-07-24 13:12:53 +01:00
committed by GitHub
parent 052b9ad628
commit cf8bd7f6ac
32 changed files with 8447 additions and 131 deletions
+462
View File
@@ -0,0 +1,462 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module API.Docs.Commands where
import API.Docs.Responses
import API.Docs.Syntax.Types
import API.Docs.Types
import API.TypeInfo
import Data.String
import Data.List (find)
import Data.Text (Text)
import GHC.Generics
import Simplex.Chat.Controller
import Simplex.Messaging.Parsers (dropPrefix, fstToLower)
chatCommandsDocs :: [CCCategory]
chatCommandsDocs = map toCategory chatCommandsDocsData
where
toCategory (categoryName, categoryDescr, commandsData) =
CCCategory {categoryName, categoryDescr, commands = map toCmd commandsData}
toCmd (consName, hideParams, commandDescr, respNames, errors, network, syntax) = case find ((consName ==) . consName') chatCommandsTypeInfo of
Just RecordTypeInfo {fieldInfos} ->
let fields = filter ((`notElem` hideParams) . fieldName') $ map (toAPIField consName) fieldInfos
commandType = ATUnionMember (fstToLower consName) fields
findResp name = case find ((name ==) . consName') chatResponsesDocs of
Just resp -> resp
Nothing -> error $ "Missing response doc for " <> name
responses = map findResp respNames
errors' = map (\(TD err descr) -> TD (dropPrefix "CE" err) descr) errors
in CCDoc {consName, commandType, commandDescr, responses, errors = errors', network, syntax}
Nothing -> error $ "Missing command type info for " <> consName
deriving instance Generic ChatCommand
chatCommandsTypeInfo :: [RecordTypeInfo]
chatCommandsTypeInfo = recordTypesInfo @ChatCommand
data CCCategory = CCCategory
{ categoryName :: String,
categoryDescr :: String,
commands :: [CCDoc]
}
data CCDoc = CCDoc
{ consName :: ConsName,
commandType :: ATUnionMember,
commandDescr :: Text,
responses :: [CRDoc],
errors :: [ErrorTypeDoc],
network :: Maybe UsesNetwork,
syntax :: Expr
}
instance ConstructorName CCDoc where consName' CCDoc {consName} = consName
data ErrorTypeDoc = TD
{ consName :: ConsName,
description :: String
}
data UsesNetwork = UNBackground | UNInteractive
instance IsString ErrorTypeDoc where fromString s = TD s ""
-- category name, category description, commands
-- inner: constructor, description, responses, errors (ChatErrorType constructors), network usage, syntax
chatCommandsDocsData :: [(String, String, [(ConsName, [String], Text, [ConsName], [ErrorTypeDoc], Maybe UsesNetwork, Expr)])]
chatCommandsDocsData =
[ ( "Address commands",
"Bots can use these commands to automatically check and create address when initialized",
[ ("APICreateMyAddress", [], "Create bot address.", ["CRUserContactLinkCreated"], [], Just UNInteractive, "/_address " <> Param "userId"),
("APIDeleteMyAddress", [], "Delete bot address.", ["CRUserContactLinkDeleted"], [], Just UNBackground, "/_delete_address " <> Param "userId"),
("APIShowMyAddress", [], "Get bot address and settings.", ["CRUserContactLink"], [], Nothing, "/_show_address " <> Param "userId"),
("APISetProfileAddress", [], "Add address to bot profile.", ["CRUserProfileUpdated"], [], Just UNInteractive, "/_profile_address " <> Param "userId" <> " " <> OnOff "enable"),
("APISetAddressSettings", [], "Set bot address settings.", ["CRUserContactLinkUpdated"], [], Just UNInteractive, "/_address_settings " <> Param "userId" <> " " <> Json "settings")
]
),
( "Message commands",
"Commands to send, update, delete, moderate messages and set message reactions",
[ ("APISendMessages", [], "Send messages.", ["CRNewChatItems"], [], Just UNBackground, "/_send " <> Param "sendRef" <> OnOffParam "live" "liveMessage" (Just False) <> Optional "" (" ttl=" <> Param "$0") "ttl" <> " json " <> Json "composedMessages"),
( "APIUpdateChatItem",
[],
"Update message.",
["CRChatItemUpdated", "CRChatItemNotChanged", "CRChatCmdError"],
[TD "CEInvalidChatItemUpdate" "Not user's message or cannot be edited"],
Just UNBackground,
"/_update item " <> Param "chatRef" <> " " <> Param "chatItemId" <> OnOffParam "live" "liveMessage" (Just False) <> " json " <> Json "updatedMessage"
),
("APIDeleteChatItem", [], "Delete message.", ["CRChatItemsDeleted"], [], Just UNBackground, "/_delete item " <> Param "chatRef" <> " " <> Join ',' "chatItemIds" <> " " <> Param "deleteMode"),
("APIDeleteMemberChatItem", [], "Moderate message. Requires Moderator role (and higher than message author's).", ["CRChatItemsDeleted"], [], Just UNBackground, "/_delete member item #" <> Param "groupId" <> " " <> Join ',' "chatItemIds"),
("APIChatItemReaction", [], "Add/remove message reaction.", ["CRChatItemReaction"], [], Just UNBackground, "/_reaction " <> Param "chatRef" <> " " <> Param "chatItemId" <> " " <> OnOff "add" <> " " <> Json "reaction")
]
),
( "File commands",
"Commands to receive and to cancel files. Files are sent as part of the message, there are no separate commands to send files.",
[ ("ReceiveFile", [], "Receive file.", ["CRRcvFileAccepted", "CRRcvFileAcceptedSndCancelled"], [], Nothing, "/freceive " <> Param "fileId" <> OnOffParam "approved_relays" "userApprovedRelays" (Just False) <> OnOffParam "encrypt" "storeEncrypted" Nothing <> OnOffParam "inline" "fileInline" Nothing <> Optional "" (" " <> Param "$0") "filePath"),
("CancelFile", [], "Cancel file.", ["CRSndFileCancelled", "CRRcvFileCancelled", "CRChatCmdError"], [TD "CEFileCancel" "Cannot cancel file"], Just UNBackground, "/fcancel " <> Param "fileId")
]
),
( "Group commands",
"Commands to manage and moderate groups. These commands can be used with business chats as well - they are groups. E.g., a common scenario would be to add human agents to business chat with the customer who connected via business address.",
[ ("APIAddMember", [], "Add contact to group. Requires bot to have Admin role.", ["CRSentGroupInvitation"], [], Just UNInteractive, "/_add #" <> Param "groupId" <> " " <> Param "contactId" <> " " <> Param "memberRole"),
("APIJoinGroup", ["enableNtfs"], "Join group.", ["CRUserAcceptedGroupSent"], [], Just UNInteractive, "/_join #" <> Param "groupId"),
("APIAcceptMember", [], "Accept group member. Requires Admin role.", ["CRMemberAccepted", "CRChatCmdError"], [TD "CEGroupMemberNotActive" "Member is not connected yet"], Just UNBackground, "/_accept member #" <> Param "groupId" <> " " <> Param "groupMemberId" <> " " <> Param "memberRole"),
("APIMembersRole", [], "Set members role. Requires Admin role.", ["CRMembersRoleUser"], [], Just UNBackground, "/_member role #" <> Param "groupId" <> " " <> Join ',' "groupMemberIds" <> " " <> Param "memberRole"),
("APIBlockMembersForAll", [], "Block members. Requires Moderator role.", ["CRMembersBlockedForAllUser"], [], Just UNBackground, "/_block #" <> Param "groupId" <> " " <> Join ',' "groupMemberIds" <> OnOffParam "blocked" "blocked" Nothing),
("APIRemoveMembers", [], "Remove members. Requires Admin role.", ["CRUserDeletedMembers", "CRChatCmdError"], ["CEGroupMemberNotFound"], Just UNBackground, "/_remove #" <> Param "groupId" <> " " <> Join ',' "groupMemberIds" <> OnOffParam "messages" "withMessages" (Just False)),
("APILeaveGroup", [], "Leave group.", ["CRLeftMemberUser"], [], Just UNBackground, "/_leave #" <> Param "groupId")
-- ("APIListMembers", [], "Get group members.", ["CRGroupMembers"], [], Nothing, ""),
-- ("APINewGroup", [], "Create group.", ["CRGroupCreated"], [], Nothing, ""),
-- ("APIUpdateGroupProfile", [], "Update group profile.", ["CRGroupUpdated"], [], Just UNBackground, [])
]
),
( "Group link commands",
"These commands can be used by bots that manage multiple public groups",
[ ("APICreateGroupLink", [], "Create group link.", ["CRGroupLinkCreated"], [], Just UNInteractive, "/_create link #" <> Param "groupId" <> " " <> Param "memberRole"),
("APIGroupLinkMemberRole", [], "Set member role for group link.", ["CRGroupLink"], [], Nothing, "/_set link role #" <> Param "groupId" <> " " <> Param "memberRole"),
("APIDeleteGroupLink", [], "Delete group link.", ["CRGroupLinkDeleted"], [], Just UNBackground, "/_delete link #" <> Param "groupId"),
("APIGetGroupLink", [], "Get group link.", ["CRGroupLink"], [], Nothing, "/_get link #" <> Param "groupId")
]
),
( "Connection commands",
"These commands may be used to create connections. Most bots do not need to use them - bot users will connect via bot address with auto-accept enabled.",
[ ("APIAddContact", [], "Create 1-time invitation link.", ["CRInvitation"], [], Just UNInteractive, "/_connect " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False)),
("APIConnectPlan", [], "Determine SimpleX link type and if the bot is already connected via this link.", ["CRConnectionPlan"], [], Just UNInteractive, "/_connect plan " <> Param "userId" <> " " <> Param "connectionLink"),
("APIConnect", [], "Connect via SimpleX link. The link can be 1-time invitation link, contact address or group link", ["CRSentConfirmation", "CRContactAlreadyExists", "CRSentInvitation"], [], Just UNInteractive, "/_connect " <> Param "userId" <> " " <> Param "connLink_"),
("APIAcceptContact", ["incognito"], "Accept contact request.", ["CRAcceptingContactRequest"], [], Just UNInteractive, "/_accept " <> Param "contactReqId"),
("APIRejectContact", [], "Reject contact request. The user who sent the request is **not notified**.", ["CRContactRequestRejected"], [], Nothing, "/_reject " <> Param "contactReqId")
]
),
( "Chat commands",
"Commands to list and delete coversations.",
[ ("APIListContacts", [], "Get contacts.", ["CRContactsList"], [], Nothing, "/_contacts " <> Param "userId"),
("APIListGroups", [], "Get groups.", ["CRGroupsList"], [], Nothing, "/_groups " <> Param "userId" <> Optional "" (" @" <> Param "$0") "contactId_" <> Optional "" (" " <> Param "$0") "search"),
("APIDeleteChat", [], "Delete chat.", ["CRContactDeleted", "CRContactConnectionDeleted", "CRGroupDeletedUser"], [], Just UNBackground, "/_delete " <> Param "chatRef" <> " " <> Param "chatDeleteMode")
-- ("APIChatItemsRead", [], "Mark items as read.", ["CRItemsReadForChat"], [], Nothing, ""),
-- ("APIChatRead", [], "Mark chat as read.", ["CRCmdOk"], [], Nothing, ""),
-- ("APIChatUnread", [], "Mark chat as unread.", ["CRCmdOk"], [], Nothing, ""),
-- ("APIClearChat", [], "Clear chat.", ["CRChatCleared"], [], Nothing, ""),
-- ("APIGetChat", [], "Get chat.", ["CRApiChat"], [], Nothing, ""),
-- ("APIGetChatItemInfo", [], "Get message information.", ["CRChatItemInfo"], [], Nothing, ""),
-- ("APIGetChatItems", [], "Get the most recent messages from all chats.", ["CRChatItems"], [], Nothing, ""),
-- ("APIGetChats", [], "Get chats.", ["CRApiChats"], [], Nothing, ""),
-- ("APISetChatSettings", [], "Set chat settings.", ["CRCmdOk"], [], Nothing, ""),
-- ("APISetChatTTL", [], "Set TTL for chat messages.", ["CRCmdOk"], [], Nothing, ""),
-- ("APISetConnectionAlias", [], "Set connection alias.", ["CRConnectionAliasUpdated"], [], Nothing, ""),
-- ("APISetContactAlias", [], "Set contact alias.", ["CRContactAliasUpdated"], [], Nothing, ""),
-- ("APISetContactPrefs", [], "Set contact preferences.", ["CRContactPrefsUpdated"], [], Just UNBackground, ""),
-- ("APISetGroupAlias", [], "Set group alias.", ["CRGroupAliasUpdated"], [], Nothing, ""),
-- ("APISyncContactRatchet", [], "Synchronize encryption with contact.", ["CRContactRatchetSyncStarted"], [], Just UNBackground, ""),
-- ("APISyncGroupMemberRatchet", [], "Synchronize encryption with member.", ["CRGroupMemberRatchetSyncStarted"], [], Just UNBackground, ""),
]
),
( "User profile commands",
"Most bots don't need to use these commands, as bot profile can be configured manually via CLI or desktop client. These commands can be used by bots that need to manage multiple user profiles (e.g., the profiles of support agents).",
[ ("ShowActiveUser", [], "Get active user profile", ["CRActiveUser"], [], Nothing, "/user"),
( "CreateActiveUser",
[],
"Create new user profile",
["CRActiveUser"],
[TD "CEUserExists" "User or contact with this name already exists", TD "CEInvalidDisplayName" "Invalid user display name"],
Nothing,
"/_create user " <> Json "newUser"
),
("ListUsers", [], "Get all user profiles", ["CRUsersList"], [], Nothing, "/users"),
("APISetActiveUser", [], "Set active user profile", ["CRActiveUser"], ["CEChatNotStarted"], Nothing, "/_user " <> Param "userId" <> Optional "" (" " <> Json "$0") "viewPwd"),
("APIDeleteUser", [], "Delete user profile.", ["CRCmdOk"], [], Just UNBackground, "/_delete user " <> Param "userId" <> OnOffParam "del_smp" "delSMPQueues" Nothing <> Optional "" (" " <> Json "$0") "viewPwd"),
("APIUpdateProfile", [], "Update user profile.", ["CRUserProfileUpdated"], [], Just UNBackground, "/_profile " <> Param "userId" <> " " <> Json "profile")
]
)
]
cliCommands :: [ConsName]
cliCommands =
[ "AbortSwitchContact",
"AbortSwitchGroupMember",
"AcceptContact",
"AcceptMember",
"AddContact",
"AddMember",
"BlockForAll",
"ChatHelp",
"ClearContact",
"ClearGroup",
"ClearNoteFolder",
"Connect",
"ConnectSimplex",
"ContactInfo",
"ContactQueueInfo",
"CreateGroupLink",
"CreateMyAddress",
"DeleteContact",
"DeleteGroup",
"DeleteGroupLink",
"DeleteMemberMessage",
"DeleteMessage",
"DeleteMyAddress",
"DeleteUser",
"EditMessage",
"EnableContact",
"EnableGroupMember",
"FileStatus",
"ForwardFile",
"ForwardGroupMessage",
"ForwardImage",
"ForwardLocalMessage",
"ForwardMessage",
"GetChatTTL",
"GetContactCode",
"GetGroupMemberCode",
"GroupLinkMemberRole",
"GroupMemberInfo",
"GroupMemberQueueInfo",
"HideUser",
"JoinGroup",
"LastChatItemId",
"LastChats",
"LastMessages",
"LeaveGroup",
"ListContacts",
"ListGroups",
"ListMembers",
"ListMemberSupportChats",
"MemberRole",
"MuteUser",
"NewGroup",
"QuitChat",
"ReactToMessage",
"RejectContact",
"RemoveMembers",
"ReportMessage",
"SendCallInvitation",
"SendFile",
"SendFileDescription",
"SendGroupMessageQuote",
"SendImage",
"SendLiveMessage",
"SendMemberContactMessage",
"SendMessage",
"SendMessageBroadcast",
"SendMessageQuote",
"SetActiveUser",
"SetAddressSettings",
"SetChatTTL",
"SetContactFeature",
"SetContactTimedMessages",
"SetGroupFeature",
"SetGroupFeatureRole",
"SetGroupMemberAdmissionReview",
"SetGroupTimedMessages",
"SetLocalDeviceName",
"SetProfileAddress",
"SetSendReceipts",
"SetShowMemberMessages",
"SetShowMessages",
"SetUserContactReceipts",
"SetUserFeature",
"SetUserGroupReceipts",
"SetUserTimedMessages",
"ShowChatItem",
"ShowChatItemInfo",
"ShowGroupDescription",
"ShowGroupInfo",
"ShowGroupLink",
"ShowGroupProfile",
"ShowLiveItems",
"ShowMyAddress",
"ShowProfile",
"ShowProfileImage",
"ShowVersion",
"SwitchContact",
"SwitchGroupMember",
"SyncContactRatchet",
"SyncGroupMemberRatchet",
"UnhideUser",
"UnmuteUser",
"UpdateGroupDescription",
"UpdateGroupNames",
"UpdateLiveMessage",
"UpdateProfile",
"UpdateProfileImage",
"UserRead",
"VerifyContact",
"VerifyGroupMember",
"Welcome"
]
undocumentedCommands :: [ConsName]
undocumentedCommands =
[ "APIAbortSwitchContact",
"APIAbortSwitchGroupMember",
"APIAcceptConditions",
"APIActivateChat",
"APIAddGroupShortLink",
"APIAddMyAddressShortLink",
"APIArchiveReceivedReports",
"APICallStatus",
"APIChangeConnectionUser",
"APIChangePreparedContactUser",
"APIChangePreparedGroupUser",
"APIChatItemsRead",
"APIChatRead",
"APIChatUnread",
"APICheckToken",
"APIClearChat",
"APIConnectContactViaAddress",
"APIConnectPreparedContact",
"APIConnectPreparedGroup",
"APIContactInfo",
"APIContactQueueInfo",
"APICreateChatItems",
"APICreateChatTag",
"APICreateMemberContact",
"APIDeleteChatTag",
"APIDeleteMemberSupportChat",
"APIDeleteReceivedReports",
"APIDeleteStorage",
"APIDeleteToken",
"APIDownloadStandaloneFile",
"APIEnableContact",
"APIEnableGroupMember",
"APIEndCall",
"APIExportArchive",
"APIForwardChatItems",
"APIGetAppSettings",
"APIGetCallInvitations",
"APIGetChat",
"APIGetChatItemInfo",
"APIGetChatItems",
"APIGetChatItemTTL",
"APIGetChats",
"APIGetChatTags",
"APIGetConnNtfMessages",
"APIGetContactCode",
"APIGetGroupMemberCode",
"APIGetNetworkConfig",
"APIGetNetworkStatuses",
"APIGetNtfConns",
"APIGetNtfToken",
"APIGetReactionMembers",
"APIGetServerOperators",
"APIGetUsageConditions",
"APIGetUserServers",
"APIGroupInfo",
"APIGroupMemberInfo",
"APIGroupMemberQueueInfo",
"APIHideUser",
"APIImportArchive",
"APIListMembers",
"APIMuteUser",
"APINewGroup",
"APIPlanForwardChatItems",
"APIPrepareContact",
"APIPrepareGroup",
"APIRegisterToken",
"APIRejectCall",
"APIReorderChatTags",
"APIReportMessage",
"APISaveAppSettings",
"APISendCallAnswer",
"APISendCallExtraInfo",
"APISendCallInvitation",
"APISendCallOffer",
"APISendMemberContactInvitation",
"APISetAppFilePaths",
"APISetChatItemTTL",
"APISetChatSettings",
"APISetChatTags",
"APISetChatTTL",
"APISetChatUIThemes",
"APISetConditionsNotified",
"APISetConnectionAlias",
"APISetConnectionIncognito",
"APISetContactAlias",
"APISetContactPrefs",
"APISetEncryptLocalFiles",
"APISetGroupAlias",
"APISetMemberSettings",
"APISetNetworkConfig",
"APISetNetworkInfo",
"APISetServerOperators",
"APISetUserContactReceipts",
"APISetUserGroupReceipts",
"APISetUserServers",
"APISetUserUIThemes",
"APIStandaloneFileInfo",
"APIStopChat",
"APIStorageEncryption",
"APISuspendChat",
"APISwitchContact",
"APISwitchGroupMember",
"APISyncContactRatchet",
"APISyncGroupMemberRatchet",
"APITestProtoServer",
"APIUnhideUser",
"APIUnmuteUser",
"APIUpdateChatTag",
"APIUpdateGroupProfile",
"APIUploadStandaloneFile",
"APIUserRead",
"APIValidateServers",
"APIVerifyContact",
"APIVerifyGroupMember",
"APIVerifyToken",
"CheckChatRunning",
"ConfirmRemoteCtrl",
"ConnectRemoteCtrl",
"CustomChatCommand",
"DebugEvent",
"DebugLocks",
"DeleteRemoteCtrl",
"DeleteRemoteHost",
"ExecAgentStoreSQL",
"ExecChatStoreSQL",
"ExportArchive",
"FindKnownRemoteCtrl",
"GetAgentQueuesInfo",
"GetAgentServersSummary",
"GetAgentSubs",
"GetAgentSubsDetails",
"GetAgentSubsTotal",
"GetAgentWorkers",
"GetAgentWorkersDetails",
"GetChatItemTTL",
"GetRemoteFile",
"GetUserProtoServers",
"ListRemoteCtrls",
"ListRemoteHosts",
"ReconnectAllServers",
"ReconnectServer",
"ResetAgentServersStats",
"ResubscribeAllConnections",
"SetAllContactReceipts",
"SetChatItemTTL",
"SetContactMergeEnabled",
"SetFilesFolder",
"SetFileToReceive",
"SetNetworkConfig",
"SetRemoteHostsFolder",
"SetServerOperators",
"SetTempFolder",
"SetUserProtoServers",
"SlowSQLQueries",
"StartChat",
"StartRemoteHost",
"StopRemoteCtrl",
"StopRemoteHost",
"StoreRemoteFile",
"SwitchRemoteHost",
"TestProtoServer",
"TestStorageEncryption",
"VerifyRemoteCtrlSession"
]
+216
View File
@@ -0,0 +1,216 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module API.Docs.Events where
import API.Docs.Types
import API.TypeInfo
import Data.List (find)
import GHC.Generics
import Simplex.Chat.Controller
import Simplex.Messaging.Parsers (dropPrefix)
data CECategory = CECategory
{ categoryName :: String,
categoryDescr :: String,
mainEvents :: [CEDoc],
otherEvents :: [CEDoc]
}
data CEDoc = CEDoc
{ consName :: ConsName,
eventType :: ATUnionMember,
eventDescr :: String
}
instance ConstructorName CEDoc where consName' CEDoc {consName} = consName
chatEventsDocs :: [CECategory]
chatEventsDocs = map toCategory chatEventsDocsData
where
toCategory (categoryName, categoryDescr, mainEvts, otherEvts) =
CECategory {categoryName, categoryDescr, mainEvents = map toEvt mainEvts, otherEvents = map toEvt otherEvts}
toEvt (consName, eventDescr)
| consName == "CEvtChatError" =
let field = toAPIField consName $ FieldInfo "chatError" (ti "ChatError")
eventType = ATUnionMember (dropPrefix "CEvt" consName) [field]
in CEDoc {consName, eventType, eventDescr}
| otherwise = case find ((consName ==) . consName') chatEventsTypeInfo of
Just RecordTypeInfo {fieldInfos} ->
let fields = map (toAPIField consName) fieldInfos
eventType = ATUnionMember (dropPrefix "CEvt" consName) fields
in CEDoc {consName, eventType, eventDescr}
Nothing -> error $ "Missing event type info for " <> consName
deriving instance Generic ChatEvent
chatEventsTypeInfo :: [RecordTypeInfo]
chatEventsTypeInfo = recordTypesInfo @ChatEvent
chatEventsDocsData :: [(String, String, [(ConsName, String)], [(ConsName, String)])]
chatEventsDocsData =
[ ( "Contact connection events", -- which event should be processed by a bot that has business address. Maybe needs a separate category.
"Bots must use these events to process connecting users.\n\n\
\Most bots enable auto-accept and don't need to accept connections via commands.\n\n\
\You may create bot SimpleX address manually via CLI or desktop app or from bot code with these commands:\n\
\- [APIShowMyAddress](./COMMANDS.md#apishowmyaddress) to check if address exists,\n\
\- [APICreateMyAddress](./COMMANDS.md#apicreatemyaddress) to create address,\n\
\- [APISetAddressSettings](./COMMANDS.md#apisetaddresssettings) to enable auto-access.",
[ ( "CEvtContactConnected", "This event is sent after a user connects via bot SimpleX address (not a business address).")
],
[
("CEvtContactUpdated", "Contact profile of another user is updated."),
("CEvtContactDeletedByContact", "Bot user's connection with another contact is deleted (conversation is kept)."),
("CEvtReceivedContactRequest", "Contact request received.\n\nThis event is only sent when auto-accept is disabled.\n\nThe request needs to be accepted using [APIAcceptContact](./COMMANDS.md#apiacceptcontact) command"),
("CEvtNewMemberContactReceivedInv", "Received invitation to connect directly with a group member.\n\nThis event only needs to be processed to associate contact with group, the connection will proceed automatically."),
("CEvtContactSndReady", "Connecting via 1-time invitation or after accepting contact request.\n\nAfter this event bot can send messages to this contact.") -- JOINED
]
),
( "Message events",
"Bots must use these events to process received messages.",
[ ("CEvtNewChatItems", "Received message(s).")
],
[ ("CEvtChatItemReaction", "Received message reaction."),
("CEvtChatItemsDeleted", "Message was deleted by another user."),
("CEvtChatItemUpdated", "Message was updated by another user."),
("CEvtGroupChatItemsDeleted", "Group messages are deleted or moderated."),
("CEvtChatItemsStatusesUpdated", "Message delivery status updates.")
]
),
( "Group events",
"Bots may use these events to manage users' groups and business address groups.\n\n\
\*Please note*: programming groups is more complex than programming direct connections",
[ ("CEvtReceivedGroupInvitation", ""),
("CEvtUserJoinedGroup", "Bot user joined group. Received when connection via group link completes."),
("CEvtGroupUpdated", "Group profile or preferences updated."),
("CEvtJoinedGroupMember", "Another member joined group."),
("CEvtMemberRole", "Member (or bot user's) group role changed."),
("CEvtDeletedMember", "Another member is removed from the group."),
("CEvtLeftMember", "Another member left the group."),
("CEvtDeletedMemberUser", "Bot user was removed from the group."),
("CEvtGroupDeleted", "Group was deleted by the owner (not bot user).")
],
[ ("CEvtConnectedToGroupMember", "Connected to another group member."),
("CEvtMemberAcceptedByOther", "Another group owner, admin or moderator accepted member to the group after review (\"knocking\")."),
("CEvtMemberBlockedForAll", "Another member blocked for all members."),
("CEvtGroupMemberUpdated", "Another group member profile updated.")
]
),
( "File events",
"Bots that send or receive files may process these events to track delivery status and to process completion.\n\n\
\Bots that need to receive or moderate files (e.g., based on name, size or extension), \
\can use relevant commands (e.g., [ReceiveFile](./COMMANDS.md#receivefile) or \
\[APIDeleteMemberChatItem](./COMMANDS.md#apideletememberchatitem)) \
\when processing [NewChatItems](#newchatitems) event.\n\n\
\Bots that need to send files should use [APISendMessages](./COMMANDS.md#apisendmessages) command.",
[ ( "CEvtRcvFileDescrReady",
"File is ready to be received.\n\n\
\This event is useful for processing sender file servers and monitoring file reception progress.\n\n\
\[ReceiveFile](./COMMANDS.md#receivefile) command can be used before this event."
),
("CEvtRcvFileComplete", "File reception is competed."),
("CEvtSndFileCompleteXFTP", "File upload is competed.")
],
[ ("CEvtRcvFileStart", "File reception started. This event will be sent after [CEvtRcvFileDescrReady](#rcvfiledescrready) event."),
("CEvtRcvFileSndCancelled", "File was cancelled by the sender. This event may be sent instead of [CEvtRcvFileDescrReady](#rcvfiledescrready) event."),
("CEvtRcvFileAccepted", "This event will be sent when file is automatically accepted because of CLI option."),
("CEvtRcvFileError", "Error receiving file."),
("CEvtRcvFileWarning", "Warning when receiving file. It can happen when CLI settings do not allow to connect to file server(s)."),
("CEvtSndFileError", "Error sending file."),
("CEvtSndFileWarning", "Warning when sending file.")
]
),
( "Connection progress events",
"Bots may use these events to track progress of connections for monitoring or debugging.",
[ ("CEvtAcceptingContactRequest", "Automatically accepting contact request via bot's SimpleX address with auto-accept enabled."),
("CEvtAcceptingBusinessRequest", "Automatically accepting contact request via bot's business address."),
("CEvtContactConnecting", "Contact confirmed connection.\n\nSent when contact started connecting via bot's 1-time invitation link or when bot connects to another SimpleX address."), -- CONF
("CEvtBusinessLinkConnecting", "Contact confirmed connection.\n\nSent when bot connects to another business address."), -- CONF
("CEvtJoinedGroupMemberConnecting", "Group member is announced to the group and will be connecting to bot."), -- MSG
("CEvtSentGroupInvitation", "Sent when another user joins group via bot's link."), -- INV
("CEvtGroupLinkConnecting", "Sent when bot joins group via another user link.") -- CONF
],
[]
),
( "Error events",
"Bots may log these events for debugging. \
\There will be many error events - this does NOT indicate a malfunction - \
\e.g., they may happen because of bad network connectivity, \
\or because messages may be delivered to deleted chats for a short period of time \
\(they will be ignored).",
[ ("CEvtMessageError", ""),
("CEvtChatError", ""), -- only used in WebSockets API, Haskell code uses Either, with error in Left
("CEvtChatErrors", "")
],
[]
)
]
undocumentedEvents :: [ConsName]
undocumentedEvents =
[ "CEvtAcceptingGroupJoinRequestMember",
"CEvtAgentConnsDeleted",
"CEvtAgentRcvQueuesDeleted",
"CEvtAgentUserDeleted",
"CEvtBusinessRequestAlreadyAccepted",
"CEvtCallAnswer",
"CEvtCallEnded",
"CEvtCallExtraInfo",
"CEvtCallInvitation",
"CEvtCallOffer",
"CEvtChatInfoUpdated",
"CEvtChatItemDeletedNotFound",
"CEvtChatItemNotChanged",
"CEvtChatSuspended",
"CEvtConnectionDisabled",
"CEvtConnectionInactive",
"CEvtContactAndMemberAssociated",
"CEvtContactAnotherClient",
"CEvtContactDisabled",
"CEvtContactPQEnabled",
"CEvtContactRatchetSync",
"CEvtContactRequestAlreadyAccepted",
"CEvtContactsDisconnected",
"CEvtContactsMerged",
"CEvtContactsSubscribed",
"CEvtContactSubError",
"CEvtContactSubSummary",
"CEvtContactSwitch",
"CEvtCustomChatEvent",
"CEvtGroupMemberRatchetSync",
"CEvtGroupMemberSwitch",
"CEvtHostConnected",
"CEvtHostDisconnected",
"CEvtNetworkStatus",
"CEvtNetworkStatuses",
"CEvtNewRemoteHost",
"CEvtNoMemberContactCreating",
"CEvtNtfMessage",
"CEvtRcvFileAcceptedSndCancelled", -- only sent with legacy SMP files when they are cancelled
"CEvtRcvFileProgressXFTP",
"CEvtRcvStandaloneFileComplete",
"CEvtRemoteCtrlFound",
"CEvtRemoteCtrlSessionCode",
"CEvtRemoteCtrlStopped",
"CEvtRemoteHostConnected",
"CEvtRemoteHostSessionCode",
"CEvtRemoteHostStopped",
"CEvtSndFileComplete", -- legacy SMP files
"CEvtSndFileProgressXFTP",
"CEvtSndFileRcvCancelled", -- legacy SMP files
"CEvtSndFileRedirectStartXFTP",
"CEvtSndFileStart", -- legacy SMP files
"CEvtSndStandaloneFileComplete",
"CEvtSubscriptionEnd",
"CEvtTerminalEvent",
"CEvtTimedAction",
"CEvtUnknownMemberAnnounced",
"CEvtUnknownMemberBlocked",
"CEvtUnknownMemberCreated",
"CEvtUserAcceptedGroupSent", -- repeat group invitation after it was accepted by the user
"CEvtUserContactSubSummary"
]
+164
View File
@@ -0,0 +1,164 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module API.Docs.Generate where
import API.Docs.Commands
import API.Docs.Events
import API.Docs.Responses
import API.Docs.Syntax
import API.Docs.Syntax.Types
import API.Docs.Types
import API.TypeInfo
import Data.Char (isSpace, isUpper, toLower, toUpper)
import qualified Data.List.NonEmpty as L
import Data.Text (Text)
import qualified Data.Text as T
commandsDocFile :: FilePath
commandsDocFile = "./bots/api/COMMANDS.md"
eventsDocFile :: FilePath
eventsDocFile = "./bots/api/EVENTS.md"
typesDocFile :: FilePath
typesDocFile = "./bots/api/TYPES.md"
commandsDocText :: Text
commandsDocText =
("# API Commands and Responses\n\n" <> autoGenerated <> "\n")
<> foldMap commandCatTOC chatCommandsDocs
<> "\n---\n"
<> foldMap commandCatText chatCommandsDocs
where
commandCatTOC CCCategory {categoryName, commands} =
(T.pack $ "\n" <> withLink "" categoryName <> "\n")
<> foldMap commandTOC commands
where
commandTOC CCDoc {commandType = ATUnionMember tag _} = T.pack $ "- " <> withLink "" (fstToUpper tag) <> "\n"
commandCatText CCCategory {categoryName, categoryDescr, commands} =
(T.pack $ "\n\n## " <> categoryName <> "\n\n" <> categoryDescr <> "\n")
<> foldMap commandDocText commands
where
commandDocText CCDoc {commandType = ATUnionMember tag params, commandDescr, network, syntax, responses, errors} =
("\n\n### " <> T.pack (fstToUpper tag) <> "\n\n" <> commandDescr <> "\n\n*Network usage*: " <> networkUsage <> ".\n")
<> (if null params then "" else paramsText)
<> (if syntax == "" then "" else syntaxText (tag, params) syntax)
<> (if length responses > 1 then "\n**Responses**:\n" else "\n**Response**:\n")
<> foldMap responseText responses
<> (if null errors then "" else "\n**Errors**:\n")
<> foldMap errorText errors
<> "\n---\n"
where
networkUsage = case network of
Nothing -> "no"
Just UNInteractive -> "interactive"
Just UNBackground -> "background"
paramsText = "\n**Parameters**:\n" <> fieldsText "./TYPES.md" params
responseText CRDoc {responseType = ATUnionMember tag fields, responseDescr} =
(T.pack $ "\n" <> fstToUpper tag <> ": " <> respDescr <> ".\n")
<> ("- type: \"" <> T.pack tag <> "\"\n")
<> fieldsText "./TYPES.md" fields
where
respDescr = if null responseDescr then camelToSpace tag else responseDescr
errorText (TD err descr) =
let descr' = if null descr then camelToSpace err else descr
in T.pack $ "- " <> fstToUpper err <> ": " <> descr' <> ".\n"
syntaxText :: TypeAndFields -> Expr -> Text
syntaxText r syntax =
"\n**Syntax**:\n"
<> "\n```\n" <> docSyntaxText r syntax <> "\n```\n"
<> (if isConst syntax then "" else "\n```javascript\n" <> jsSyntaxText r syntax <> " // JavaScript\n```\n")
<> (if isConst syntax then "" else "\n```python\n" <> pySyntaxText r syntax <> " # Python\n```\n")
camelToSpace :: String -> String
camelToSpace [] = []
camelToSpace (x : xs) = toUpper x : go xs
where
go [] = []
go (y : ys)
| isUpper y = ' ' : toLower y : go ys
| otherwise = y : go ys
eventsDocText :: Text
eventsDocText =
("# API Events\n\n" <> autoGenerated <> "\n")
<> foldMap eventCatTOC chatEventsDocs
<> "\n---\n"
<> foldMap eventCatText chatEventsDocs
where
eventCatTOC CECategory {categoryName, mainEvents, otherEvents} =
(T.pack $ "\n" <> withLink "" categoryName <> "\n")
<> (if hasOthers then "- Main event" <> plural mainEvents <> "\n" else "")
<> foldMap eventTOC mainEvents
<> (if hasOthers then "- Other event" <> plural otherEvents <> "\n" <> foldMap eventTOC otherEvents else "")
where
eventTOC CEDoc {eventType = ATUnionMember tag _} = T.pack $ indent <> "- " <> withLink "" (fstToUpper tag) <> "\n"
hasOthers = not $ null otherEvents
indent = if hasOthers then " " else ""
eventCatText CECategory {categoryName, categoryDescr, mainEvents, otherEvents} =
(T.pack $ "\n\n## " <> categoryName <> "\n\n" <> categoryDescr <> "\n")
<> foldMap eventDocText (mainEvents ++ otherEvents)
where
eventDocText CEDoc {eventType = ATUnionMember tag fields, eventDescr} =
(T.pack $ "\n\n### " <> fstToUpper tag <> "\n\n" <> evtDescr)
<> "\n\n**Record type**:\n"
<> ("- type: \"" <> T.pack tag <> "\"\n")
<> fieldsText "./TYPES.md" fields
<> "\n---\n"
where
evtDescr = if null eventDescr then camelToSpace tag <> "." else eventDescr
plural evts = if length evts == 1 then "" else "s"
typesDocText :: Text
typesDocText =
("# API Types\n\n" <> autoGenerated <> "\n")
<> (foldMap (\t -> T.pack $ "\n- " <> withLink "" (docTypeName t)) chatTypesDocs <> "\n")
<> foldMap typeDocText chatTypesDocs
where
typeDocText CTDoc {typeDef = td@APITypeDef {typeName' = name, typeDef}, typeDescr, typeSyntax} =
("\n\n---\n\n## " <> T.pack name <> "\n")
<> (if T.null typeDescr then "" else "\n" <> typeDescr <> "\n")
<> typeDefText typeDef
<> (if typeSyntax == "" then "" else syntaxText (name, self : typeFields) typeSyntax)
where
self = APIRecordField "self" (ATDef td)
typeFields = case typeDef of
ATDRecord fs -> L.toList fs
ATDUnion ms -> APIRecordField "type" tagType : concatMap (\(ATUnionMember _ fs) -> fs) ms
where
tagType = ATDef $ APITypeDef (name <> ".type") $ ATDEnum tags
tags = L.map (\(ATUnionMember tag _) -> tag) ms
ATDEnum _ -> []
typeDefText = \case
ATDRecord fields -> "\n**Record type**:\n" <> fieldsText "" (L.toList fields)
ATDEnum cs -> "\n**Enum type**:\n" <> foldMap (\m -> "- \"" <> T.pack m <> "\"\n") cs
ATDUnion cs -> "\n**Discriminated union type**:\n" <> foldMap constrText cs
where
constrText (ATUnionMember tag fields) =
("\n" <> T.pack (fstToUpper tag) <> ":\n")
<> ("- type: \"" <> T.pack tag <> "\"\n")
<> fieldsText "" fields
fieldsText :: FilePath -> [APIRecordField] -> Text
fieldsText docPath = foldMap $ T.pack . fieldText
where
fieldText (APIRecordField name t) = "- " <> name <> ": " <> typeText t <> "\n"
typeText = \case
ATPrim (PT t) -> t
ATDef (APITypeDef t _) -> withLink docPath t
ATRef t -> withLink docPath t
ATOptional t -> typeText t <> "?"
ATArray {elemType} -> "[" <> typeText elemType <> "]"
ATMap (PT t) valueType -> "{" <> t <> " : " <> typeText valueType <> "}"
autoGenerated :: Text
autoGenerated = "This file is generated automatically."
withLink :: FilePath -> String -> String
withLink docPath s = "[" <> s <> "](" <> docPath <> "#" <> headerAnchor s <> ")"
where
headerAnchor = map $ \c -> if isSpace c then '-' else toLower c
+206
View File
@@ -0,0 +1,206 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module API.Docs.Responses where
import API.Docs.Types
import API.TypeInfo
import Data.List (find)
import GHC.Generics
import Simplex.Chat.Controller
import Simplex.Messaging.Parsers (dropPrefix)
data CRDoc = CRDoc
{ consName :: ConsName,
responseType :: ATUnionMember,
responseDescr :: String
}
instance ConstructorName CRDoc where consName' CRDoc {consName} = consName
chatResponsesDocs :: [CRDoc]
chatResponsesDocs = map toResp chatResponsesDocsData
where
toResp (consName, responseDescr)
| consName == "CRChatCmdError" =
let field = toAPIField consName $ FieldInfo "chatError" (ti "ChatError")
responseType = ATUnionMember (dropPrefix "CR" consName) [field]
in CRDoc {consName, responseType, responseDescr}
| otherwise = case find ((consName ==) . consName') chatResponsesTypeInfo of
Just RecordTypeInfo {fieldInfos} ->
let fields = map (toAPIField consName) fieldInfos
responseType = ATUnionMember (dropPrefix "CR" consName) fields
in CRDoc {consName, responseType, responseDescr}
Nothing -> error $ "Missing response type info for " <> consName
deriving instance Generic ChatResponse
chatResponsesTypeInfo :: [RecordTypeInfo]
chatResponsesTypeInfo = recordTypesInfo @ChatResponse
chatResponsesDocsData :: [(ConsName, String)]
chatResponsesDocsData =
[ ("CRAcceptingContactRequest", "Contact request accepted"),
("CRActiveUser", "Active user profile"),
("CRChatItemNotChanged", "Message not changed"),
("CRChatItemReaction", "Message reaction"),
("CRChatItemUpdated", "Message updated"),
("CRChatItemsDeleted", "Messages deleted"),
("CRCmdOk", "Ok"),
("CRChatCmdError", "Command error"), -- only used in WebSockets API, Haskell code uses Either, with error in Left
("CRConnectionPlan", "Connection link information"),
("CRContactAlreadyExists", ""),
("CRContactConnectionDeleted", "Connection deleted"),
("CRContactDeleted", ""),
("CRContactRequestRejected", ""),
("CRContactsList", "Contacts"),
("CRGroupDeletedUser", "User deleted group"),
("CRGroupLink", ""),
("CRGroupLinkCreated", ""),
("CRGroupLinkDeleted", ""),
("CRGroupsList", "Groups"),
("CRInvitation", "One-time invitation"),
("CRLeftMemberUser", "User left group"),
("CRMemberAccepted", "Member accepted to group"),
("CRMembersBlockedForAllUser", "Members blocked for all by admin"),
("CRMembersRoleUser", "Members role changed by user"),
("CRNewChatItems", "New messages"),
("CRRcvFileAccepted", "File accepted to be received"),
("CRRcvFileAcceptedSndCancelled", "File accepted, but no longer sent"),
("CRRcvFileCancelled", "Cancelled receiving file"),
("CRSentConfirmation", "Confirmation sent to one-time invitation"),
("CRSentGroupInvitation", "Group invitation sent"),
("CRSentInvitation", "Invitation sent to contact address"),
("CRSndFileCancelled", "Cancelled sending file"),
("CRUserAcceptedGroupSent", "User accepted group invitation"),
("CRUserContactLink", "User contact address"),
("CRUserContactLinkCreated", "User contact address created"),
("CRUserContactLinkDeleted", "User contact address deleted"),
("CRUserContactLinkUpdated", "User contact address updated"),
("CRUserDeletedMembers", "Members deleted"),
("CRUserProfileUpdated", "User profile updated"),
("CRUsersList", "Users")
-- ("CRApiChat", "Chat and messages"),
-- ("CRApiChats", "Chats with the most recent messages"),
-- ("CRChatCleared", ""),
-- ("CRChatItemInfo", "Message information"),
-- ("CRChatItems", "The most recent messages"),
-- ("CRConnectionAliasUpdated", ""),
-- ("CRContactAliasUpdated", ""),
-- ("CRContactPrefsUpdated", "Contact preferences updated"),
-- ("CRContactRatchetSyncStarted", "Contact encryption synchronization started"),
-- ("CRGroupAliasUpdated", ""),
-- ("CRGroupCreated", ""),
-- ("CRGroupMemberRatchetSyncStarted", "Member encryption synchronization started"),
-- ("CRGroupMembers", ""),
-- ("CRGroupUpdated", ""),
-- ("CRItemsReadForChat", "Messages marked as read"),
-- ("CRReactionMembers", "Members who set reaction on the message"),
]
undocumentedResponses :: [ConsName]
undocumentedResponses =
[ "CRAgentQueuesInfo",
"CRAgentServersSummary",
"CRAgentSubs",
"CRAgentSubsDetails",
"CRAgentSubsTotal",
"CRAgentWorkersDetails",
"CRAgentWorkersSummary",
"CRApiChat",
"CRApiChats",
"CRAppSettings",
"CRArchiveExported",
"CRArchiveImported",
"CRBroadcastSent",
"CRCallInvitations",
"CRChatCleared",
"CRChatHelp",
"CRChatItemId",
"CRChatItemInfo",
"CRChatItems",
"CRChatItemTTL",
"CRChatRunning",
"CRChats",
"CRChatStarted",
"CRChatStopped",
"CRChatTags",
"CRConnectionAliasUpdated",
"CRConnectionIncognitoUpdated",
"CRConnectionUserChanged",
"CRConnectionVerified",
"CRConnNtfMessages",
"CRContactAliasUpdated",
"CRContactCode",
"CRContactInfo",
"CRContactPrefsUpdated",
"CRContactRatchetSyncStarted",
"CRContactSwitchAborted",
"CRContactSwitchStarted",
"CRContactUserChanged",
"CRCurrentRemoteHost",
"CRCustomChatResponse",
"CRDebugLocks",
"CRFileTransferStatus",
"CRFileTransferStatusXFTP",
"CRForwardPlan",
"CRGroupAliasUpdated",
"CRGroupChatItemsDeleted",
"CRGroupCreated",
"CRGroupDescription",
"CRGroupInfo",
"CRGroupMemberCode",
"CRGroupMemberInfo",
"CRGroupMemberRatchetSyncStarted",
"CRGroupMembers",
"CRGroupMemberSwitchAborted",
"CRGroupMemberSwitchStarted",
"CRGroupProfile",
"CRGroupUpdated",
"CRGroupUserChanged",
"CRItemsReadForChat",
"CRJoinedGroupMember",
"CRMemberSupportChatDeleted",
"CRMemberSupportChats",
"CRNetworkConfig",
"CRNetworkStatuses",
"CRNewMemberContact",
"CRNewMemberContactSentInv",
"CRNewPreparedChat",
"CRNtfConns",
"CRNtfToken",
"CRNtfTokenStatus",
"CRQueueInfo",
"CRRcvStandaloneFileCreated",
"CRReactionMembers",
"CRRemoteCtrlConnected",
"CRRemoteCtrlConnecting",
"CRRemoteCtrlList",
"CRRemoteFileStored",
"CRRemoteHostList",
"CRRemoteHostStarted",
"CRSentInvitationToContact",
"CRServerOperatorConditions",
"CRServerTestResult",
"CRSlowSQLQueries",
"CRSndStandaloneFileCreated",
"CRSQLResult",
"CRStandaloneFileInfo",
"CRStartedConnectionToContact",
"CRStartedConnectionToGroup",
"CRTagsUpdated",
"CRUsageConditions",
"CRUserPrivacy",
"CRUserProfile",
"CRUserProfileImage",
"CRUserProfileNoChange",
"CRUserServers",
"CRUserServersValidation",
"CRVersionInfo",
"CRWelcome"
]
+220
View File
@@ -0,0 +1,220 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
module API.Docs.Syntax where
import API.Docs.Syntax.Types
import API.Docs.Types
import API.TypeInfo
import Data.List (find, intercalate)
import qualified Data.List.NonEmpty as L
import Data.Maybe (mapMaybe)
import Data.Text (Text)
import qualified Data.Text as T
docSyntaxText :: TypeAndFields -> Expr -> Text
docSyntaxText r@(tag, _) = T.pack . go Nothing
where
go param = \case
Concat exs -> concatMap (go param) exs
Const s -> s
Param p ->
withParamType r param p $ \case
ATDef td -> strSyntax td
ATOptional (ATDef td) -> strSyntax td
_ -> defSyntax Nothing
where
strSyntax (APITypeDef typeName t)
| typeHasSyntax typeName = "<str(" <> paramName param p <> ")>"
| otherwise = defSyntax (Just t)
defSyntax = \case
Just (ATDEnum ms) -> intercalate "|" $ L.toList ms
_ -> "<" <> paramName param p <> ">"
Optional exN exJ p ->
withParamType r param p $ \case
ATOptional {}
| exN == "" -> "[" <> go (Just p) exJ <> "]"
| otherwise -> go param exN <> "|" <> go (Just p) exJ
_ -> paramError r param p "is not optional"
Choice p opts else' ->
withParamType r param p $ \case
ATDef td -> choiceSyntax td
ATOptional (ATDef td) -> choiceSyntax td
_ -> paramError r param p "is not union type"
where
choiceSyntax = \case
APITypeDef _ (ATDUnion _) -> choices
APITypeDef _ (ATDEnum _) -> choices
_ -> paramError r param p "is not union or enum type"
choices = (if null optsSyntax then "" else optsSyntax <> "|") <> go param else'
where
optsSyntax = intercalate "|" (mapMaybe ((\s -> if null s then Nothing else Just s) . go param . snd) (L.toList opts))
Join c p ->
withParamType r param p $ \case
ATArray {} -> let n = paramName param p in "<" <> n <> "[0]>[" <> [c] <> "<" <> n <> "[1]>...]"
_ -> paramError r param p "is not array"
Json p ->
withParamType r param p $ \_ -> "<json(" <> paramName param p <> ")>"
OnOff p -> withBoolParam r param p "on|off"
OnOffParam name p def_
| null name -> error $ fstToUpper tag <> ": on/off parameter " <> paramName param p <> " has empty name"
| otherwise -> case def_ of
Just def -> withOptBoolParam r param p $ \_ -> "[ " <> name <> "=" <> onOff <> "]"
where
onOff = if def then "off" else "on"
Nothing -> withOptBoolParam r param p $ \optional -> if optional then "[" <> res <> "]" else res
where
res = " " <> name <> "=on|off"
typeHasSyntax :: String -> Bool
typeHasSyntax typeName = case find ((typeName ==) . docTypeName) chatTypesDocs of
Just CTDoc {typeSyntax} -> typeSyntax /= ""
_ -> False
paramError :: TypeAndFields -> Maybe ExprParam -> ExprParam -> String -> String
paramError (tag, _) param p err = error $ fstToUpper tag <> ": " <> paramName param p <> " " <> err
withParamType :: TypeAndFields -> Maybe ExprParam -> ExprParam -> (APIType -> String) -> String
withParamType r@(_, params) param p f = case find ((paramName param p ==) . fieldName') params of
Just APIRecordField {typeInfo} -> f typeInfo
Nothing -> paramError r param p "is unknown"
withBoolParam :: TypeAndFields -> Maybe ExprParam -> ExprParam -> String -> String
withBoolParam r param p s =
withParamType r param p $ \case
ATPrim (PT TBool) -> s
_ -> paramError r param p "is not boolean"
withOptBoolParam :: TypeAndFields -> Maybe ExprParam -> ExprParam -> (Bool -> String) -> String
withOptBoolParam r param p f =
withParamType r param p $ \case
ATPrim (PT TBool) -> f False
(ATOptional (ATPrim (PT TBool))) -> f True
_ -> paramError r param p "is not [optional] boolean"
jsSyntaxText :: TypeAndFields -> Expr -> Text
jsSyntaxText r = T.replace "' + '" "" . T.pack . go Nothing True
where
go param top = \case
Concat exs -> intercalate " + " $ map (go param False) $ L.toList exs
Const s -> "'" <> escapeChar '\'' s <> "'"
Param p ->
withParamType r param p $ \case
ATDef td -> toStringSyntax td
ATOptional (ATDef td) -> toStringSyntax td
_ -> paramName param p
where
toStringSyntax (APITypeDef typeName _)
| typeHasSyntax typeName = paramName param p <> ".toString()"
| otherwise = paramName param p
Optional exN exJ p -> open <> n <> " ? " <> go (Just p) False exJ <> " : " <> nothing <> close
where
n = paramName param p
nothing = if exN == "" then "''" else go param False exN
Choice p opts else' ->
withParamType r param p $ \case
ATDef td -> choiceSyntax td
ATOptional (ATDef td) -> choiceSyntax td
_ -> paramError r param p "is not union type"
where
choiceSyntax = \case
APITypeDef _ (ATDUnion _) -> choices "type"
APITypeDef _ (ATDEnum _) -> choices "self"
_ -> paramError r param p "is not union type"
choices var = open <> optsSyntax <> " : " <> go param top else' <> close
where
optsSyntax = intercalate " : " $ map (\(tag, ex) -> var <> " == '" <> tag <> "' ? " <> go param top ex) $ L.toList opts
Join c p -> paramName param p <> ".join('" <> [c] <> "')"
Json p -> "JSON.stringify(" <> paramName param p <> ")"
OnOff p -> open <> paramName param p <> " ? 'on' : 'off'" <> close
OnOffParam name p def_ -> case def_ of
Nothing ->
withOptBoolParam r param p $ \optional ->
if optional
then "(typeof " <> n <> " == 'boolean' ? " <> res <> " : '')"
else res
where
n = paramName param p
res = "' " <> name <> "=' + (" <> n <> " ? 'on' : 'off')"
Just def
| def -> open <> "!" <> n <> " ? ' " <> name <> "=off' : ''" <> close
| otherwise -> open <> n <> " ? ' " <> name <> "=on' : ''" <> close
where
n = paramName param p
where
open = if top then "" else "("
close = if top then "" else ")"
escapeChar :: Char -> String -> String
escapeChar c s
| c `elem` s = concatMap (\c' -> if c' == c then ['\\', c] else [c]) s
| otherwise = s
pySyntaxText :: TypeAndFields -> Expr -> Text
pySyntaxText r = T.pack . go Nothing True
where
go param top = \case
Concat exs -> intercalate " + " $ map (go param False) $ L.toList exs
Const s -> "'" <> escapeChar '\'' s <> "'"
Param p ->
withParamType r param p $ \case
ATPrim (PT TString) -> paramName param p
ATOptional (ATPrim (PT TString)) -> paramName param p
_ -> "str(" <> paramName param p <> ")"
Optional exN exJ p -> open <> "(" <> go (Just p) False exJ <> ") if " <> n <> " is not None else " <> nothing <> close
where
n = paramName param p
nothing = if exN == "" then "''" else go param False exN
Choice p opts else' ->
withParamType r param p $ \case
ATDef td -> choiceSyntax td
ATOptional (ATDef td) -> choiceSyntax td
_ -> paramError r param p "is not union type"
where
choiceSyntax = \case
APITypeDef _ (ATDUnion _) -> choices "type"
APITypeDef _ (ATDEnum _) -> choices "self"
_ -> paramError r param p "is not union type"
choices var = open <> optsSyntax <> " else " <> go param top else' <> close
where
optsSyntax = intercalate " else " $ map (\(tag, ex) -> go param top ex <> " if " <> var' <> " == '" <> tag <> "'") $ L.toList opts
var' =
withParamType r param var $ \case
ATPrim (PT TString) -> var
ATOptional (ATPrim (PT TString)) -> var
_ -> "str(" <> var <> ")"
Join c p ->
withParamType r param p $ \case
ATArray {elemType = ATPrim (PT TString)} -> "'" <> [c] <> "'.join(" <> paramName param p <> ")"
_ -> "'" <> [c] <> "'.join(map(str, " <> paramName param p <> "))"
Json p -> "json.dumps(" <> paramName param p <> ")"
OnOff p -> open <> "'on' if " <> paramName param p <> " else 'off'" <> close
OnOffParam name p def_ -> case def_ of
Nothing ->
withOptBoolParam r param p $ \optional ->
if optional
then "((" <> res <> ") if " <> n <> " is not None else '')"
else res
where
n = paramName param p
res = "' " <> name <> "=' + ('on' if " <> n <> " else 'off')"
Just def
| def -> open <> "' " <> name <> "=off' if not " <> n <> " else ''" <> close
| otherwise -> open <> "' " <> name <> "=on' if " <> n <> " else ''" <> close
where
n = paramName param p
where
open = if top then "" else "("
close = if top then "" else ")"
paramName :: Maybe ExprParam -> ExprParam -> String
paramName param_ p = case param_ of
Just param | p == "$0" -> param
_ -> p
+30
View File
@@ -0,0 +1,30 @@
{-# LANGUAGE LambdaCase #-}
module API.Docs.Syntax.Types where
import Data.List.NonEmpty (NonEmpty)
import Data.Semigroup
import Data.String
type ExprParam = String -- param name
data Expr
= Concat (NonEmpty Expr)
| Const String
| Param ExprParam
| Optional Expr Expr ExprParam -- Nothing expr, Just expr (using [$0] as ExprParam), optional param
| Choice ExprParam (NonEmpty (String, Expr)) Expr -- union type param, choices for "type" tags, else
| Join Char ExprParam
| Json ExprParam
| OnOff ExprParam -- does not include leading space
| OnOffParam String ExprParam (Maybe Bool) -- name, param, default. Includes leading space in all cases. Name must not be empty
deriving (Eq, Show)
isConst :: Expr -> Bool
isConst = \case
Const _ -> True
_ -> False
instance IsString Expr where fromString = Const
instance Semigroup Expr where sconcat = Concat
+557
View File
@@ -0,0 +1,557 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module API.Docs.Types where
import API.Docs.Syntax.Types
import API.TypeInfo
import Data.Bifunctor (second)
import Data.Char (isUpper, toLower, toUpper)
import Data.List (find, mapAccumL, sortOn)
import qualified Data.List.NonEmpty as L
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import qualified Data.Set as S
import Data.Text (Text)
import GHC.Generics
import Simplex.Chat.Controller
import Simplex.Chat.Markdown
import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Messages.CIContent.Events
import Simplex.Chat.Protocol
import Simplex.Chat.Store.Groups
import Simplex.Chat.Store.Profiles
import Simplex.Chat.Store.Shared
import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared
import Simplex.Chat.Types.UITheme
import Simplex.FileTransfer.Transport
import Simplex.FileTransfer.Types hiding (RcvFileStatus) -- the type with the same name is used in simplex-chat.
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Client
import Simplex.Messaging.Crypto.File
import Simplex.Messaging.Parsers (dropPrefix, fstToLower)
import Simplex.Messaging.Protocol (BlockingInfo (..), BlockingReason (..), CommandError (..), ErrorType (..), ProxyError (..))
import Simplex.Messaging.Transport
import Simplex.RemoteControl.Types
import System.Console.ANSI.Types (Color (..))
data CTDoc = CTDoc
{ typeDef :: APITypeDef,
typeSyntax :: Expr, -- syntax for types used in commands
typeDescr :: Text
}
docTypeName :: CTDoc -> String
docTypeName CTDoc {typeDef = APITypeDef name _} = name
toAPIField :: ConsName -> FieldInfo -> APIRecordField
toAPIField typeName = snd . toAPIField_ typeName (S.empty, chatTypeDefs)
chatTypeDefs :: M.Map String APITypeDef
chatTypeDefs = M.fromList $ map (\CTDoc {typeDef = td@(APITypeDef name _)} -> (name, td)) chatTypesDocs
chatTypesDocs :: [CTDoc]
chatTypesDocs = sortOn docTypeName $! snd $! mapAccumL toCTDoc (S.empty, M.empty) chatTypesDocsData
where
toCTDoc !tds sumTypeInfo@(STI typeName _, _, _, _, typeSyntax, typeDescr) =
let (tds', td_) = toTypeDef tds sumTypeInfo
in case td_ of
Just typeDef -> (tds', CTDoc {typeDef, typeSyntax, typeDescr})
Nothing -> error $ "Recursive type: " <> typeName
toTypeDef :: (S.Set String, M.Map String APITypeDef) -> (SumTypeInfo, SumTypeJsonEncoding, String, [ConsName], Expr, Text) -> ((S.Set String, M.Map String APITypeDef), Maybe APITypeDef)
toTypeDef acc@(!visited, !typeDefs) (STI typeName allConstrs, jsonEncoding, consPrefix, hideConstrs, _, _) =
let constrs = filter ((`notElem` hideConstrs) . consName') allConstrs
in case M.lookup typeName typeDefs of
Just td -> (acc, Just td)
Nothing
| S.member typeName visited -> (acc, Nothing)
| otherwise -> case jsonEncoding of
STRecord -> case constrs of
[RecordTypeInfo {fieldInfos}] ->
let fields = fromMaybe (error $ "Record type without fields: " <> typeName) $ L.nonEmpty fieldInfos
((visited', typeDefs'), fields') = mapAccumL (toAPIField_ typeName) (S.insert typeName visited, typeDefs) fields
td = APITypeDef typeName $ ATDRecord fields'
in ((S.insert typeName visited', M.insert typeName td typeDefs'), Just td)
_ -> error $ "Record type with " <> show (length constrs) <> " constructors: " <> typeName
STUnion -> if length constrs > 1 then toUnionType constrs else unionError constrs
STUnion1 -> if length constrs == 1 then toUnionType constrs else unionError constrs
STEnum -> if length constrs > 1 then toEnumType constrs else enumError constrs
STEnum1 -> if length constrs == 1 then toEnumType constrs else enumError constrs
STEnum' f
| length constrs <= 1 -> enumError constrs
| null consPrefix -> toEnumType_ f constrs
| otherwise -> error $ "Enum type with custom encoding and prefix: " <> typeName
where
toUnionType constrs =
let ((visited', typeDefs'), members) = mapAccumL toUnionMember (S.insert typeName visited, typeDefs) $ fromMaybe (unionError constrs) $ L.nonEmpty constrs
td = APITypeDef typeName $ ATDUnion members
in ((S.insert typeName visited', M.insert typeName td typeDefs'), Just td)
toUnionMember tds RecordTypeInfo {consName, fieldInfos} =
let memberTag = normalizeConsName consPrefix consName
in second (ATUnionMember memberTag) $ mapAccumL (toAPIField_ typeName) tds fieldInfos
unionError constrs = error $ "Union type with " <> show (length constrs) <> " constructor(s): " <> typeName
toEnumType = toEnumType_ $ normalizeConsName consPrefix
toEnumType_ f constrs =
let members = L.map toEnumMember $ fromMaybe (enumError constrs) $ L.nonEmpty constrs
td = APITypeDef typeName $ ATDEnum members
in ((S.insert typeName visited, M.insert typeName td typeDefs), Just td)
where
toEnumMember RecordTypeInfo {consName, fieldInfos} = case fieldInfos of
[] -> f consName
_ -> error $ "Enum type with fields in constructor: " <> typeName <> ", " <> consName
enumError constrs = error $ "Enum type with " <> show (length constrs) <> " constructor(s): " <> typeName
toAPIField_ :: ConsName -> (S.Set String, M.Map String APITypeDef) -> FieldInfo -> ((S.Set String, M.Map String APITypeDef), APIRecordField)
toAPIField_ typeName tds (FieldInfo fieldName typeInfo) = second (APIRecordField fieldName) $ toAPIType typeInfo
where
toAPIType :: TypeInfo -> ((S.Set String, M.Map String APITypeDef), APIType)
toAPIType = \case
TIType (ST name _) -> apiTypeForName name
TIOptional tInfo -> second ATOptional $ toAPIType tInfo
TIArray {elemType, nonEmpty} -> second (`ATArray`nonEmpty) $ toAPIType elemType
TIMap {keyType = ST name _, valueType}
| name `elem` primitiveTypes -> second (ATMap (PT name)) $ toAPIType valueType
| otherwise -> error $ "Non-primitive key type in " <> typeName <> ", " <> fieldName
apiTypeForName :: String -> ((S.Set String, M.Map String APITypeDef), APIType)
apiTypeForName name
| name `elem` primitiveTypes = (tds, ATPrim $ PT name)
| otherwise = case M.lookup name $ snd tds of
Just td -> (tds, ATDef td)
Nothing -> case find (\(STI name' _, _, _, _, _, _) -> name == name') chatTypesDocsData of
Just sumTypeInfo ->
let (tds', td_) = toTypeDef tds sumTypeInfo -- recursion to outer function, loops are resolved via type defs map lookup
in case td_ of
Just td -> (tds', ATDef td)
Nothing -> (tds', ATRef name)
Nothing -> error $ "Undefined type: " <> name
data SumTypeJsonEncoding = STRecord | STUnion | STUnion1 | STEnum | STEnum1 | STEnum' (ConsName -> String)
dropPfxSfx :: String -> String -> ConsName -> String
dropPfxSfx pfx sfx = dropSuffix sfx . dropPrefix pfx
fstToUpper :: String -> String
fstToUpper "" = ""
fstToUpper (h : t) = toUpper h : t
consLower :: String -> ConsName -> String
consLower pfx = map toLower . dropPrefix pfx
consSep :: String -> Char -> ConsName -> String
consSep pfx sep = foldr (\c s -> if isUpper c then sep : toLower c : s else c : s) "" . dropPrefix pfx
dropSuffix :: String -> String -> String
dropSuffix sfx s =
let (s', sfx') = splitAt (length s - length sfx) s
in fstToLower $ if sfx' == sfx then s' else s
normalizeConsName :: String -> ConsName -> ConsName
normalizeConsName pfx consName
| null pfx && uppercase = consName
| null pfx = fstToLower consName
| uppercase = map toUpper noPfx
| otherwise = noPfx
where
uppercase = all (\c -> isUpper c || c == '_') consName
noPfx = dropPrefix pfx consName
-- making chatDir optional because clients use CIDirection? instead of CIQDirection (the type is replaced in Types.hs)
ciQuoteType :: SumTypeInfo
ciQuoteType =
let st@(STI _ records) = sti @(CIQuote 'CTDirect)
optChatDir f@(FieldInfo n t) = if n == "chatDir" then FieldInfo n (TIOptional t) else f
updateRecord (RecordTypeInfo name fields) = RecordTypeInfo name $ map optChatDir fields
in st {recordTypes = map updateRecord records} -- need to map even though there is one constructor in this type
chatTypesDocsData :: [(SumTypeInfo, SumTypeJsonEncoding, String, [ConsName], Expr, Text)]
chatTypesDocsData =
[ ((sti @(Chat 'CTDirect)) {typeName = "AChat"}, STRecord, "", [], "", ""),
((sti @JSONChatInfo) {typeName = "ChatInfo"}, STUnion, "JCInfo", [], "", ""),
((sti @JSONCIContent) {typeName = "CIContent"}, STUnion, "JCI", [], "", ""),
((sti @JSONCIDeleted) {typeName = "CIDeleted"}, STUnion, "JCID", [], "", ""),
((sti @JSONCIDirection) {typeName = "CIDirection"}, STUnion, "JCI", [], "", ""),
((sti @JSONCIFileStatus) {typeName = "CIFileStatus"}, STUnion, "JCIFS", [], "", ""),
((sti @JSONCIStatus) {typeName = "CIStatus"}, STUnion, "JCIS", [], "", ""),
(ciQuoteType, STRecord, "", [], "", ""),
(STI "AChatItem" [RecordTypeInfo "AChatItem" [FieldInfo "chatInfo" (ti "ChatInfo"), FieldInfo "chatItem" (ti "ChatItem")]], STRecord, "", [], "", ""),
(STI "ACIReaction" [RecordTypeInfo "ACIReaction" [FieldInfo "chatInfo" (ti "ChatInfo"), FieldInfo "chatReaction" (ti "CIReaction")]], STRecord, "", [], "", ""),
-- (STI "JSONObject" [], STRecord, "", [], "Arbitrary JSON object."),
-- (STI "UTCTime" [], STRecord, "", [], "Timestampe in ISO8601 format as string."),
(STI "VersionRange" [RecordTypeInfo "VersionRange" [FieldInfo "minVersion" (ti TInt), FieldInfo "maxVersion" (ti TInt)]], STRecord, "", [], "", ""),
(sti @(ChatItem 'CTDirect 'MDSnd), STRecord, "", [], "", ""),
(sti @(CIFile 'MDSnd), STRecord, "", [], "", ""),
(sti @(CIMeta 'CTDirect 'MDSnd), STRecord, "", [], "", ""),
(sti @(CIReaction 'CTDirect 'MDSnd), STRecord, "", [], "", ""),
(sti @(ContactUserPref SimplePreference), STUnion, "CUP", [], "", ""),
(sti @(ContactUserPreference SimplePreference), STRecord, "", [], "", ""),
(sti @(CreatedConnLink 'CMContact), STRecord, "", [], Param "connFullLink" <> Optional "" (" " <> Param "$0") "connShortLink", ""),
(sti @AddressSettings, STRecord, "", [], "", ""),
(sti @AgentCryptoError, STUnion, "", [], "", ""),
(sti @AgentErrorType, STUnion, "", [], "", ""),
(sti @AutoAccept, STRecord, "", [], "", ""),
(sti @BlockingInfo, STRecord, "", [], "", ""),
(sti @BlockingReason, STEnum, "BR", [], "", ""),
(sti @BrokerErrorType, STUnion, "", [], "", ""),
(sti @BusinessChatInfo, STRecord, "", [], "", ""),
(sti @BusinessChatType, STEnum, "BC", [], "", ""),
(sti @ChatDeleteMode, STUnion, "CDM", [], Param "type" <> Choice "self" [("messages", "")] (OnOffParam "notify" "notify" (Just True)), ""),
(sti @ChatError, STUnion, "Chat", ["ChatErrorDatabase", "ChatErrorRemoteHost", "ChatErrorRemoteCtrl"], "", ""),
(sti @ChatErrorType, STUnion, "CE", ["CEContactNotFound", "CEServerProtocol", "CECallState", "CEInvalidChatMessage"], "", ""),
(sti @ChatFeature, STEnum, "CF", [], "", ""),
(sti @ChatItemDeletion, STRecord, "", [], "", "Message deletion result."),
(sti @ChatRef, STRecord, "", [], Param "chatType" <> Param "chatId" <> Optional "" (Param "$0") "chatScope", "Used in API commands. Chat scope can only be passed with groups."),
(sti @ChatSettings, STRecord, "", [], "", ""),
(sti @ChatStats, STRecord, "", [], "", ""),
(sti @ChatType, STEnum, "CT", ["CTContactRequest", "CTContactConnection"], Choice "self" [("contact", "@"), ("group", "#"), ("local", "*")] "", ""),
(sti @ChatWallpaper, STRecord, "", [], "", ""),
(sti @ChatWallpaperScale, STEnum, "CWS", [], "", ""),
(sti @CICallStatus, STEnum, "CISCall", [], "", ""),
(sti @CIDeleteMode, STEnum, "CIDM", [], "", ""),
(sti @CIForwardedFrom, STUnion, "CIFF", [], "", ""),
(sti @CIGroupInvitation, STRecord, "", [], "", ""),
(sti @CIGroupInvitationStatus, STEnum, "CIGIS", [], "", ""),
(sti @CIMention, STRecord, "", [], "", ""),
(sti @CIMentionMember, STRecord, "", [], "", ""),
(sti @CIReactionCount, STRecord, "", [], "", ""),
(sti @CITimed, STRecord, "", [], "", ""),
(sti @Color, STEnum, "", [], "", ""),
(sti @CommandError, STUnion, "", [], "", ""),
(sti @CommandErrorType, STUnion, "", [], "", ""),
(sti @ComposedMessage, STRecord, "", [], "", ""),
(sti @Connection, STRecord, "", [], "", ""),
(sti @ConnectionEntity, STUnion, "", [], "", ""),
(sti @ConnectionErrorType, STUnion, "", [], "", ""),
(sti @ConnectionMode, (STEnum' $ take 3 . consLower "CM"), "", [], "", ""),
(sti @ConnectionPlan, STUnion, "CP", [], "", ""),
(sti @ConnStatus, (STEnum' $ consSep "Conn" '-'), "", [], "", ""),
(sti @ConnType, (STEnum' $ consSep "Conn" '_'), "", ["ConnSndFile", "ConnRcvFile"], "", ""),
(sti @Contact, STRecord, "", [], "", ""),
(sti @ContactAddressPlan, STUnion, "CAP", [], "", ""),
(sti @ContactShortLinkData, STRecord, "", [], "", ""),
(sti @ContactStatus, STEnum, "CS", [], "", ""),
(sti @ContactUserPreferences, STRecord, "", [], "", ""),
(sti @CryptoFile, STRecord, "", [], "", ""),
(sti @CryptoFileArgs, STRecord, "", [], "", ""),
(sti @E2EInfo, STRecord, "", [], "", ""),
(sti @ErrorType, STUnion, "", [], "", ""),
(sti @FeatureAllowed, STEnum, "FA", [], "", ""),
(sti @FileDescr, STRecord, "", [], "", ""),
(sti @FileError, STUnion, "FileErr", [], "", ""),
(sti @FileErrorType, STUnion, "", [], "", ""),
(sti @FileInvitation, STRecord, "", [], "", ""),
(sti @FileProtocol, (STEnum' $ consLower "FP"), "", [], "", ""),
(sti @FileStatus, STEnum, "FS", [], "", ""),
(sti @FileTransferMeta, STRecord, "", [], "", ""),
(sti @Format, STUnion, "", [], "", ""),
(sti @FormattedText, STRecord, "", [], "", ""),
(sti @FullGroupPreferences, STRecord, "", [], "", ""),
(sti @FullPreferences, STRecord, "", [], "", ""),
(sti @GroupChatScope, STUnion1, "GCS", [], "(_support" <> Optional "" (":" <> Param "$0") "groupMemberId_" <> ")", ""),
(sti @GroupChatScopeInfo, STUnion1, "GCSI", [], "", ""),
(sti @GroupFeature, STEnum, "GF", [], "", ""),
(sti @GroupFeatureEnabled, STEnum, "FE", [], "", ""),
(sti @GroupInfo, STRecord, "", [], "", ""),
(sti @GroupInfoSummary, STRecord, "", [], "", ""),
(sti @GroupLink, STRecord, "", [], "", ""),
(sti @GroupLinkPlan, STUnion, "GLP", [], "", ""),
(sti @GroupMember, STRecord, "", [], "", ""),
(sti @GroupMemberAdmission, STRecord, "", [], "", ""),
(sti @GroupMemberCategory, (STEnum' $ dropPfxSfx "GC" "Member"), "", [], "", ""),
(sti @GroupMemberRef, STRecord, "", [], "", ""),
(sti @GroupMemberRole, STEnum, "GR", [], "", ""),
(sti @GroupMemberSettings, STRecord, "", [], "", ""),
(sti @GroupMemberStatus, (STEnum' $ (\case "group_deleted" -> "deleted"; "intro_invited" -> "intro-inv"; s -> s) . consSep "GSMem" '_'), "", [], "", ""),
(sti @GroupPreference, STRecord, "", [], "", ""),
(sti @GroupPreferences, STRecord, "", [], "", ""),
(sti @GroupProfile, STRecord, "", [], "", ""),
(sti @GroupShortLinkData, STRecord, "", [], "", ""),
(sti @GroupSummary, STRecord, "", [], "", ""),
(sti @GroupSupportChat, STRecord, "", [], "", ""),
(sti @HandshakeError, STEnum, "", [], "", ""),
(sti @InlineFileMode, STEnum, "IFM", [], "", ""),
(sti @InvitationLinkPlan, STUnion, "ILP", [], "", ""),
(sti @InvitedBy, STUnion, "IB", [], "", ""),
(sti @LinkContent, STUnion, "LC", [], "", ""),
(sti @LinkPreview, STRecord, "", [], "", ""),
(sti @LocalProfile, STRecord, "", [], "", ""),
(sti @MemberCriteria, STEnum1, "MC", [], "", ""),
(sti @MsgChatLink, STUnion, "MCL", [], "", "Connection link sent in a message - only short links are allowed."),
(sti @MsgContent, STUnion, "MC", [], "", ""),
(sti @MsgDecryptError, STEnum, "MDE", [], "", ""),
(sti @MsgDirection, STEnum, "MD", [], "", ""),
(sti @MsgErrorType, STUnion, "", [], "", ""), -- check, may be correct?
(sti @MsgFilter, STEnum, "MF", [], "", ""),
(sti @MsgReaction, STUnion, "MR", [], "", ""),
(sti @MsgReceiptStatus, STEnum, "MR", [], "", ""),
(sti @NewUser, STRecord, "", [], "", ""),
(sti @NoteFolder, STRecord, "", [], "", ""),
(sti @PendingContactConnection, STRecord, "", [], "", ""),
(sti @PrefEnabled, STRecord, "", [], "", ""),
(sti @Preferences, STRecord, "", [], "", ""),
(sti @PreparedContact, STRecord, "", [], "", ""),
(sti @PreparedGroup, STRecord, "", [], "", ""),
(sti @Profile, STRecord, "", [], "", ""),
(sti @ProxyClientError, STUnion, "Proxy", [], "", ""),
(sti @ProxyError, STUnion, "", [], "", ""),
(sti @RatchetSyncState, STEnum, "RS", [], "", ""),
(sti @RCErrorType, STUnion, "RCE", [], "", ""),
(sti @RcvConnEvent, STUnion, "RCE", [], "", ""),
(sti @RcvDirectEvent, STUnion, "RDE", [], "", ""),
(sti @RcvFileDescr, STRecord, "", [], "", ""),
(sti @RcvFileInfo, STRecord, "", [], "", ""),
(sti @RcvFileStatus, STUnion, "RFS", [], "", ""),
(sti @RcvFileTransfer, STRecord, "", [], "", ""),
(sti @RcvGroupEvent, STUnion, "RGE", [], "", ""),
(sti @ReportReason, (STEnum' $ dropPfxSfx "RR" ""), "", ["RRUnknown"], "", ""),
(sti @RoleGroupPreference, STRecord, "", [], "", ""),
(sti @SecurityCode, STRecord, "", [], "", ""),
(sti @SimplePreference, STRecord, "", [], "", ""),
(sti @SimplexLinkType, STEnum, "XL", [], "", ""),
(sti @SMPAgentError, STUnion, "", [], "", ""),
(sti @SndCIStatusProgress, STEnum, "SSP", [], "", ""),
(sti @SndConnEvent, STUnion, "SCE", [], "", ""),
(sti @SndError, STUnion, "SndErr", [], "", ""),
(sti @SndFileTransfer, STRecord, "", [], "", ""),
(sti @SndGroupEvent, STUnion, "SGE", [], "", ""),
(sti @SrvError, STUnion, "SrvErr", [], "", ""),
(sti @StoreError, STUnion, "SE", [], "", ""),
(sti @SwitchPhase, STEnum, "SP", [], "", ""),
(sti @TimedMessagesGroupPreference, STRecord, "", [], "", ""),
(sti @TimedMessagesPreference, STRecord, "", [], "", ""),
(sti @TransportError, STUnion, "TE", [], "", ""),
(sti @UIColorMode, STEnum, "UCM", [], "", ""),
(sti @UIColors, STRecord, "", [], "", ""),
(sti @UIThemeEntityOverride, STRecord, "", [], "", ""),
(sti @UIThemeEntityOverrides, STRecord, "", [], "", ""),
(sti @UpdatedMessage, STRecord, "", [], "", ""),
(sti @User, STRecord, "", [], "", ""),
(sti @UserContact, STRecord, "", [], "", ""),
(sti @UserContactLink, STRecord, "", [], "", ""),
(sti @UserContactRequest, STRecord, "", [], "", ""),
(sti @UserInfo, STRecord, "", [], "", ""),
(sti @UserProfileUpdateSummary, STRecord, "", [], "", ""),
(sti @UserPwdHash, STRecord, "", [], "", ""),
(sti @XFTPErrorType, STUnion, "", [], "", ""),
(sti @XFTPRcvFile, STRecord, "", [], "", ""),
(sti @XFTPSndFile, STRecord, "", [], "", "")
-- (sti @DatabaseError, STUnion, "DB", [], "", ""),
-- (sti @ChatItemInfo, STRecord, "", [], "", ""),
-- (sti @ChatItemVersion, STRecord, "", [], "", ""),
-- (sti @ChatListQuery, STUnion, "CLQ", [], "", ""),
-- (sti @ChatName, STRecord, "", [], "", ""),
-- (sti @ChatPagination, STRecord, "CP", [], "", ""),
-- (sti @ConnectionStats, STRecord, "", [], "", ""),
-- (sti @Group, STRecord, "", [], "", ""),
-- (sti @GroupSndStatus, STUnion, "GSS", [], "", ""),
-- (sti @MemberDeliveryStatus, STRecord, "", [], "", ""),
-- (sti @MemberReaction, STRecord, "", [], "", ""),
-- (sti @MsgContentTag, (STEnum' $ dropPfxSfx "MC" '_'), "", ["MCUnknown_"], "", ""),
-- (sti @NavigationInfo, STRecord, "", [], "", ""),
-- (sti @PaginationByTime, STRecord, "", [], "", ""),
-- (sti @RcvQueueInfo, STRecord, "", [], "", ""),
-- (sti @RcvSwitchStatus, STEnum, "", [], "", ""), -- incorrect
-- (sti @SendRef, STRecord, "", [], "", ""),
-- (sti @SndQueueInfo, STRecord, "", [], "", ""),
-- (sti @SndSwitchStatus, STEnum, "", [], "", ""), -- incorrect
]
data SimplePreference = SimplePreference {allow :: FeatureAllowed} deriving (Generic)
data RoleGroupPreference = RoleGroupPreference {enable :: GroupFeatureEnabled, role :: Maybe GroupMemberRole} deriving (Generic)
deriving instance Generic (Chat c)
deriving instance Generic (ChatItem c d)
deriving instance Generic (CIFile d)
deriving instance Generic (CIMeta c d)
deriving instance Generic (CIQuote d)
deriving instance Generic (CIReaction c d)
deriving instance Generic (ContactUserPref p)
deriving instance Generic (ContactUserPreference p)
deriving instance Generic (CreatedConnLink m)
deriving instance Generic AddressSettings
deriving instance Generic AgentCryptoError
deriving instance Generic AgentErrorType
deriving instance Generic AutoAccept
deriving instance Generic BlockingInfo
deriving instance Generic BlockingReason
deriving instance Generic BrokerErrorType
deriving instance Generic BusinessChatInfo
deriving instance Generic BusinessChatType
deriving instance Generic ChatDeleteMode
deriving instance Generic ChatError
deriving instance Generic ChatErrorType
deriving instance Generic ChatFeature
deriving instance Generic ChatItemDeletion
deriving instance Generic ChatRef
deriving instance Generic ChatSettings
deriving instance Generic ChatStats
deriving instance Generic ChatType
deriving instance Generic ChatWallpaper
deriving instance Generic ChatWallpaperScale
deriving instance Generic CICallStatus
deriving instance Generic CIDeleteMode
deriving instance Generic CIForwardedFrom
deriving instance Generic CIGroupInvitation
deriving instance Generic CIGroupInvitationStatus
deriving instance Generic CIMention
deriving instance Generic CIMentionMember
deriving instance Generic CIReactionCount
deriving instance Generic CITimed
deriving instance Generic Color
deriving instance Generic CommandError
deriving instance Generic CommandErrorType
deriving instance Generic ComposedMessage
deriving instance Generic Connection
deriving instance Generic ConnectionEntity
deriving instance Generic ConnectionErrorType
deriving instance Generic ConnectionMode
deriving instance Generic ConnectionPlan
deriving instance Generic ConnStatus
deriving instance Generic ConnType
deriving instance Generic Contact
deriving instance Generic ContactAddressPlan
deriving instance Generic ContactShortLinkData
deriving instance Generic ContactStatus
deriving instance Generic ContactUserPreferences
deriving instance Generic CryptoFile
deriving instance Generic CryptoFileArgs
deriving instance Generic E2EInfo
deriving instance Generic ErrorType
deriving instance Generic FeatureAllowed
deriving instance Generic FileDescr
deriving instance Generic FileError
deriving instance Generic FileErrorType
deriving instance Generic FileInvitation
deriving instance Generic FileProtocol
deriving instance Generic FileStatus
deriving instance Generic FileTransferMeta
deriving instance Generic Format
deriving instance Generic FormattedText
deriving instance Generic FullGroupPreferences
deriving instance Generic FullPreferences
deriving instance Generic GroupChatScope
deriving instance Generic GroupChatScopeInfo
deriving instance Generic GroupFeature
deriving instance Generic GroupFeatureEnabled
deriving instance Generic GroupInfo
deriving instance Generic GroupInfoSummary
deriving instance Generic GroupLink
deriving instance Generic GroupLinkPlan
deriving instance Generic GroupMember
deriving instance Generic GroupMemberAdmission
deriving instance Generic GroupMemberCategory
deriving instance Generic GroupMemberRef
deriving instance Generic GroupMemberRole
deriving instance Generic GroupMemberSettings
deriving instance Generic GroupMemberStatus
deriving instance Generic GroupPreference
deriving instance Generic GroupPreferences
deriving instance Generic GroupProfile
deriving instance Generic GroupShortLinkData
deriving instance Generic GroupSummary
deriving instance Generic GroupSupportChat
deriving instance Generic HandshakeError
deriving instance Generic InlineFileMode
deriving instance Generic InvitationLinkPlan
deriving instance Generic InvitedBy
deriving instance Generic JSONChatInfo
deriving instance Generic JSONCIContent
deriving instance Generic JSONCIDeleted
deriving instance Generic JSONCIDirection
deriving instance Generic JSONCIFileStatus
deriving instance Generic JSONCIStatus
deriving instance Generic LinkContent
deriving instance Generic LinkPreview
deriving instance Generic LocalProfile
deriving instance Generic MemberCriteria
deriving instance Generic MsgChatLink
deriving instance Generic MsgContent
deriving instance Generic MsgDecryptError
deriving instance Generic MsgDirection
deriving instance Generic MsgErrorType
deriving instance Generic MsgFilter
deriving instance Generic MsgReaction
deriving instance Generic MsgReceiptStatus
deriving instance Generic NewUser
deriving instance Generic NoteFolder
deriving instance Generic PendingContactConnection
deriving instance Generic PrefEnabled
deriving instance Generic Preferences
deriving instance Generic PreparedContact
deriving instance Generic PreparedGroup
deriving instance Generic Profile
deriving instance Generic ProxyClientError
deriving instance Generic ProxyError
deriving instance Generic RatchetSyncState
deriving instance Generic RCErrorType
deriving instance Generic RcvConnEvent
deriving instance Generic RcvDirectEvent
deriving instance Generic RcvFileDescr
deriving instance Generic RcvFileInfo
deriving instance Generic RcvFileStatus
deriving instance Generic RcvFileTransfer
deriving instance Generic RcvGroupEvent
deriving instance Generic ReportReason
deriving instance Generic SecurityCode
deriving instance Generic SimplexLinkType
deriving instance Generic SMPAgentError
deriving instance Generic SndCIStatusProgress
deriving instance Generic SndConnEvent
deriving instance Generic SndError
deriving instance Generic SndFileTransfer
deriving instance Generic SndGroupEvent
deriving instance Generic SrvError
deriving instance Generic StoreError
deriving instance Generic SwitchPhase
deriving instance Generic TimedMessagesGroupPreference
deriving instance Generic TimedMessagesPreference
deriving instance Generic TransportError
deriving instance Generic UIColorMode
deriving instance Generic UIColors
deriving instance Generic UIThemeEntityOverride
deriving instance Generic UIThemeEntityOverrides
deriving instance Generic UpdatedMessage
deriving instance Generic User
deriving instance Generic UserContact
deriving instance Generic UserContactLink
deriving instance Generic UserContactRequest
deriving instance Generic UserInfo
deriving instance Generic UserProfileUpdateSummary
deriving instance Generic UserPwdHash
deriving instance Generic XFTPErrorType
deriving instance Generic XFTPRcvFile
deriving instance Generic XFTPSndFile
-- deriving instance Generic DatabaseError
-- deriving instance Generic ChatItemInfo
-- deriving instance Generic ChatItemVersion
-- deriving instance Generic ChatListQuery
-- deriving instance Generic ChatName
-- deriving instance Generic ChatPagination
-- deriving instance Generic ConnectionStats
-- deriving instance Generic Group
-- deriving instance Generic GroupSndStatus
-- deriving instance Generic MemberDeliveryStatus
-- deriving instance Generic MemberReaction
-- deriving instance Generic MsgContentTag
-- deriving instance Generic NavigationInfo
-- deriving instance Generic PaginationByTime
-- deriving instance Generic RcvQueueInfo
-- deriving instance Generic RcvSwitchStatus
-- deriving instance Generic SendRef
-- deriving instance Generic SndQueueInfo
-- deriving instance Generic SndSwitchStatus
+224
View File
@@ -0,0 +1,224 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module API.TypeInfo where
import Data.Kind (Type)
import Data.List.NonEmpty (NonEmpty)
import Data.Typeable
import GHC.Generics
import Simplex.Messaging.Parsers (fstToLower)
data APIType
= ATPrim PrimitiveType
| ATDef APITypeDef
| ATRef String -- to support recursive types
| ATOptional APIType
| ATArray {elemType :: APIType, nonEmpty :: Bool}
| ATMap {keyType :: PrimitiveType, valueType :: APIType}
data APITypeDef = APITypeDef {typeName' :: String, typeDef :: APITypeDefinition}
data APITypeDefinition
= ATDRecord (NonEmpty APIRecordField)
| ATDUnion (NonEmpty ATUnionMember)
| ATDEnum (NonEmpty String)
type TypeAndFields = (String, [APIRecordField])
data APIRecordField = APIRecordField {fieldName' :: String, typeInfo :: APIType}
data ATUnionMember = ATUnionMember {memberTag :: String, memberFields :: [APIRecordField]}
newtype PrimitiveType = PT String
-- data PrimitiveType = PTBool | PTString StringFormat | PTInt | PTInt64 | PTWord32 | PTDouble
data StringFormat = SFTimestamp | SFBase64 | SFSimpleXLink | SFServerAddr | SFHexColor
pattern TBool :: String
pattern TBool = "bool"
pattern TString :: String
pattern TString = "string"
pattern TInt :: String
pattern TInt = "int"
pattern TInt64 :: String
pattern TInt64 = "int64"
primitiveTypes :: [ConsName]
primitiveTypes = [TBool, TString, TInt, TInt64, "word32", "double", "JSONObject", "UTCTime"]
data SumTypeInfo = STI {typeName :: String, recordTypes :: [RecordTypeInfo]}
deriving (Show)
sti :: forall t. (GTypeInfo (Rep t), GetDatatypeName (Rep t)) => SumTypeInfo
sti = STI {typeName = getDatatypeName @(Rep t), recordTypes = gTypeInfo @(Rep t)}
class GetDatatypeName (f :: Type -> Type) where getDatatypeName :: String
instance (Datatype d) => GetDatatypeName (D1 d g) where
getDatatypeName = datatypeName (undefined :: D1 d g p)
recordTypesInfo :: forall t. (GTypeInfo (Rep t)) => [RecordTypeInfo]
recordTypesInfo = gTypeInfo @(Rep t)
data RecordTypeInfo = RecordTypeInfo {consName :: ConsName, fieldInfos :: [FieldInfo]}
deriving (Show)
class ConstructorName t where consName' :: t -> ConsName
instance ConstructorName RecordTypeInfo where consName' RecordTypeInfo {consName} = consName
type ConsName = String
data FieldInfo = FieldInfo {fieldName :: String, typeInfo :: TypeInfo}
deriving (Show)
data SimpleType = ST {tcName :: ConsName, tcParams :: [String]}
deriving (Show)
data TypeInfo
= TIType SimpleType -- for simple types
| TIOptional TypeInfo -- for Maybe
| TIArray {elemType :: TypeInfo, nonEmpty :: Bool} -- for [] and NonEmpty
| TIMap {keyType :: SimpleType, valueType :: TypeInfo} -- keys are only base types
deriving (Show)
ti :: ConsName -> TypeInfo
ti n = TIType $ ST n []
class GTypeInfo (f :: Type -> Type) where
gTypeInfo :: [RecordTypeInfo]
instance GTypeInfo U1 where
gTypeInfo = []
instance GTypeInfo V1 where
gTypeInfo = []
instance (GTypeInfo f) => GTypeInfo (D1 d f) where
gTypeInfo = gTypeInfo @f
instance (Constructor c, GFieldsInfo f) => GTypeInfo (C1 c f) where
gTypeInfo = [RecordTypeInfo {consName = conName (undefined :: M1 C c f p), fieldInfos = gfieldsInfo @f}]
instance (GTypeInfo l, GTypeInfo r) => GTypeInfo (l :+: r) where
gTypeInfo = gTypeInfo @l ++ gTypeInfo @r
class GFieldsInfo (f :: Type -> Type) where
gfieldsInfo :: [FieldInfo]
instance GFieldsInfo U1 where
gfieldsInfo = []
instance GFieldsInfo V1 where
gfieldsInfo = []
instance (GFieldsInfo l, GFieldsInfo r) => GFieldsInfo (l :*: r) where
gfieldsInfo = gfieldsInfo @l ++ gfieldsInfo @r
instance forall s i c. (Selector s, Typeable c) => GFieldsInfo (S1 s (K1 i c)) where
gfieldsInfo = [FieldInfo {fieldName = selName (undefined :: S1 s (K1 i c) p), typeInfo = toTypeInfo $ typeRep (Proxy :: Proxy c)}]
toTypeInfo :: TypeRep -> TypeInfo
toTypeInfo tr =
let tc = typeRepTyCon tr
args = typeRepArgs tr
name = tyConName tc
in case name of
"List" -> case args of
[elemTr]
| elemTr == typeRep (Proxy @Char) -> TIType (ST TString [])
| otherwise -> TIArray {elemType = toTypeInfo elemTr, nonEmpty = False}
_ -> TIType (simpleType tr)
"NonEmpty" -> case args of
[elemTr] -> TIArray {elemType = toTypeInfo elemTr, nonEmpty = True}
_ -> TIType (simpleType tr)
"Maybe" -> case args of
[innerTr] -> TIOptional (toTypeInfo innerTr)
_ -> TIType (simpleType tr)
"Map" -> case args of
[keyTr, valTr] -> TIMap {keyType = simpleType keyTr, valueType = toTypeInfo valTr}
_ -> TIType (simpleType tr)
_ -> TIType (simpleType tr)
where
simpleType tr' = primitiveToLower $ case tyConName (typeRepTyCon tr') of
"AgentUserId" -> ST TInt64 []
"Integer" -> ST TInt64 []
"Version" -> ST TInt []
"PQEncryption" -> ST TBool []
"PQSupport" -> ST TBool []
"ACreatedConnLink" -> ST "CreatedConnLink" []
"CChatItem" -> ST "ChatItem" []
"FormatColor" -> ST "Color" []
"CustomData" -> ST "JSONObject" []
"KeyMap" -> ST "JSONObject" []
"CIQDirection" -> ST "CIDirection" []
"SendRef" -> ST "ChatRef" []
t
| t `elem` stringTypes -> ST TString []
| t `elem` simplePrefTypes -> ST "SimplePreference" []
| t `elem` groupPrefTypes -> ST "GroupPreference" []
| t `elem` roleGroupPrefTypes -> ST "RoleGroupPreference" []
| otherwise -> case words $ show tr' of
(tcName : tcParams) -> ST {tcName, tcParams}
_ -> ST "" []
primitiveToLower st@(ST t ps) = let t' = fstToLower t in if t' `elem` primitiveTypes then ST t' ps else st
stringTypes =
[ "AConnectionLink",
"AgentConnId",
"AgentInvId",
"AgentRcvFileId",
"AgentSndFileId",
"B64UrlByteString",
"CbNonce",
"ConnectionLink",
"ConnShortLink",
"ConnectionRequestUri",
"FileDigest",
"GroupLinkId",
"ImageData",
"MemberId",
"Text",
"MREmojiChar",
"ProtocolServer",
"SbKey",
"SharedMsgId",
"UIColor",
"UserPwd",
"XContactId"
]
simplePrefTypes =
[ "CallsPreference",
"FullDeletePreference",
"ReactionsPreference",
"VoicePreference"
]
groupPrefTypes =
[ "FullDeleteGroupPreference",
"ReactionsGroupPreference",
"ReportsGroupPreference",
"HistoryGroupPreference"
]
roleGroupPrefTypes =
[ "DirectMessagesGroupPreference",
"VoiceGroupPreference",
"FilesGroupPreference",
"SimplexLinksGroupPreference"
]