diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index d260269..b714327 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,11 +7,246 @@ dependencies-toml-version = "2" distribution-version = "2201.11.0" +[[package]] +org = "ballerina" +name = "auth" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.9.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.8.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.11.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.13.2" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.7.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] + [[package]] org = "ballerina" name = "jballerina.java" version = "0.0.0" +[[package]] +org = "ballerina" +name = "jwt" +version = "2.14.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.11.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.11.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] + [[package]] org = "ballerina" name = "observe" @@ -20,6 +255,60 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "os" +version = "1.9.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "os", moduleName = "os"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "url", moduleName = "url"} +] + [[package]] org = "ballerinai" name = "observe" @@ -37,6 +326,10 @@ org = "ballerinax" name = "hubspot.crm.extensions.videoconferencing" version = "1.0.0" dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "test"}, + {org = "ballerina", name = "url"}, {org = "ballerinai", name = "observe"} ] modules = [ diff --git a/ballerina/client.bal b/ballerina/client.bal index c000777..22962c7 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -1,15 +1,86 @@ -// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). -// -// WSO2 LLC. licenses this file to you under the Apache License, -// Version 2.0 (the "License"); you may not use this file except -// in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/http; + +# These APIs allow you to specify URLs that can be used to interact with a video conferencing application, to allow HubSpot to add video conference links to meeting requests with contacts. +public isolated client class Client { + final http:Client clientEp; + final readonly & ApiKeysConfig apiKeyConfig; + # Gets invoked to initialize the `connector`. + # + # + apiKeyConfig - API keys for authorization + # + config - The configurations to be used when initializing the `connector` + # + serviceUrl - URL of the target service + # + return - An error if connector initialization failed + public isolated function init(ApiKeysConfig apiKeyConfig, ConnectionConfig config = {}, string serviceUrl = "https://api.hubapi.com/crm/v3/extensions/videoconferencing/settings") returns error? { + http:ClientConfiguration httpClientConfig = {httpVersion: config.httpVersion, timeout: config.timeout, forwarded: config.forwarded, poolConfig: config.poolConfig, compression: config.compression, circuitBreaker: config.circuitBreaker, retryConfig: config.retryConfig, validation: config.validation}; + do { + if config.http1Settings is ClientHttp1Settings { + ClientHttp1Settings settings = check config.http1Settings.ensureType(ClientHttp1Settings); + httpClientConfig.http1Settings = {...settings}; + } + if config.http2Settings is http:ClientHttp2Settings { + httpClientConfig.http2Settings = check config.http2Settings.ensureType(http:ClientHttp2Settings); + } + if config.cache is http:CacheConfig { + httpClientConfig.cache = check config.cache.ensureType(http:CacheConfig); + } + if config.responseLimits is http:ResponseLimitConfigs { + httpClientConfig.responseLimits = check config.responseLimits.ensureType(http:ResponseLimitConfigs); + } + if config.secureSocket is http:ClientSecureSocket { + httpClientConfig.secureSocket = check config.secureSocket.ensureType(http:ClientSecureSocket); + } + if config.proxy is http:ProxyConfig { + httpClientConfig.proxy = check config.proxy.ensureType(http:ProxyConfig); + } + } + http:Client httpEp = check new (serviceUrl, httpClientConfig); + self.clientEp = httpEp; + self.apiKeyConfig = apiKeyConfig.cloneReadOnly(); + return; + } + + # Delete settings + # + # + appId - The ID of the video conference application. This is the identifier of the application created in your HubSpot developer portal. + # + headers - Headers to be sent with the request + # + return - No content + resource isolated function delete [int:Signed32 appId](map headers = {}) returns http:Response|error { + string resourcePath = string `/${getEncodedUri(appId)}`; + map queryParam = {}; + queryParam["hapikey"] = self.apiKeyConfig.hapikey; + resourcePath = resourcePath + check getPathForQueryParam(queryParam); + return self.clientEp->delete(resourcePath, headers = headers); + } + + # Get settings + # + # + appId - The ID of the video conference application. This is the identifier of the application created in your HubSpot developer portal. + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function get [int:Signed32 appId](map headers = {}) returns ExternalSettings|error { + string resourcePath = string `/${getEncodedUri(appId)}`; + map queryParam = {}; + queryParam["hapikey"] = self.apiKeyConfig.hapikey; + resourcePath = resourcePath + check getPathForQueryParam(queryParam); + return self.clientEp->get(resourcePath, headers); + } + + # Update settings + # + # + appId - The ID of the video conference application. This is the identifier of the application created in your HubSpot developer portal. + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function put [int:Signed32 appId](ExternalSettings payload, map headers = {}) returns ExternalSettings|error { + string resourcePath = string `/${getEncodedUri(appId)}`; + map queryParam = {}; + queryParam["hapikey"] = self.apiKeyConfig.hapikey; + resourcePath = resourcePath + check getPathForQueryParam(queryParam); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->put(resourcePath, request, headers); + } +} diff --git a/ballerina/types.bal b/ballerina/types.bal new file mode 100644 index 0000000..da92165 --- /dev/null +++ b/ballerina/types.bal @@ -0,0 +1,88 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/http; + +# The URLs of the various actions provided by the video conferencing application. All URLs must use the `https` protocol. +public type ExternalSettings record { + # The URL that HubSpot will use to verify that a user exists in the video conference application. + string? userVerifyUrl?; + string? fetchAccountsUri?; + # The URL that HubSpot will send requests to create a new video conference. + string createMeetingUrl; + # The URL that HubSpot will send updates to existing meetings. Typically called when the user changes the topic or times of a meeting. + string? updateMeetingUrl?; + # The URL that HubSpot will send notifications of meetings that have been deleted in HubSpot. + string? deleteMeetingUrl?; +}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; +|}; + +public type AnydataDefault record {| + *http:DefaultStatusCodeResponse; + anydata body; +|}; + +# Proxy server configurations to be used with the HTTP client endpoint. +public type ProxyConfig record {| + # Host name of the proxy server + string host = ""; + # Proxy server port + int port = 0; + # Proxy server username + string userName = ""; + # Proxy server password + @display {label: "", kind: "password"} + string password = ""; +|}; + +# Provides API key configurations needed when communicating with a remote HTTP endpoint. +public type ApiKeysConfig record {| + # HubSpot developer API key + @display {label: "", kind: "password"} + string hapikey; +|}; + +# Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. +@display {label: "Connection Config"} +public type ConnectionConfig record {| + # The HTTP version understood by the client + http:HttpVersion httpVersion = http:HTTP_2_0; + # Configurations related to HTTP/1.x protocol + ClientHttp1Settings http1Settings?; + # Configurations related to HTTP/2 protocol + http:ClientHttp2Settings http2Settings?; + # The maximum time to wait (in seconds) for a response before closing the connection + decimal timeout = 60; + # The choice of setting `forwarded`/`x-forwarded` header + string forwarded = "disable"; + # Configurations associated with request pooling + http:PoolConfiguration poolConfig?; + # HTTP caching related configurations + http:CacheConfig cache?; + # Specifies the way of handling compression (`accept-encoding`) header + http:Compression compression = http:COMPRESSION_AUTO; + # Configurations associated with the behaviour of the Circuit Breaker + http:CircuitBreakerConfig circuitBreaker?; + # Configurations associated with retrying + http:RetryConfig retryConfig?; + # Configurations associated with inbound response size limits + http:ResponseLimitConfigs responseLimits?; + # SSL/TLS-related options + http:ClientSecureSocket secureSocket?; + # Proxy server related options + http:ProxyConfig proxy?; + # Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default + boolean validation = true; + # Enables relaxed data binding on the client side. When enabled, `nil` values are treated as optional, + # and absent fields are handled as `nilable` types. Enabled by default. + boolean laxDataBinding = true; +|}; diff --git a/ballerina/utils.bal b/ballerina/utils.bal new file mode 100644 index 0000000..e470e3e --- /dev/null +++ b/ballerina/utils.bal @@ -0,0 +1,203 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/http; +import ballerina/url; + +type SimpleBasicType string|boolean|int|float|decimal; + +# Represents encoding mechanism details. +type Encoding record { + # Defines how multiple values are delimited + string style = FORM; + # Specifies whether arrays and objects should generate as separate fields + boolean explode = true; + # Specifies the custom content type + string contentType?; + # Specifies the custom headers + map headers?; +}; + +enum EncodingStyle { + DEEPOBJECT, FORM, SPACEDELIMITED, PIPEDELIMITED +} + +final Encoding & readonly defaultEncoding = {}; + +# Serialize the record according to the deepObject style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + return - Serialized record as a string +isolated function getDeepObjectStyleRequest(string parent, record {} anyRecord) returns string { + string[] recordArray = []; + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(parent + "[" + key + "]" + "=" + getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(parent + "[" + key + "]" + "[]", value, DEEPOBJECT, true)); + } else if value is record {} { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getDeepObjectStyleRequest(nextParent, value)); + } else if value is record {}[] { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getSerializedRecordArray(nextParent, value, DEEPOBJECT)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + return string:'join("", ...recordArray); +} + +# Serialize the record according to the form style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getFormStyleRequest(string parent, record {} anyRecord, boolean explode = true) returns string { + string[] recordArray = []; + if explode { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = explode)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + } else { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, ",", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = false)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push(","); + } + _ = recordArray.pop(); + } + return string:'join("", ...recordArray); +} + +# Serialize arrays. +# +# + arrayName - Name of the field with arrays +# + anyArray - Array to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized array as a string +isolated function getSerializedArray(string arrayName, anydata[] anyArray, string style = "form", boolean explode = true) returns string { + string key = arrayName; + string[] arrayValues = []; + if anyArray.length() > 0 { + if style == FORM && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), ","); + } + } else if style == SPACEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "%20"); + } + } else if style == PIPEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "|"); + } + } else if style == DEEPOBJECT { + foreach anydata i in anyArray { + arrayValues.push(key, "[]", "=", getEncodedUri(i.toString()), "&"); + } + } else { + foreach anydata i in anyArray { + arrayValues.push(key, "=", getEncodedUri(i.toString()), "&"); + } + } + _ = arrayValues.pop(); + } + return string:'join("", ...arrayValues); +} + +# Serialize the array of records according to the form style. +# +# + parent - Parent record name +# + value - Array of records to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getSerializedRecordArray(string parent, record {}[] value, string style = FORM, boolean explode = true) returns string { + string[] serializedArray = []; + if style == DEEPOBJECT { + int arayIndex = 0; + foreach var recordItem in value { + serializedArray.push(getDeepObjectStyleRequest(parent + "[" + arayIndex.toString() + "]", recordItem), "&"); + arayIndex = arayIndex + 1; + } + } else { + if !explode { + serializedArray.push(parent, "="); + } + foreach var recordItem in value { + serializedArray.push(getFormStyleRequest(parent, recordItem, explode), ","); + } + } + _ = serializedArray.pop(); + return string:'join("", ...serializedArray); +} + +# Get Encoded URI for a given value. +# +# + value - Value to be encoded +# + return - Encoded string +isolated function getEncodedUri(anydata value) returns string { + string|error encoded = url:encode(value.toString(), "UTF8"); + if encoded is string { + return encoded; + } else { + return value.toString(); + } +} + +# Generate query path with query parameter. +# +# + queryParam - Query parameter map +# + encodingMap - Details on serialization mechanism +# + return - Returns generated Path or error at failure of client initialization +isolated function getPathForQueryParam(map queryParam, map encodingMap = {}) returns string|error { + map queriesMap = http:getQueryMap(queryParam); + string[] param = []; + if queriesMap.length() > 0 { + param.push("?"); + foreach var [key, value] in queriesMap.entries() { + if value is () { + _ = queriesMap.remove(key); + continue; + } + Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : defaultEncoding; + if value is SimpleBasicType { + param.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + param.push(getSerializedArray(key, value, encodingData.style, encodingData.explode)); + } else if value is record {} { + if encodingData.style == DEEPOBJECT { + param.push(getDeepObjectStyleRequest(key, value)); + } else { + param.push(getFormStyleRequest(key, value, encodingData.explode)); + } + } else { + param.push(key, "=", value.toString()); + } + param.push("&"); + } + _ = param.pop(); + } + string restOfPath = string:'join("", ...param); + return restOfPath; +} diff --git a/docs/spec/openapi.json b/docs/spec/openapi.json new file mode 100644 index 0000000..b5bab6d --- /dev/null +++ b/docs/spec/openapi.json @@ -0,0 +1,337 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "Video Conferencing Extension", + "description" : "These APIs allow you to specify URLs that can be used to interact with a video conferencing application, to allow HubSpot to add video conference links to meeting requests with contacts.", + "version" : "v3", + "x-hubspot-product-tier-requirements" : { + "marketing" : "FREE", + "sales" : "FREE", + "service" : "FREE", + "cms" : "STARTER" + }, + "x-hubspot-documentation-banner" : "NONE", + "x-hubspot-api-use-case" : "If your company often conducts meetings over Zoom, use this API to enable users to add Zoom information to meeting invitations when schedule meetings in HubSpot.", + "x-hubspot-related-documentation" : [ { + "name" : "Video Conference Guide", + "url" : "https://developers.hubspot.com/docs/guides/api/crm/extensions/video-conferencing" + } ], + "x-hubspot-introduction" : "Use the video conference API to add video conferencing options when scheduling a meeting in HubSpot." + }, + "servers" : [ { + "url" : "https://api.hubapi.com/crm/v3/extensions/videoconferencing/settings" + } ], + "tags" : [ { + "name" : "Settings" + } ], + "paths" : { + "/{appId}" : { + "get" : { + "tags" : [ "Settings" ], + "summary" : "Get settings", + "description" : "Return the settings for a video conference application with the specified ID.", + "operationId" : "get-/crm/v3/extensions/videoconferencing/settings/{appId}_getById", + "parameters" : [ { + "name" : "appId", + "in" : "path", + "description" : "The ID of the video conference application. This is the identifier of the application created in your HubSpot developer portal.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ExternalSettings" + } + } + } + }, + "default" : { + "description" : "An error occurred.", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Error" + } + } + } + } + }, + "security" : [ { + "developer_hapikey" : [ ] + } ] + }, + "put" : { + "tags" : [ "Settings" ], + "summary" : "Update settings", + "description" : "Updates the settings for a video conference application with the specified ID.", + "operationId" : "put-/crm/v3/extensions/videoconferencing/settings/{appId}_replace", + "parameters" : [ { + "name" : "appId", + "in" : "path", + "description" : "The ID of the video conference application. This is the identifier of the application created in your HubSpot developer portal.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ExternalSettings" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ExternalSettings" + } + } + } + }, + "default" : { + "description" : "An error occurred.", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Error" + } + } + } + } + }, + "security" : [ { + "developer_hapikey" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Settings" ], + "summary" : "Delete settings", + "description" : "Deletes the settings for a video conference application with the specified ID.", + "operationId" : "delete-/crm/v3/extensions/videoconferencing/settings/{appId}_archive", + "parameters" : [ { + "name" : "appId", + "in" : "path", + "description" : "The ID of the video conference application. This is the identifier of the application created in your HubSpot developer portal.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "204" : { + "description" : "No content", + "content" : { } + }, + "default" : { + "description" : "An error occurred.", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Error" + } + } + } + } + }, + "security" : [ { + "developer_hapikey" : [ ] + } ] + } + } + }, + "components" : { + "schemas" : { + "ErrorDetail" : { + "required" : [ "message" ], + "type" : "object", + "properties" : { + "subCategory" : { + "type" : "string", + "description" : "A specific category that contains more specific detail about the error" + }, + "code" : { + "type" : "string", + "description" : "The status code associated with the error detail" + }, + "in" : { + "type" : "string", + "description" : "The name of the field or parameter in which the error was found." + }, + "context" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : "Context about the error condition", + "example" : { + "missingScopes" : [ "scope1", "scope2" ] + } + }, + "message" : { + "type" : "string", + "description" : "A human readable message describing the error along with remediation steps where appropriate" + } + } + }, + "ExternalSettings" : { + "required" : [ "createMeetingUrl" ], + "type" : "object", + "properties" : { + "userVerifyUrl" : { + "type" : "string", + "description" : "The URL that HubSpot will use to verify that a user exists in the video conference application.", + "example" : "https://example.com/user-verify", + "nullable": true + }, + "fetchAccountsUri" : { + "type" : "string", + "example": "https://example.xyz/1", + "nullable": true + }, + "createMeetingUrl" : { + "type" : "string", + "description" : "The URL that HubSpot will send requests to create a new video conference.", + "example" : "https://example.com/create-meeting" + }, + "updateMeetingUrl" : { + "type" : "string", + "description" : "The URL that HubSpot will send updates to existing meetings. Typically called when the user changes the topic or times of a meeting.", + "example" : "https://example.com/update-meeting", + "nullable": true + }, + "deleteMeetingUrl" : { + "type" : "string", + "description" : "The URL that HubSpot will send notifications of meetings that have been deleted in HubSpot.", + "example" : "https://example.com/delete-meeting", + "nullable": true + } + }, + "description" : "The URLs of the various actions provided by the video conferencing application. All URLs must use the `https` protocol.", + "example" : { + "createMeetingUrl" : "https://example.com/create-meeting", + "updateMeetingUrl" : "https://example.com/update-meeting", + "deleteMeetingUrl" : "https://example.com/delete-meeting", + "userVerifyUrl" : "https://example.com/user-verify" + } + }, + "Error" : { + "required" : [ "category", "correlationId", "message" ], + "type" : "object", + "properties" : { + "subCategory" : { + "type" : "string", + "description" : "A specific category that contains more specific detail about the error" + }, + "context" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : "Context about the error condition", + "example" : { + "missingScopes" : [ "scope1", "scope2" ], + "invalidPropertyName" : [ "propertyValue" ] + } + }, + "correlationId" : { + "type" : "string", + "description" : "A unique identifier for the request. Include this value with any error reports or support tickets", + "format" : "uuid", + "example" : "aeb5f871-7f07-4993-9211-075dc63e7cbf" + }, + "links" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + }, + "description" : "A map of link names to associated URIs containing documentation about the error or recommended remediation steps", + "example" : { + "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base" + } + }, + "message" : { + "type" : "string", + "description" : "A human readable message describing the error along with remediation steps where appropriate", + "example" : "Invalid input (details will vary based on the error)" + }, + "category" : { + "type" : "string", + "description" : "The error category", + "example" : "VALIDATION_ERROR" + }, + "errors" : { + "type" : "array", + "description" : "further information about the error", + "items" : { + "$ref" : "#/components/schemas/ErrorDetail" + } + } + }, + "example" : { + "message" : "Invalid input (details will vary based on the error)", + "correlationId" : "aeb5f871-7f07-4993-9211-075dc63e7cbf", + "category" : "VALIDATION_ERROR", + "links" : { + "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base" + } + } + } + }, + "responses" : { + "Error" : { + "description" : "An error occurred.", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Error" + } + } + } + } + }, + "securitySchemes" : { + "developer_hapikey" : { + "type" : "apiKey", + "name" : "hapikey", + "in" : "query", + "description" : "HubSpot developer API key" + } + } + }, + "x-hubspot-available-client-libraries" : [ "PHP", "Node", "Ruby", "Python" ], + "x-hubspot-product-tier-requirements" : { + "marketing" : "FREE", + "sales" : "FREE", + "service" : "FREE", + "cms" : "STARTER" + }, + "x-hubspot-documentation-banner" : "NONE" +} diff --git a/docs/spec/sanitations.md b/docs/spec/sanitations.md index d15881a..fca021d 100644 --- a/docs/spec/sanitations.md +++ b/docs/spec/sanitations.md @@ -1,24 +1,123 @@ -_Author_: \ -_Created_: \ -_Updated_: \ +_Author_: @rtweera\ +_Created_: 2025/02/13 \ +_Updated_: 2025/02/14 \ _Edition_: Swan Lake # Sanitation for OpenAPI specification -This document records the sanitation done on top of the official OpenAPI specification from HubSpot CRM Video conference. -The OpenAPI specification is obtained from (TODO: Add source link). +This document records the sanitation done on top of the official OpenAPI specification from HubSpot CRM Video conference. +The OpenAPI specification is obtained from [Hubspot API Reference](https://github.com/HubSpot/HubSpot-public-api-spec-collection/blob/main/PublicApiSpecs/CRM/Video%20Conferencing%20Extension/Rollouts/148903/v3/videoConferencingExtension.json). These changes are done in order to improve the overall usability, and as workarounds for some known language limitations. -[//]: # (TODO: Add sanitation details) -1. -2. -3. +1. Change the `url` property of the objects in servers array + + - **Original**: + `https://api.hubspot.com` + + - **Updated**: + `https://api.hubapi.com/crm/v3/extensions/videoconferencing/settings` + + - **Reason**: This change of adding the common prefix `crm/v3/extensions/videoconferencing/settings` to the base url makes it easier to access the endpoints using the client, also makes the code readable. + +2. Update the API `paths` + + - **Original**: Paths included the common prefix above `(i.e. /crm/v3/extensions/videoconferencing/settings)` in each endpoint. + `/crm/v3/extensions/videoconferencing/settings/{appId}` + + - **Updated**: Common prefix removed from path endpoints. + `/{appId}` + + - **Reason**: This simplifies the API paths making them shorter and easier to read. + +3. Add example for `fetchAccountsUri` object in External settings for `put` method + + - **Original**: No example was present. + + ```json + { + "fetchAccountsUri" : { + "type" : "string" + } + } + ``` + + - **Updated**: Example section added with a sample URI. + + ```json + { + "fetchAccountsUri" : { + "type" : "string", + "example" : "https://example.xyz/1" + } + } + ``` + + - **Reason**: This indicates the proper format of the URI to avoid validation errors. + +4. Add nullable property for necessary objects in `ExternalSettings` component + + - **Original**: No nullable property was present. + + ```json + { + "updateMeetingUrl" : { + "type" : "string", + "description" : "The URL that HubSpot will send updates to existing meetings. Typically called when the user changes the topic or times of a meeting.", + "example" : "https://example.com/update-meeting" + } + } + ``` + + - **Updated**: Nullable property was added for `updateMeetingUrl`, `deleteMeetingUrl`, `fetchAccountsUri`, `userVerifyUrl`. + + ```json + { + "updateMeetingUrl" : { + "type" : "string", + "description" : "The URL that HubSpot will send updates to existing meetings. Typically called when the user changes the topic or times of a meeting.", + "example" : "https://example.com/update-meeting", + "nullable": true + }, + } + ``` + + - **Reason**: This nullable property ensures the correct generation of client resource functions to handle null values in json response fields. + +5. Add field description for `hapikey` property + + - **Original**: No field descrition was present. + + ```json + { + "developer_hapikey" : { + "type" : "apiKey", + "name" : "hapikey", + "in" : "query" + } + } + ``` + + - **Updated**: Description was added. + + ```json + { + "developer_hapikey" : { + "type" : "apiKey", + "name" : "hapikey", + "in" : "query", + "description" : "HubSpot developer API key" + } + } + ``` + + - **Reason**: This helps to find the correct key from HubSpot easily. ## OpenAPI cli command The following command was used to generate the Ballerina client from the OpenAPI specification. The command should be executed from the repository root directory. ```bash -# TODO: Add OpenAPI CLI command used to generate the client +bal openapi -i docs/spec/openapi.json --mode client --license docs/license.txt -o ballerina ``` -Note: The license year is hardcoded to 2024, change if necessary. + +Note: The license year is hardcoded to 2025, change if necessary.