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:
Evgeny
2025-08-26 16:38:27 +01:00
committed by GitHub
parent aec455c923
commit e2d5c675d0
30 changed files with 7416 additions and 2250 deletions
+38 -38
View File
@@ -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",
+10 -8
View File
@@ -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
+189
View File
@@ -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"
_ -> ""
+1 -1
View File
@@ -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
View File
@@ -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
+12
View File
@@ -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
+3 -3
View File
@@ -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", [], "", ""),
+14 -6
View File
@@ -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)