mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-11 01:57:20 +00:00
bots: generate code for TypeScript types module from Haskell tests (#6220)
* bots: generate code for TypeScript types module from Haskell tests * types for API events and command responses * code for chat command types * license, readme * fix array types * fix more types * add response type * add Connect command to docs/ts * update typescript client package to use auto-generated types
This commit is contained in:
@@ -77,16 +77,16 @@ chatCommandsDocsData :: [(String, String, [(ConsName, [String], Text, [ConsName]
|
||||
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")
|
||||
[ ("APICreateMyAddress", [], "Create bot address.", ["CRUserContactLinkCreated", "CRChatCmdError"], [], Just UNInteractive, "/_address " <> Param "userId"),
|
||||
("APIDeleteMyAddress", [], "Delete bot address.", ["CRUserContactLinkDeleted", "CRChatCmdError"], [], Just UNBackground, "/_delete_address " <> Param "userId"),
|
||||
("APIShowMyAddress", [], "Get bot address and settings.", ["CRUserContactLink", "CRChatCmdError"], [], Nothing, "/_show_address " <> Param "userId"),
|
||||
("APISetProfileAddress", [], "Add address to bot profile.", ["CRUserProfileUpdated", "CRChatCmdError"], [], Just UNInteractive, "/_profile_address " <> Param "userId" <> " " <> OnOff "enable"),
|
||||
("APISetAddressSettings", [], "Set bot address settings.", ["CRUserContactLinkUpdated", "CRChatCmdError"], [], 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"),
|
||||
[ ("APISendMessages", [], "Send messages.", ["CRNewChatItems", "CRChatCmdError"], [], Just UNBackground, "/_send " <> Param "sendRef" <> OnOffParam "live" "liveMessage" (Just False) <> Optional "" (" ttl=" <> Param "$0") "ttl" <> " json " <> Json "composedMessages"),
|
||||
( "APIUpdateChatItem",
|
||||
[],
|
||||
"Update message.",
|
||||
@@ -95,53 +95,54 @@ chatCommandsDocsData =
|
||||
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")
|
||||
("APIDeleteChatItem", [], "Delete message.", ["CRChatItemsDeleted", "CRChatCmdError"], [], Just UNBackground, "/_delete item " <> Param "chatRef" <> " " <> Join ',' "chatItemIds" <> " " <> Param "deleteMode"),
|
||||
("APIDeleteMemberChatItem", [], "Moderate message. Requires Moderator role (and higher than message author's).", ["CRChatItemsDeleted", "CRChatCmdError"], [], Just UNBackground, "/_delete member item #" <> Param "groupId" <> " " <> Join ',' "chatItemIds"),
|
||||
("APIChatItemReaction", [], "Add/remove message reaction.", ["CRChatItemReaction", "CRChatCmdError"], [], 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"),
|
||||
[ ("ReceiveFile", [], "Receive file.", ["CRRcvFileAccepted", "CRRcvFileAcceptedSndCancelled", "CRChatCmdError"], [], 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"),
|
||||
[ ("APIAddMember", [], "Add contact to group. Requires bot to have Admin role.", ["CRSentGroupInvitation", "CRChatCmdError"], [], Just UNInteractive, "/_add #" <> Param "groupId" <> " " <> Param "contactId" <> " " <> Param "memberRole"),
|
||||
("APIJoinGroup", ["enableNtfs"], "Join group.", ["CRUserAcceptedGroupSent", "CRChatCmdError"], [], 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),
|
||||
("APIMembersRole", [], "Set members role. Requires Admin role.", ["CRMembersRoleUser", "CRChatCmdError"], [], Just UNBackground, "/_member role #" <> Param "groupId" <> " " <> Join ',' "groupMemberIds" <> " " <> Param "memberRole"),
|
||||
("APIBlockMembersForAll", [], "Block members. Requires Moderator role.", ["CRMembersBlockedForAllUser", "CRChatCmdError"], [], 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, "/_members #" <> Param "groupId"),
|
||||
("APINewGroup", [], "Create group.", ["CRGroupCreated"], [], Nothing, "/_group " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False) <> " " <> Json "groupProfile"),
|
||||
("APIUpdateGroupProfile", [], "Update group profile.", ["CRGroupUpdated"], [], Just UNBackground, "/_group_profile #" <> Param "groupId" <> " " <> Json "groupProfile")
|
||||
("APILeaveGroup", [], "Leave group.", ["CRLeftMemberUser", "CRChatCmdError"], [], Just UNBackground, "/_leave #" <> Param "groupId"),
|
||||
("APIListMembers", [], "Get group members.", ["CRGroupMembers", "CRChatCmdError"], [], Nothing, "/_members #" <> Param "groupId"),
|
||||
("APINewGroup", [], "Create group.", ["CRGroupCreated", "CRChatCmdError"], [], Nothing, "/_group " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False) <> " " <> Json "groupProfile"),
|
||||
("APIUpdateGroupProfile", [], "Update group profile.", ["CRGroupUpdated", "CRChatCmdError"], [], Just UNBackground, "/_group_profile #" <> Param "groupId" <> " " <> Json "groupProfile")
|
||||
]
|
||||
),
|
||||
( "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")
|
||||
[ ("APICreateGroupLink", [], "Create group link.", ["CRGroupLinkCreated", "CRChatCmdError"], [], Just UNInteractive, "/_create link #" <> Param "groupId" <> " " <> Param "memberRole"),
|
||||
("APIGroupLinkMemberRole", [], "Set member role for group link.", ["CRGroupLink", "CRChatCmdError"], [], Nothing, "/_set link role #" <> Param "groupId" <> " " <> Param "memberRole"),
|
||||
("APIDeleteGroupLink", [], "Delete group link.", ["CRGroupLinkDeleted", "CRChatCmdError"], [], Just UNBackground, "/_delete link #" <> Param "groupId"),
|
||||
("APIGetGroupLink", [], "Get group link.", ["CRGroupLink", "CRChatCmdError"], [], 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")
|
||||
[ ("APIAddContact", [], "Create 1-time invitation link.", ["CRInvitation", "CRChatCmdError"], [], 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", "CRChatCmdError"], [], Just UNInteractive, "/_connect plan " <> Param "userId" <> " " <> Param "connectionLink"),
|
||||
("APIConnect", [], "Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link", ["CRSentConfirmation", "CRContactAlreadyExists", "CRSentInvitation", "CRChatCmdError"], [], Just UNInteractive, "/_connect " <> Param "userId" <> Optional "" (" " <> Param "$0") "preparedLink_"),
|
||||
("Connect", [], "Connect via SimpleX link as string in the active user profile.", ["CRSentConfirmation", "CRContactAlreadyExists", "CRSentInvitation", "CRChatCmdError"], [], Just UNInteractive, "/connect" <> Optional "" (" " <> Param "$0") "connLink_"),
|
||||
("APIAcceptContact", ["incognito"], "Accept contact request.", ["CRAcceptingContactRequest", "CRChatCmdError"], [], Just UNInteractive, "/_accept " <> Param "contactReqId"),
|
||||
("APIRejectContact", [], "Reject contact request. The user who sent the request is **not notified**.", ["CRContactRequestRejected", "CRChatCmdError"], [], Nothing, "/_reject " <> Param "contactReqId")
|
||||
]
|
||||
),
|
||||
( "Chat commands",
|
||||
"Commands to list and delete conversations.",
|
||||
[ ("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")
|
||||
[ ("APIListContacts", [], "Get contacts.", ["CRContactsList", "CRChatCmdError"], [], Nothing, "/_contacts " <> Param "userId"),
|
||||
("APIListGroups", [], "Get groups.", ["CRGroupsList", "CRChatCmdError"], [], Nothing, "/_groups " <> Param "userId" <> Optional "" (" @" <> Param "$0") "contactId_" <> Optional "" (" " <> Param "$0") "search"),
|
||||
("APIDeleteChat", [], "Delete chat.", ["CRContactDeleted", "CRContactConnectionDeleted", "CRGroupDeletedUser", "CRChatCmdError"], [], 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, ""),
|
||||
@@ -162,20 +163,20 @@ chatCommandsDocsData =
|
||||
),
|
||||
( "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"),
|
||||
[ ("ShowActiveUser", [], "Get active user profile", ["CRActiveUser", "CRChatCmdError"], [], Nothing, "/user"),
|
||||
( "CreateActiveUser",
|
||||
[],
|
||||
"Create new user profile",
|
||||
["CRActiveUser"],
|
||||
["CRActiveUser", "CRChatCmdError"],
|
||||
[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"),
|
||||
("APISetContactPrefs", [], "Configure chat preference overrides for the contact.", ["CRContactPrefsUpdated"], [], Just UNBackground, "/_set prefs @" <> Param "contactId" <> " " <> Json "preferences")
|
||||
("ListUsers", [], "Get all user profiles", ["CRUsersList", "CRChatCmdError"], [], Nothing, "/users"),
|
||||
("APISetActiveUser", [], "Set active user profile", ["CRActiveUser", "CRChatCmdError"], ["CEChatNotStarted"], Nothing, "/_user " <> Param "userId" <> Optional "" (" " <> Json "$0") "viewPwd"),
|
||||
("APIDeleteUser", [], "Delete user profile.", ["CRCmdOk", "CRChatCmdError"], [], Just UNBackground, "/_delete user " <> Param "userId" <> OnOffParam "del_smp" "delSMPQueues" Nothing <> Optional "" (" " <> Json "$0") "viewPwd"),
|
||||
("APIUpdateProfile", [], "Update user profile.", ["CRUserProfileUpdated", "CRUserProfileNoChange", "CRChatCmdError"], [], Just UNBackground, "/_profile " <> Param "userId" <> " " <> Json "profile"),
|
||||
("APISetContactPrefs", [], "Configure chat preference overrides for the contact.", ["CRContactPrefsUpdated", "CRChatCmdError"], [], Just UNBackground, "/_set prefs @" <> Param "contactId" <> " " <> Json "preferences")
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -193,7 +194,6 @@ cliCommands =
|
||||
"ClearContact",
|
||||
"ClearGroup",
|
||||
"ClearNoteFolder",
|
||||
"Connect",
|
||||
"ConnectSimplex",
|
||||
"ContactInfo",
|
||||
"ContactQueueInfo",
|
||||
|
||||
@@ -43,7 +43,7 @@ commandsDocText =
|
||||
<> 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")
|
||||
("\n\n### " <> T.pack (fstToUpper tag) <> "\n\n" <> commandDescr <> "\n\n*Network usage*: " <> networkUsage network <> ".\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")
|
||||
@@ -52,10 +52,6 @@ commandsDocText =
|
||||
<> 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")
|
||||
@@ -67,11 +63,17 @@ commandsDocText =
|
||||
let descr' = if null descr then camelToSpace err else descr
|
||||
in T.pack $ "- " <> fstToUpper err <> ": " <> descr' <> ".\n"
|
||||
|
||||
networkUsage :: Maybe UsesNetwork -> Text
|
||||
networkUsage = \case
|
||||
Nothing -> "no"
|
||||
Just UNInteractive -> "interactive"
|
||||
Just UNBackground -> "background"
|
||||
|
||||
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```javascript\n" <> jsSyntaxText False r syntax <> " // JavaScript\n```\n")
|
||||
<> (if isConst syntax then "" else "\n```python\n" <> pySyntaxText r syntax <> " # Python\n```\n")
|
||||
|
||||
camelToSpace :: String -> String
|
||||
@@ -127,14 +129,14 @@ typesDocText =
|
||||
where
|
||||
self = APIRecordField "self" (ATDef td)
|
||||
typeFields = case typeDef of
|
||||
ATDRecord fs -> L.toList fs
|
||||
ATDRecord fs -> 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)
|
||||
ATDRecord fields -> "\n**Record type**:\n" <> fieldsText "" 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
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module API.Docs.Generate.TypeScript where
|
||||
|
||||
import API.Docs.Commands
|
||||
import API.Docs.Events
|
||||
import API.Docs.Generate
|
||||
import API.Docs.Responses
|
||||
import API.Docs.Syntax
|
||||
import API.Docs.Syntax.Types
|
||||
import API.Docs.Types
|
||||
import API.TypeInfo
|
||||
import Data.Char (toUpper)
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
|
||||
commandsCodeFile :: FilePath
|
||||
commandsCodeFile = "./packages/simplex-chat-client/types/typescript/src/commands.ts"
|
||||
|
||||
responsesCodeFile :: FilePath
|
||||
responsesCodeFile = "./packages/simplex-chat-client/types/typescript/src/responses.ts"
|
||||
|
||||
eventsCodeFile :: FilePath
|
||||
eventsCodeFile = "./packages/simplex-chat-client/types/typescript/src/events.ts"
|
||||
|
||||
typesCodeFile :: FilePath
|
||||
typesCodeFile = "./packages/simplex-chat-client/types/typescript/src/types.ts"
|
||||
|
||||
commandsCodeText :: Text
|
||||
commandsCodeText =
|
||||
("// API Commands\n// " <> autoGenerated <> "\n")
|
||||
<> "\nimport * as T from \"./types\"\n"
|
||||
<> "\nimport {CR} from \"./responses\"\n"
|
||||
<> foldMap commandCatCode chatCommandsDocs
|
||||
where
|
||||
commandCatCode CCCategory {categoryName, categoryDescr, commands} =
|
||||
(T.pack $ "\n// " <> categoryName <> "\n// " <> categoryDescr <> "\n")
|
||||
<> foldMap commandCode commands
|
||||
where
|
||||
commandCode CCDoc {commandType = ATUnionMember tag params, commandDescr, syntax, responses, network} =
|
||||
("\n// " <> commandDescr <> "\n")
|
||||
<> ("// Network usage: " <> networkUsage network <> ".\n")
|
||||
<> ("export interface " <> T.pack constrName <> " {\n")
|
||||
<> fieldsCode "" "T." params
|
||||
<> "}\n\n"
|
||||
<> ("export namespace " <> T.pack constrName <> " {\n")
|
||||
<> (" export type Response = " <> constrsCode " " "CR" (("CR." <> ) . T.pack . fstToUpper . memberTag) (map responseType responses))
|
||||
<> (if syntax == "" then "" else funcCode APITypeDef {typeName' = constrName, typeDef = ATDRecord params} syntax)
|
||||
<> "}\n"
|
||||
where
|
||||
constrName = fstToUpper tag
|
||||
|
||||
responsesCodeText :: Text
|
||||
responsesCodeText =
|
||||
("// API Responses\n// " <> autoGenerated <> "\n")
|
||||
<> "\nimport * as T from \"./types\"\n"
|
||||
<> unionTypeCode "CR" "T." chatRespTypeDef chatRespConstrs ""
|
||||
where
|
||||
chatRespTypeDef = APITypeDef {typeName' = "ChatResponse", typeDef = ATDUnion chatRespConstrs}
|
||||
chatRespConstrs = L.fromList $ map responseType chatResponsesDocs
|
||||
|
||||
eventsCodeText :: Text
|
||||
eventsCodeText =
|
||||
("// API Events\n// " <> autoGenerated <> "\n")
|
||||
<> "\nimport * as T from \"./types\"\n"
|
||||
<> unionTypeCode "CEvt" "T." chatEventTypeDef chatEventConstrs ""
|
||||
where
|
||||
chatEventTypeDef = APITypeDef {typeName' = "ChatEvent", typeDef = ATDUnion chatEventConstrs}
|
||||
chatEventConstrs = L.fromList $ concatMap catEvents chatEventsDocs
|
||||
catEvents CECategory {mainEvents, otherEvents} = map eventType $ mainEvents ++ otherEvents
|
||||
|
||||
typesCodeText :: Text
|
||||
typesCodeText = ("// API Types\n// " <> autoGenerated <> "\n") <> foldMap typeCode chatTypesDocs
|
||||
where
|
||||
typeCode CTDoc {typeDef = td@APITypeDef {typeName' = name, typeDef}, typeDescr, typeSyntax} =
|
||||
(if T.null typeDescr then "" else "// " <> typeDescr <> "\n")
|
||||
<> typeDefCode
|
||||
-- <> (if typeSyntax == "" then "" else syntaxText (name, self : typeFields) typeSyntax)
|
||||
where
|
||||
name' = T.pack name
|
||||
constrName tag = case name of
|
||||
"ConnectionMode" -> T.pack $ map toUpper tag
|
||||
"FileProtocol" -> T.pack $ map toUpper tag
|
||||
_ -> T.replace "-" "_" $ T.pack $ fstToUpper tag
|
||||
namespaceFuncCode = "\nexport namespace " <> name' <> " {" <> funcCode td typeSyntax <> "}\n"
|
||||
typeDefCode = case typeDef of
|
||||
ATDRecord fields ->
|
||||
("\nexport interface " <> name' <> " {\n")
|
||||
<> fieldsCode "" "" fields
|
||||
<> "}\n"
|
||||
<> (if typeSyntax == "" then "" else namespaceFuncCode)
|
||||
ATDEnum cs ->
|
||||
("\nexport enum " <> name' <> " {\n")
|
||||
<> foldMap (\m -> " " <> constrName m <> " = \"" <> T.pack m <> "\",\n") cs
|
||||
<> "}\n"
|
||||
<> (if typeSyntax == "" then "" else namespaceFuncCode)
|
||||
ATDUnion cs -> unionTypeCode name' "" td cs typeSyntax
|
||||
|
||||
unionTypeCode :: Text -> String -> APITypeDef -> L.NonEmpty ATUnionMember -> Expr -> Text
|
||||
unionTypeCode unionNamespace typesNamespace td@APITypeDef {typeName' = name} cs cmdSyntax =
|
||||
("\nexport type " <> name' <> " = " <> constrsCode "" name' constrTypeRef (L.toList cs) <> "\n")
|
||||
<> ("export namespace " <> unionNamespace <> " {\n")
|
||||
<> (" export type Tag = " <> constrsCode " " name' constrTag (L.toList cs) <> "\n")
|
||||
<> (" interface Interface {\n type: Tag\n }\n")
|
||||
<> foldMap constrType cs
|
||||
<> (if cmdSyntax == "" then "" else funcCode td cmdSyntax)
|
||||
<> "}\n"
|
||||
where
|
||||
name' = T.pack name
|
||||
constrTypeRef (ATUnionMember tag _) = unionNamespace <> "." <> constrName tag
|
||||
constrTag (ATUnionMember tag _) = T.pack $ "\"" <> tag <> "\""
|
||||
constrType c@(ATUnionMember tag fields) =
|
||||
("\n export interface " <> constrName tag <> " extends Interface {\n")
|
||||
<> " type: " <> constrTag c <> "\n"
|
||||
<> fieldsCode " " typesNamespace fields
|
||||
<> " }\n"
|
||||
constrName tag = T.replace "-" "_" (T.pack $ fstToUpper tag)
|
||||
|
||||
constrsCode :: Text -> Text -> (ATUnionMember -> Text) -> [ATUnionMember] -> Text
|
||||
constrsCode indent name' constr cs
|
||||
| T.length (name' <> " = " <> line) <= 100 = line <> "\n"
|
||||
| otherwise = "\n" <> foldMap (\c -> indent <> " | " <> c <> "\n") cs'
|
||||
where
|
||||
line = T.intercalate " | " cs'
|
||||
cs' = map constr cs
|
||||
|
||||
funcCode :: APITypeDef -> Expr -> Text
|
||||
funcCode td@APITypeDef {typeName' = name, typeDef} cmdSyntax =
|
||||
"\n export function cmdString(" <> param <> ": " <> T.pack name <> "): string {\n return " <> jsSyntaxText True (name, self : typeFields) cmdSyntax <> "\n }\n"
|
||||
where
|
||||
param = if hasParams cmdSyntax then "self" else "_self"
|
||||
self = APIRecordField "self" (ATDef td)
|
||||
typeFields = case typeDef of
|
||||
ATDRecord fs -> 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 _ -> []
|
||||
|
||||
fieldsCode :: Text -> String -> [APIRecordField] -> Text
|
||||
fieldsCode indent namespace = foldMap $ (indent <>) . T.pack . fieldText
|
||||
where
|
||||
fieldText (APIRecordField name t) = " " <> name <> optional t <> ": " <> typeText t <> typeComment t <> "\n"
|
||||
optional = \case
|
||||
ATOptional _ -> "?"
|
||||
_ -> ""
|
||||
typeText = \case
|
||||
ATPrim (PT t) -> typeName t
|
||||
ATDef (APITypeDef t _) -> namespace <> t
|
||||
ATRef t -> namespace <> t
|
||||
ATOptional t -> typeText t
|
||||
ATArray {elemType} -> typeText elemType <> "[]"
|
||||
ATMap (PT t) valueType -> "{[key: " <> typeName t <> "]: " <> typeText valueType <> "}"
|
||||
typeName = \case
|
||||
TBool -> "boolean"
|
||||
TInt -> "number"
|
||||
TInt64 -> "number"
|
||||
TWord32 -> "number"
|
||||
TDouble -> "number"
|
||||
TJSONObject -> "object"
|
||||
TUTCTime -> "string"
|
||||
t -> t
|
||||
typeComment t = let c = typeComment' t in if null c then "" else " // " <> c
|
||||
typeComment' = \case
|
||||
ATPrim (PT t) -> typeComment_ t
|
||||
ATOptional (ATPrim (PT t)) -> typeComment_ t
|
||||
ATArray {elemType, nonEmpty}
|
||||
| nonEmpty -> (if null c then "" else c <> ", ") <> "non-empty"
|
||||
| otherwise -> c
|
||||
where
|
||||
c = typeComment' elemType
|
||||
ATMap (PT k) v ->
|
||||
let kc = typeComment_ k
|
||||
vc = typeComment' v
|
||||
tc t c = if null c then t else c
|
||||
in if null kc && null vc then "" else tc (typeName k) kc <> " : " <> tc (typeText v) vc
|
||||
_ -> ""
|
||||
typeComment_ = \case
|
||||
TInt -> "int"
|
||||
TInt64 -> "int64"
|
||||
TWord32 -> "word32"
|
||||
TDouble -> "double"
|
||||
TUTCTime -> "ISO-8601 timestamp"
|
||||
_ -> ""
|
||||
@@ -88,6 +88,7 @@ chatResponsesDocsData =
|
||||
("CRUserContactLinkUpdated", "User contact address updated"),
|
||||
("CRUserDeletedMembers", "Members deleted"),
|
||||
("CRUserProfileUpdated", "User profile updated"),
|
||||
("CRUserProfileNoChange", "User profile was not changed"),
|
||||
("CRUsersList", "Users")
|
||||
-- ("CRApiChat", "Chat and messages"),
|
||||
-- ("CRApiChats", "Chats with the most recent messages"),
|
||||
@@ -195,7 +196,6 @@ undocumentedResponses =
|
||||
"CRUserPrivacy",
|
||||
"CRUserProfile",
|
||||
"CRUserProfileImage",
|
||||
"CRUserProfileNoChange",
|
||||
"CRUserServers",
|
||||
"CRUserServersValidation",
|
||||
"CRVersionInfo",
|
||||
|
||||
+22
-15
@@ -99,8 +99,8 @@ withOptBoolParam r param p f =
|
||||
(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
|
||||
jsSyntaxText :: Bool -> TypeAndFields -> Expr -> Text
|
||||
jsSyntaxText useSelf r = T.replace "' + '" "" . T.pack . go Nothing True
|
||||
where
|
||||
go param top = \case
|
||||
Concat exs -> intercalate " + " $ map (go param False) $ L.toList exs
|
||||
@@ -109,14 +109,14 @@ jsSyntaxText r = T.replace "' + '" "" . T.pack . go Nothing True
|
||||
withParamType r param p $ \case
|
||||
ATDef td -> toStringSyntax td
|
||||
ATOptional (ATDef td) -> toStringSyntax td
|
||||
_ -> paramName param p
|
||||
_ -> paramName' useSelf param p
|
||||
where
|
||||
toStringSyntax (APITypeDef typeName _)
|
||||
| typeHasSyntax typeName = paramName param p <> ".toString()"
|
||||
| otherwise = paramName param p
|
||||
| typeHasSyntax typeName = paramName' useSelf param p <> ".toString()"
|
||||
| otherwise = paramName' useSelf param p
|
||||
Optional exN exJ p -> open <> n <> " ? " <> go (Just p) False exJ <> " : " <> nothing <> close
|
||||
where
|
||||
n = paramName param p
|
||||
n = paramName' useSelf param p
|
||||
nothing = if exN == "" then "''" else go param False exN
|
||||
Choice p opts else' ->
|
||||
withParamType r param p $ \case
|
||||
@@ -125,15 +125,15 @@ jsSyntaxText r = T.replace "' + '" "" . T.pack . go Nothing True
|
||||
_ -> paramError r param p "is not union type"
|
||||
where
|
||||
choiceSyntax = \case
|
||||
APITypeDef _ (ATDUnion _) -> choices "type"
|
||||
APITypeDef _ (ATDUnion _) -> choices $ (if useSelf then "self." else "") <> "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
|
||||
Join c p -> paramName' useSelf param p <> ".join('" <> [c] <> "')"
|
||||
Json p -> "JSON.stringify(" <> paramName' useSelf param p <> ")"
|
||||
OnOff p -> open <> paramName' useSelf param p <> " ? 'on' : 'off'" <> close
|
||||
OnOffParam name p def_ -> case def_ of
|
||||
Nothing ->
|
||||
withOptBoolParam r param p $ \optional ->
|
||||
@@ -141,13 +141,13 @@ jsSyntaxText r = T.replace "' + '" "" . T.pack . go Nothing True
|
||||
then "(typeof " <> n <> " == 'boolean' ? " <> res <> " : '')"
|
||||
else res
|
||||
where
|
||||
n = paramName param p
|
||||
n = paramName' useSelf 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
|
||||
n = paramName' useSelf param p
|
||||
where
|
||||
open = if top then "" else "("
|
||||
close = if top then "" else ")"
|
||||
@@ -215,6 +215,13 @@ pySyntaxText r = T.pack . go Nothing True
|
||||
close = if top then "" else ")"
|
||||
|
||||
paramName :: Maybe ExprParam -> ExprParam -> String
|
||||
paramName param_ p = case param_ of
|
||||
Just param | p == "$0" -> param
|
||||
_ -> p
|
||||
paramName = paramName' False
|
||||
|
||||
paramName' :: Bool -> Maybe ExprParam -> ExprParam -> String
|
||||
paramName' useSelf param_ p
|
||||
| useSelf && p' /= "self" = "self." <> p'
|
||||
| otherwise = p'
|
||||
where
|
||||
p' = case param_ of
|
||||
Just param | p == "$0" -> param
|
||||
_ -> p
|
||||
|
||||
@@ -26,6 +26,18 @@ isConst = \case
|
||||
Const _ -> True
|
||||
_ -> False
|
||||
|
||||
hasParams :: Expr -> Bool
|
||||
hasParams = \case
|
||||
Concat es -> any (hasParams) es
|
||||
Const _ -> False
|
||||
Param _ -> True
|
||||
Optional {} -> True
|
||||
Choice {} -> True
|
||||
Join {} -> True
|
||||
Json _ -> True
|
||||
OnOff _ -> True
|
||||
OnOffParam {} -> True
|
||||
|
||||
instance IsString Expr where fromString = Const
|
||||
|
||||
instance Semigroup Expr where
|
||||
|
||||
@@ -83,7 +83,7 @@ toTypeDef acc@(!visited, !typeDefs) (STI typeName allConstrs, jsonEncoding, cons
|
||||
[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'
|
||||
td = APITypeDef typeName $ ATDRecord $ L.toList 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
|
||||
@@ -199,7 +199,7 @@ chatTypesDocsData =
|
||||
(sti @(ContactUserPreference SimplePreference), STRecord, "", [], "", ""),
|
||||
(sti @(CreatedConnLink 'CMContact), STRecord, "", [], Param "connFullLink" <> Optional "" (" " <> Param "$0") "connShortLink", ""),
|
||||
(sti @AddressSettings, STRecord, "", [], "", ""),
|
||||
(sti @AgentCryptoError, STUnion, "", [], "", ""),
|
||||
(sti @AgentCryptoError, STUnion, "", ["RATCHET_EARLIER", "RATCHET_SKIPPED"], "", ""), -- TODO add fields to types
|
||||
(sti @AgentErrorType, STUnion, "", [], "", ""),
|
||||
(sti @AutoAccept, STRecord, "", [], "", ""),
|
||||
(sti @BlockingInfo, STRecord, "", [], "", ""),
|
||||
@@ -217,7 +217,7 @@ chatTypesDocsData =
|
||||
(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 @ChatType, STEnum, "CT", ["CTContactRequest", "CTContactConnection"], Choice "self" [("direct", "@"), ("group", "#"), ("local", "*")] "", ""),
|
||||
(sti @ChatWallpaper, STRecord, "", [], "", ""),
|
||||
(sti @ChatWallpaperScale, STEnum, "CWS", [], "", ""),
|
||||
(sti @CICallStatus, STEnum, "CISCall", [], "", ""),
|
||||
|
||||
@@ -33,7 +33,7 @@ data APIType
|
||||
data APITypeDef = APITypeDef {typeName' :: String, typeDef :: APITypeDefinition}
|
||||
|
||||
data APITypeDefinition
|
||||
= ATDRecord (NonEmpty APIRecordField)
|
||||
= ATDRecord [APIRecordField]
|
||||
| ATDUnion (NonEmpty ATUnionMember)
|
||||
| ATDEnum (NonEmpty String)
|
||||
|
||||
@@ -45,10 +45,6 @@ data ATUnionMember = ATUnionMember {memberTag :: String, memberFields :: [APIRec
|
||||
|
||||
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"
|
||||
|
||||
@@ -61,8 +57,20 @@ pattern TInt = "int"
|
||||
pattern TInt64 :: String
|
||||
pattern TInt64 = "int64"
|
||||
|
||||
pattern TWord32 :: String
|
||||
pattern TWord32 = "word32"
|
||||
|
||||
pattern TDouble :: String
|
||||
pattern TDouble = "double"
|
||||
|
||||
pattern TJSONObject :: String
|
||||
pattern TJSONObject = "JSONObject"
|
||||
|
||||
pattern TUTCTime :: String
|
||||
pattern TUTCTime = "UTCTime"
|
||||
|
||||
primitiveTypes :: [ConsName]
|
||||
primitiveTypes = [TBool, TString, TInt, TInt64, "word32", "double", "JSONObject", "UTCTime"]
|
||||
primitiveTypes = [TBool, TString, TInt, TInt64, TWord32, TDouble, TJSONObject, TUTCTime]
|
||||
|
||||
data SumTypeInfo = STI {typeName :: String, recordTypes :: [RecordTypeInfo]}
|
||||
deriving (Show)
|
||||
|
||||
Reference in New Issue
Block a user