diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 384e783..d21ad67 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -20,22 +20,5 @@ jobs: env: JAVA_HOME: /usr/lib/jvm/default-jvm - # Build Module Examples - - name: Ballerina Examples Build - run: chmod +x ./examples/build.sh && ./examples/build.sh build - env: - JAVA_HOME: /usr/lib/jvm/default-jvm - - # Test Ballerina Project - - name: Ballerina Test - # tests will be skipped if the PR is from a forked repository (as the secrets are not available) - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} - run: bal test ./ballerina --test-report --code-coverage --coverage-format=xml - env: - JAVA_HOME: /usr/lib/jvm/default-jvm - ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }} - SECRET_ACCESS_KEY: ${{ secrets.SECRET_ACCESS_KEY }} - REGION: ${{ secrets.REGION }} - - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 087ec86..b14e157 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -2,7 +2,7 @@ distribution = "2201.8.0" org = "ballerinax" name = "aws.sns" -version = "2.2.0" +version = "3.0.0" license= ["Apache-2.0"] authors = ["Ballerina"] keywords = ["Communication/Notifications", "Cost/Freemium", "Vendor/Amazon"] diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..4ec27a9 --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,379 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.8.2" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.10.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.7.1" +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.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] +modules = [ + {org = "ballerina", packageName = "crypto", moduleName = "crypto"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.9.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.10.3" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {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.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" +modules = [ + {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {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"} +] +modules = [ + {org = "ballerina", packageName = "lang.array", moduleName = "lang.array"} +] + +[[package]] +org = "ballerina" +name = "lang.boolean" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "lang.boolean", moduleName = "lang.boolean"} +] + +[[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"} +] +modules = [ + {org = "ballerina", packageName = "lang.int", moduleName = "lang.int"} +] + +[[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"} +] +modules = [ + {org = "ballerina", packageName = "lang.regexp", moduleName = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} +] + +[[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.9.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.9.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"} +] +modules = [ + {org = "ballerina", packageName = "mime", moduleName = "mime"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.10.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" +version = "1.2.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "random" +version = "1.5.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] +modules = [ + {org = "ballerina", packageName = "random", moduleName = "random"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.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.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "time", moduleName = "time"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "url", moduleName = "url"} +] + +[[package]] +org = "ballerinax" +name = "aws.sns" +version = "2.2.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.boolean"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "random"}, + {org = "ballerina", name = "test"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"}, + {org = "ballerinax", name = "client.config"} +] +modules = [ + {org = "ballerinax", packageName = "aws.sns", moduleName = "aws.sns"} +] + +[[package]] +org = "ballerinax" +name = "client.config" +version = "1.0.1" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "oauth2"} +] +modules = [ + {org = "ballerinax", packageName = "client.config", moduleName = "client.config"} +] + diff --git a/ballerina/client.bal b/ballerina/client.bal index fe2deca..ac47a51 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -1,6 +1,6 @@ -// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // -// WSO2 Inc. licenses this file to you under the Apache License, +// 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 @@ -19,9 +19,10 @@ import ballerina/http; import ballerina/lang.array; import ballerina/time; import ballerinax/'client.config; +import ballerina/url; -# Ballerina Amazon SNS API connector provides the capability to access Amazon Simple Notification Service. -# This connector lets you to create and manage the sns topics and subscriptions. +# Ballerina Amazon SNS API connector provides the capability to access Amazon's Simple Notification Service. +# This connector allows you to create and manage SNS topics and subscriptions. # # + amazonSNSClient - Connector HTTP endpoint # + accessKeyId - Amazon API access key @@ -44,9 +45,9 @@ public isolated client class Client { # + httpClientConfig - HTTP Configuration # + return - `http:Error` in case of failure to initialize or `null` if successfully initialized public isolated function init(ConnectionConfig config) returns error? { - self.accessKeyId = config.credentials.accessKeyId; - self.secretAccessKey = config.credentials.secretAccessKey; - self.securityToken = (config?.credentials?.securityToken is string) ? (config?.credentials?.securityToken) : (); + self.accessKeyId = config.accessKeyId; + self.secretAccessKey = config.secretAccessKey; + self.securityToken = (config?.securityToken is string) ? (config?.securityToken) : (); self.region = config.region; self.amazonHost = "sns." + self.region + ".amazonaws.com"; string baseURL = "https://" + self.amazonHost; @@ -56,312 +57,934 @@ public isolated client class Client { self.amazonSNSClient = check new (baseURL, httpClientConfig); } - # Create a topic. + # Creates a topic to which notifications can be published. This action is idempotent, so if the requester already + # owns a topic with the specified name, that topic's ARN is returned without creating a new topic. # # + name - Name of topic # + attributes - Topic attributes - # + tags - Tags for the Topic - # + return - Created topic ARN on success else an `error` - @display {label: "Create Topic"} - isolated remote function createTopic(@display {label: "Topic Name"} string name, - @display {label: "Topic Attributes"} TopicAttributes? attributes = (), - @display {label: "Tags for Topic"} map? tags = ()) - returns @display {label: "Created Topic ARN"} CreateTopicResponse|error { - map parameters = {}; - parameters = createQueryString("CreateTopic", parameters); + # + dataProtectionPolicy - The body of the policy document you want to use for this topic. + # You can only add one policy per topic + # + tags - List of tags to add to a new topic + # + return - `CreateTopicResponse` or `sns:Error` in case of failure + isolated remote function createTopic(string name, InitializableTopicAttributes? attributes = (), + json? dataProtectionPolicy = (), map? tags = ()) returns string|Error { + map parameters = initiateRequest("CreateTopic"); parameters["Name"] = name; - parameters = check addTopicOptionalParameters(parameters, attributes, tags); - http:Request request = check self.generateRequest(self.createPayload(parameters)); - xml response = check sendRequest(self.amazonSNSClient, request); - CreateTopicResponse createdTopicResponse = check xmlToCreatedTopic(response); - return createdTopicResponse; + + if (attributes is InitializableTopicAttributes) { + _ = check validateInitializableTopicAttributes(attributes); + + // The suffix ".fifo" must be added to all topic names that are FIFO + if attributes.fifoTopic is boolean && attributes.fifoTopic && !name.endsWith(".fifo") { + parameters["Name"] = name + ".fifo"; + } + + record {} formattedTopicAttributes = check formatAttributes(attributes, SPECIAL_TOPIC_ATTRIBUTES_MAP); + setAttributes(parameters, formattedTopicAttributes); + } + + if dataProtectionPolicy != () { + parameters["DataProtectionPolicy"] = dataProtectionPolicy.toString(); + } + + if (tags is map) { + setTags(parameters, tags); + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return (check response.CreateTopicResponse.CreateTopicResult.TopicArn).toString(); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } } - # Subscribe to a topic. - # - # + topicArn - The ARN of the topic you want to subscribe to - # + protocol - The protocol that you want to use - # + endpoint - The endpoint that you want to receive notifications - # + 'returnSubscriptionArn - Sets whether the response from the subscribe request includes the subscription ARN - # + attributes - Subscription attributes - # + return - Created subscription ARN on success else an `error` - @display {label: "Create Subscription"} - isolated remote function subscribe(@display {label: "Topic ARN"} string topicArn, - @display {label: "Protocol"} AwsProtocol protocol, - @display {label: "Endpoint For Subscription"} string? endpoint = (), - @display {label: "Subscription ARN Status"} boolean? returnSubscriptionArn = (), - @display {label: "Subscription Attributes"} SubscriptionAttribute? attributes = ()) - returns @display {label: "Subscription ARN"} SubscribeResponse|error { - map parameters = {}; - parameters = createQueryString("Subscribe", parameters); + # Deletes a topic and all its subscriptions. Deleting a topic might prevent some messages previously sent to the + # topic from being delivered to subscribers. This action is idempotent, so deleting a topic that does not exist + # does not result in an error. + # + # + topicArn - The Amazon Resource Name (ARN) of the topic to be deleted + # + return - `()` or `sns:Error` in case of failure + isolated remote function deleteTopic(string topicArn) returns Error? { + map parameters = initiateRequest("DeleteTopic"); parameters["TopicArn"] = topicArn; - parameters["Protocol"] = protocol; - parameters = addSubscriptionOptionalParameters(parameters, endpoint, returnSubscriptionArn, attributes); - http:Request request = check self.generateRequest(self.createPayload(parameters)); - xml response = check sendRequest(self.amazonSNSClient, request); - SubscribeResponse createdSubscriptionResponse = check xmlToCreatedSubscription(response); - return createdSubscriptionResponse; - } - # Publish a message in a topic. - # - # + message - The message content to publish - # + topicArn - The ARN of the topic - # + targetArn - The ARN of the target resource - # + subject - The subject of the message - # + phoneNumber - The phone number to send message - # + messageStructure - The message structure - # + messageDeduplicationId - The message dupilcation Id - # + messageGroupId - The message group Id - # + messageAttributes - The message attributes of type 'MessageAttribute' - # + return - Result of message published on success else an `error` - @display {label: "Publish Message"} - isolated remote function publish(@display {label: "Message"} string message, - @display {label: "Topic ARN"} string? topicArn = (), - @display {label: "Target ARN"} string? targetArn = (), - @display {label: "Message Subject"} string? subject = (), - @display {label: "Phone Number"} string? phoneNumber = (), - @display {label: "Message Structure"} string? messageStructure = (), - @display {label: "Message Duplication Id"} string? messageDeduplicationId = (), - @display {label: "Message Group Id"} string? messageGroupId = (), - @display {label: "Message Attributes"} MessageAttribute? messageAttributes = ()) - returns @display {label: "Published result"} PublishResponse|error { - map parameters = {}; - parameters = createQueryString("Publish", parameters); - parameters["Message"] = message; - addPublishOptionalParameters(parameters, topicArn, targetArn, subject, phoneNumber, messageStructure, messageGroupId, messageDeduplicationId, messageAttributes); - http:Request request = check self.generateRequest(self.createPayload(parameters)); - xml response = check sendRequest(self.amazonSNSClient, request); - PublishResponse publishResponse = check xmlToPublishResponse(response); - return publishResponse; + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); } - # Unsubscribe to a topic. - # - # + subscriptionArn - The ARN of the subscription - # + return - Result of unsubscription on success else an `error` - @display {label: "Unsubscribe Topic"} - isolated remote function unsubscribe(@display {label: "Subscription ARN"} string subscriptionArn) - returns @display {label: "Unsubscription Status"} UnsubscribeResponse|error { - map parameters = {}; - parameters = createQueryString("Unsubscribe", parameters); - parameters["SubscriptionArn"] = subscriptionArn; - http:Request request = check self.generateRequest(self.createPayload(parameters)); - xml response = check sendRequest(self.amazonSNSClient, request); - UnsubscribeResponse unsubscribeResponse = check xmlToUnsubscribeResponse(response); - return unsubscribeResponse; + # Returns the topics ARNs that are owned by the AWS account. + # + # + return - A stream of topic ARNs + isolated remote function listTopics() returns stream { + TopicStream topicsStreamObject = new (self.amazonSNSClient, self.generateRequest); + stream topicsStream = new (topicsStreamObject); + return topicsStream; } - # Delete a topic. - # - # + topicArn - The ARN of the topic - # + return - Result of deleted topic on success else an `error` - @display {label: "Delete Topic"} - isolated remote function deleteTopic(@display {label: "Topic ARN"} string topicArn) - returns @display {label: "Delete Status"} DeleteTopicResponse|error { - map parameters = {}; - parameters = createQueryString("DeleteTopic", parameters); + # Retrieves an existing topic along with its attributes. + # + # + topicArn - The Amazon Resource Name (ARN) of the topic + # + return - `Topic` or `sns:Error` in case of failure + isolated remote function getTopicAttributes(string topicArn) returns GettableTopicAttributes|Error { + map parameters = initiateRequest("GetTopicAttributes"); parameters["TopicArn"] = topicArn; - http:Request request = check self.generateRequest(self.createPayload(parameters)); - xml response = check sendRequest(self.amazonSNSClient, request); - DeleteTopicResponse deletedResponse = check xmlToDeletedTopicResponse(response); - return deletedResponse; - } - # Verifies an endpoint owner's intent to receive messages by validating the token sent to the endpoint by an earlier Subscribe action. - # - # + token - The token to confirm subscription - # + topicArn - The ARN of the topic - # + authenticateOnUnsubscribe - Authenticate on unsubscription - # + return - Result of subscription confirmation on success else an `error` - @display {label: "Confirm Subscription"} - isolated remote function confirmSubscription(@display {label: "Confirmation Token"} string token, - @display {label: "Topic ARN"} string topicArn, - @display {label: "Unsubscription Need Authentication"} string? authenticateOnUnsubscribe = ()) - returns @display {label: "Confirm Subscription Status"} ConfirmedSubscriptionResponse|error { - map parameters = {}; - parameters = buildQueryString("ConfirmSubscription", parameters, token, topicArn); - parameters = check addOptionalStringParameters(parameters, authenticateOnUnsubscribe); - http:Request request = check self.generateRequest(self.createPayload(parameters)); - xml response = check sendRequest(self.amazonSNSClient, request); - ConfirmedSubscriptionResponse confirmedSubscriptionResponse = check xmlToConfirmedSubscriptionResponse(response); - return confirmedSubscriptionResponse; - } + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); - # Add a topic attributes for a topic created. - # - # + topicArn - Name of topic - # + attributeName - Name of a attribute - # + attributeValue - Value corresponding to that attribute - # + return - Null on success else an `error` - @display {label: "Add Topic Attribute"} - isolated remote function setTopicAttribute(@display {label: "Topic ARN"} string topicArn, - @display {label: "Attribute Name"} string attributeName, - @display {label: "Attribute Value"} string? attributeValue = ()) - returns @display {label: "Topic Attribute Status"} error? { - map parameters = {}; - parameters = buildQueryString("SetTopicAttributes", parameters, topicArn, attributeName); - parameters = check addOptionalStringParameters(parameters, attributeValue); - http:Request request = check self.generateRequest(self.createPayload(parameters)); - http:Response|error httpResponse = self.amazonSNSClient->post("/", request); - xml response = check handleResponse(httpResponse); - return xmlToHttpResponse(response); + do { + json attributes = check response.GetTopicAttributesResponse.GetTopicAttributesResult.Attributes; + return check mapJsonToGettableTopicAttributes(attributes); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } } - # Get values of topic attributes for a topic. - # - # + topicArn - ARN of a topic - # + return - Array of TopicAttribute on success else an `error` - @display {label: "Get Topic Attributes"} - isolated remote function getTopicAttributes(@display {label: "Topic ARN"} string topicArn) - returns @display {label: "Topic Attributes"} GetTopicAttributesResponse|error { - map parameters = {}; - parameters = createQueryString("GetTopicAttributes", parameters); + # Modifies the a single attribute of an Amazon SNS topic. + # + # + topicArn - The Amazon Resource Name (ARN) of the topic + # + attributeName - The name of the attribute you want to set + # + value - The new value for the attribute + # + return - `()` or `sns:Error` in case of failure + isolated remote function setTopicAttributes(string topicArn, TopicAttributeName attributeName, + json|string|int|boolean value) returns Error? { + check validateTopicAttribute(attributeName, value); + + map parameters = initiateRequest("SetTopicAttributes"); parameters["TopicArn"] = topicArn; - http:Request request = check self.generateRequest(self.createPayload(parameters)); - http:Response|error httpResponse = self.amazonSNSClient->post("/", request); - xml response = check handleResponse(httpResponse); - return xmlToGetTopicAttributes(response); - } + parameters["AttributeName"] = attributeName; + parameters["AttributeValue"] = value.toString(); - # Add a SMS attributes for a SMS send. - # - # + attributes - SMSAttributes record contain attribute information - # + return - Null on success else an `error` - @display {label: "Add SMS Attribute"} - isolated remote function setSMSAttributes(@display {label: "SMS Attribute To Add"} SmsAttributes attributes) - returns @display {label: "SMS Attribute Status"} error? { - map parameters = {}; - parameters = buildQueryString("SetSMSAttributes", parameters); - parameters = setSmsAttributes(parameters, attributes); - http:Request request = check self.generateRequest(self.createPayload(parameters)); - http:Response|error httpResponse = self.amazonSNSClient->post("/", request); - xml response = check handleResponse(httpResponse); - return xmlToHttpResponse(response); + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); } - # Get values of SMS attributes. + # Publishes a message to an SNS topic, a phone number, or a mobile platform endpoint. + # + # + target - The target (topic ARN, target ARN or phone number) to which to publish the message to + # + message - The message to publish. If you are publishing to a topic and you want to send the same message to all + # transport protocols, include the text of the message as a `string` value. If you want to send + # different messages for each transport protocol, use a `MessageRecord` value + # + targetType - The type of target (topic, phone number, or application endpoint) to publish the message to + # + attributes - Attributes of the message + # + deduplicationId - Every message must have a unique `deduplicationId`, which is a token used for deduplication + # of sent messages. If a message with a particular `deduplicationId` is sent successfully, any + # message sent with the same `deduplicationId` during the 5-minute deduplication interval is + # treated as a duplicate. If the topic has `contentBasedDeduplication` set, the system + # generates a `deduplicationId` based on the contents of the message. Your `deduplicationId` + # overrides the generated one. Applies to FIFO topics only + # + groupId - Specifies the message group to which a message belongs to. Messages that belong to the same message + # group are processed in a FIFO manner (however, messages in different message groups might be processed + # out of order). Every message must include a `groupId`. Applies to FIFO topics only + # + return - `PublishMessageResponse` or `sns:Error` in case of failure + isolated remote function publish(string target, Message message, TargetType targetType = TOPIC, + map? attributes = (), string? deduplicationId = (), string? groupId = ()) + returns PublishMessageResponse|Error { + _ = check validatePublishParameters(target, targetType, groupId); + map parameters = initiateRequest("Publish"); + + if (targetType == TOPIC) { + parameters["TopicArn"] = target; + } else if (targetType == ARN) { + parameters["TargetArn"] = target; + } else { + parameters["PhoneNumber"] = target; + } + + if message is string { + parameters["Message"] = message; + } else { + parameters["MessageStructure"] = "json"; + + if message.hasKey("subject") { + parameters["Subject"] = message.subject; + _ = message.remove("subject"); + } + + parameters["Message"] = mapMessageRecordToJson(message).toJsonString(); + } + + if (deduplicationId is string) { + parameters["MessageDeduplicationId"] = deduplicationId; + } + + if (groupId is string) { + parameters["MessageGroupId"] = groupId; + } + + if (attributes is map) { + check setMessageAttributes(parameters, attributes); + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + PublishMessageResponse publishMessageResponse = { + messageId: (check response.PublishResponse.PublishResult.MessageId).toString() + }; + + json|error sequenceNumber = response.PublishResponse.PublishResult.SequenceNumber; + if sequenceNumber is json && sequenceNumber.toString() != "" { + publishMessageResponse.sequenceNumber = check sequenceNumber; + } + + return publishMessageResponse; + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Publishes up to ten messages to the specified topic. + # + # + topicArn - The Amazon Resource Name (ARN) of the topic + # + entries - A list of `PublishBatchRequestEntry` objects that contain the separate messages to publish + # + return - `PublishBatchResponse` or `sns:Error` in case of failure + isolated remote function publishBatch(string topicArn, PublishBatchRequestEntry[] entries) + returns PublishBatchResponse|Error { + map parameters = initiateRequest("PublishBatch"); + parameters["TopicArn"] = topicArn; + + check setPublishBatchEntries(parameters, entries); + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + PublishBatchResponse publishBatchResponse = { + successful: [], + failed: [] + }; + + json[] successful = check (check response.PublishBatchResponse.PublishBatchResult.Successful).ensureType(); + foreach [int, json] [_ , successfulEntry] in successful.enumerate() { + PublishBatchResultEntry entry = { + id: check successfulEntry.Id, + messageId: check successfulEntry.MessageId + }; + + if successfulEntry.SequenceNumber is json { + entry.sequenceNumber = (check successfulEntry.SequenceNumber); + } + + publishBatchResponse.successful.push(entry); + } + + json[] failed = check (check response.PublishBatchResponse.PublishBatchResult.Failed).ensureType(); + foreach [int, json] [_, failedEntry] in failed.enumerate() { + BatchResultErrorEntry entry = { + code: check failedEntry.Code, + id: check failedEntry.Id, + senderFault: check failedEntry.SenderFault + }; + + if failedEntry.Message is json { + entry.message = check failedEntry.Message; + } + + publishBatchResponse.failed.push(entry); + } + + return publishBatchResponse; + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Creates a subscription to a topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are + # not in the same AWS account, the endpoint owner must confirm the subscription. + # + # + topicArn - The Amazon Resource Name (ARN) of the topic you want to subscribe to + # + endpoint - The endpoint that you want to receive notifications to. + # + protocol - The protocol you want to use. + # + attributes - Attributes of the subscription + # + returnSubscriptionArn - Whether the response from the Subscribe request includes the subscription ARN, even if + # the subscription is not yet confirmed. + # + return - The ARN of the subscription if it is confirmed, or the string "pending confirmation" if the + # subscription requires confirmation. However, if the `returnSubscriptionArn` parameter is set to `true`, + # then the value is always the subscription ARN, even if the subscription requires confirmation. + isolated remote function subscribe(string topicArn, string endpoint, SubscriptionProtocol protocol, + SubscriptionAttributes? attributes = (), boolean returnSubscriptionArn = false) + returns string|Error { + map parameters = initiateRequest("Subscribe"); + parameters["TopicArn"] = topicArn; + parameters["Endpoint"] = endpoint; + parameters["Protocol"] = protocol; + parameters["ReturnSubscriptionArn"] = returnSubscriptionArn.toString(); + + if attributes is SubscriptionAttributes { + record {} formattedSubscriptionsAttributes = check formatAttributes(attributes); + setAttributes(parameters, formattedSubscriptionsAttributes); + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return (check response.SubscribeResponse.SubscribeResult.SubscriptionArn).toString(); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Verifies an endpoint owner's intent to receive messages by validating the token sent to the endpoint by an + # earlier subscribe action. # - # + attributes - SMS attribute names - # + return - Array of SmsAttribute on success else an `error` - isolated remote function getSMSAttributes(string[]? attributes = ()) returns GetSMSAttributesResponse|error { - map parameters = {}; - parameters = buildQueryString("GetSMSAttributes", parameters); - if (attributes is string[]) { - parameters = addSmsAttributes(parameters, attributes); - } - http:Request request = check self.generateRequest(self.createPayload(parameters)); - http:Response|error httpResponse = self.amazonSNSClient->post("/", request); - xml response = check handleResponse(httpResponse); - return xmlToGetSmsAttributes(response); - } + # + topicArn - The ARN of the topic for which you wish to confirm a subscription + # + token - Short-lived token sent to an endpoint during the subscribe action + # + authenticateOnUnsubscribe - Disallows unauthenticated unsubscribes of the subscription. If the value of this + # parameter is `true`, then only the topic owner and the subscription owner can + # unsubscribe the endpoint. + # + return - The ARN of the created subscription or `sns:Error` in case of failure + isolated remote function confirmSubscription(string topicArn, string token, boolean? authenticateOnUnsubscribe = ()) + returns string|Error { + map parameters = initiateRequest("ConfirmSubscription"); + parameters["TopicArn"] = topicArn; + parameters["Token"] = token; - # Add a subscription attributes for a subscription created. - # - # + subscriptionArn - Name of subscription - # + attributeName - Name of a attribute - # + attributeValue - Value corresponding to that attribute - # + return - Null on success else an `error` - @display {label: "Add Subscription Attribute"} - isolated remote function setSubscriptionAttribute(@display {label: "Subscription ARN"} string subscriptionArn, - @display {label: "Attribute Name"} string attributeName, - @display {label: "Attribute Value"} string? attributeValue = ()) - returns @display {label: "Subscription Attribute Status"} error? { - map parameters = {}; - parameters[ACTION] = "SetSubscriptionAttributes"; - parameters[VERSION] = VERSION_NUMBER; + if authenticateOnUnsubscribe is boolean { + parameters["AuthenticateOnUnsubscribe"] = authenticateOnUnsubscribe.toString(); + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return (check response.ConfirmSubscriptionResponse.ConfirmSubscriptionResult.SubscriptionArn).toString(); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Retrieves the requester's subscriptions. + # + # + topicArn - The ARN of the topic for which you wish list the subscriptions + # + return - A stream of `Subscription` records + isolated remote function listSubscriptions(string? topicArn = ()) returns stream { + SubscriptionStream subscriptionsStreamObject = new (self.amazonSNSClient, self.generateRequest, topicArn); + stream subscriptionsStream = new (subscriptionsStreamObject); + return subscriptionsStream; + } + + # Retrieves the attributes of the requested subscription. + # + # + subscriptionArn - The ARN of the subscription + # + return - `SubscriptionObject` or `sns:Error` in case of failure + isolated remote function getSubscriptionAttributes(string subscriptionArn) returns GettableSubscriptionAttributes|Error { + map parameters = initiateRequest("GetSubscriptionAttributes"); parameters["SubscriptionArn"] = subscriptionArn; - parameters["AttributeName"] = attributeName; - if (attributeValue is string) { - parameters["AttributeValue"] = attributeValue; - } - parameters = buildQueryString("SetSubscriptionAttributes", parameters, subscriptionArn, attributeName); - parameters = check addOptionalStringParameters(parameters, attributeValue); - http:Request request = check self.generateRequest(self.createPayload(parameters)); - http:Response|error httpResponse = self.amazonSNSClient->post("/", request); - xml response = check handleResponse(httpResponse); - return xmlToHttpResponse(response); - } - # Get values of subscription attributes for a subscription. - # - # + subscriptionArn - ARN of a subscription - # + return - Array of SubscriptionAttribute on success else an `error` - @display {label: "Get Subscription Attributes"} - isolated remote function getSubscriptionAttributes(@display {label: "Subscription ARN"} string subscriptionArn) - returns @display {label: "Subscription Attributes"} GetSubscriptionAttributesResponse|error { - map parameters = {}; - parameters = buildQueryString("GetSubscriptionAttributes", parameters); + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + json attributes = + check response.GetSubscriptionAttributesResponse.GetSubscriptionAttributesResult.Attributes; + return check mapJsonToSubscriptionAttributes(attributes); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Modifies a single of a subscription. + # + # + subscriptionArn - The ARN of the subscription to modify + # + attributeName - The name of the attribute you want to set + # + value - The new value for the attribute + # + return - `()` or `sns:Error` in case of failure + isolated remote function setSubscriptionAttributes(string subscriptionArn, SubscriptionAttributeName attributeName, + json|FilterPolicyScope|boolean|string value) returns Error? { + check validateSubscriptionAttribute(attributeName, value); + + map parameters = initiateRequest("SetSubscriptionAttributes"); + parameters["SubscriptionArn"] = subscriptionArn; + parameters["AttributeName"] = attributeName.toString(); + parameters["AttributeValue"] = value.toString(); + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Deletes a subscription. If the subscription requires authentication for deletion, only the owner of the + # subscription or the owner of the topic may unsubscribe. If the unsubscribe call does not require authentication + # and the requester is not the subscription owner, a final cancellation message is delivered to the endpoint, so + # that the endpoint owner can easily resubscribe to the topic if the unsubscribe request was unintended. + # + # + subscriptionArn - The ARN of the subscription to be deleted + # + return - `()` or `sns:Error` in case of failure + isolated remote function unsubscribe(string subscriptionArn) returns Error? { + map parameters = initiateRequest("Unsubscribe"); parameters["SubscriptionArn"] = subscriptionArn; - http:Request request = check self.generateRequest(self.createPayload(parameters)); - http:Response|error httpResponse = self.amazonSNSClient->post("/", request); - xml response = check handleResponse(httpResponse); - return xmlToGetSubscriprionAttributes(response); + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Creates a platform application object for one of the supported push notification services. You must specify + # `auth.platformPrincipal` and `auth.platformCredential` parameters. + # - for `ADM` the `platformPrincipal` is the `client id` and `platformCredential` is the `client secret` + # - for `APNS` and `APNS_SANDBOX` using certificate credentials, the `platformPrincipal` is the `SSL certificate` + # and `platformCredential` is the `private key` + # - for `APNS` and `APNS_SANDBOX` using token credentials, the `platformPrincipal` is the `signing key ID` and the + # `platformCredential` is the `signing key` + # - for `FCM` there is no `platformPrincipal` and `platformCredential` is the `API key` + # - for `BAIDU` the `platformPrincipal` is the `API key` and `platformCredential` is the `secret key` + # - for `MPNS` the `platformPrincipal` is the `TLS certificate` and `platformCredential` is the `private key` + # - for `WNS` the `platformPrincipal` is the `Package Security Identifier` and `platformCredential` is the + # `secret key` + # + # + name - The name of the platform application object to create + # + platform - The platform of the application + # + attributes - Attributes of the platform application + # + auth - Authentication credentials for the platform application + # + return - The ARN of the platform application if successful, or `sns:Error` in case of failure + isolated remote function createPlatformApplication(string name, Platform platform, + PlatformApplicationAuthentication auth, PlatformApplicationAttributes? attributes = ()) returns string|Error { + map parameters = initiateRequest("CreatePlatformApplication"); + parameters["Name"] = name; + parameters["Platform"] = platform; + + record {} attributesRecord = {}; + if attributes is PlatformApplicationAttributes { + attributesRecord = { + ...auth, + ...attributes + }; + } else { + attributesRecord = auth; + } + + record {} formattedPlatformApplicationAttributes = check formatAttributes(attributesRecord); + setAttributes(parameters, formattedPlatformApplicationAttributes); + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return (check response.CreatePlatformApplicationResponse.CreatePlatformApplicationResult + .PlatformApplicationArn).toString(); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Retrives the platform application objects for the supported push notification services. + # + # + return - A stream of `PlatformApplication` records + isolated remote function listPlatformApplications() returns stream { + PlatformApplicationStream platformApplicationsStreamObject = new (self.amazonSNSClient, self.generateRequest); + stream platformApplicationsStream = new (platformApplicationsStreamObject); + return platformApplicationsStream; + }; + + # Retrieves a platform application object for one of the supported push notification services. + # + # + platformApplicationArn - The ARN of the platform application object to retrieve + # + return - `PlatformApplication` or `sns:Error` in case of failure + isolated remote function getPlatformApplicationAttributes(string platformApplicationArn) + returns RetrievablePlatformApplicationAttributes|Error { + map parameters = initiateRequest("GetPlatformApplicationAttributes"); + parameters["PlatformApplicationArn"] = platformApplicationArn; + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + json attributes = + check response.GetPlatformApplicationAttributesResponse.GetPlatformApplicationAttributesResult + .Attributes; + return check mapJsonToPlatformApplicationAttributes(attributes); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Modifies the attributes of a platform application object for one of the supported push notification services. + # + # + platformApplicationArn - The ARN of the platform application object to modify + # + attributes - The attributes to modify + # + return - `()` or `sns:Error` in case of failure + isolated remote function setPlatformApplicationAttributes(string platformApplicationArn, + SettablePlatformApplicationAttributes attributes) returns Error? { + map parameters = initiateRequest("SetPlatformApplicationAttributes"); + parameters["PlatformApplicationArn"] = platformApplicationArn; + + record {} formattedPlatformApplicationAttributes = check formatAttributes(attributes); + setAttributes(parameters, formattedPlatformApplicationAttributes); + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Deletes a platform application object for one of the supported push notification services. + # + # + platformApplicationArn - The ARN of the platform application object to delete + # + return - `()` or `sns:Error` in case of failure + isolated remote function deletePlatformApplication(string platformApplicationArn) returns Error? { + map parameters = initiateRequest("DeletePlatformApplication"); + parameters["PlatformApplicationArn"] = platformApplicationArn; + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Creates an endpoint for a device and mobile app on one of the supported push notification services. This action is + # idempotent, so if the requester already owns an endpoint with the same device token and attributes, that + # endpoint's ARN is returned without creating a new endpoint. + # + # + platformApplicationArn - The ARN of the platform application + # + token - Unique identifier created by the notification service for an app on a device. The specific name for + # the token will vary, depending on which notification service is being used + # + attributes - Attributes of the endpoint + # + customUserData - Arbitrary user data to associate with the endpoint. Amazon SNS does not use this data + # + return - The ARN of the endpoint if successful, or `sns:Error` in case of failure + isolated remote function createEndpoint(string platformApplicationArn, string token, + EndpointAttributes? attributes = (), string? customUserData = ()) returns string|Error { + map parameters = initiateRequest("CreatePlatformEndpoint"); + parameters["PlatformApplicationArn"] = platformApplicationArn; + parameters["Token"] = token; + + if customUserData is string { + parameters["CustomUserData"] = customUserData; + } + + if (attributes is EndpointAttributes) { + record {} formattedTopicAttributes = check formatAttributes(attributes); + setAttributes(parameters, formattedTopicAttributes); + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return (check response.CreatePlatformEndpointResponse.CreatePlatformEndpointResult.EndpointArn).toString(); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Retrieves the endpoints associated with a specific platform application. + # + # + platformApplicationArn - The ARN of the platform application to retrieve endpoints for + # + return - A stream of `Endpoint` records + isolated remote function listEndpoints(string platformApplicationArn) + returns stream { + EndpointStream endpointsStreamObject = new (self.amazonSNSClient, self.generateRequest, platformApplicationArn); + stream endpointsStream = new (endpointsStreamObject); + return endpointsStream; + }; + + # Retrieves a platform application endpoint. + # + # + endpointArn - The ARN of the endpoint + # + return - The attributes of the endpoint or `sns:Error` in case of failure + isolated remote function getEndpointAttributes(string endpointArn) returns EndpointAttributes|Error { + map parameters = initiateRequest("GetEndpointAttributes"); + parameters["EndpointArn"] = endpointArn; + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + json attributes = check response.GetEndpointAttributesResponse.GetEndpointAttributesResult.Attributes; + return check mapJsonToEndpointAttributes(attributes); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Modifies the attributes of a platform application endpoint. + # + # + endpointArn - The ARN of the endpoint + # + attributes - The attributes to modify + # + return - `()` or `sns:Error` in case of failure + isolated remote function setEndpointAttributes(string endpointArn, EndpointAttributes attributes) + returns Error? { + map parameters = initiateRequest("SetEndpointAttributes"); + parameters["EndpointArn"] = endpointArn; + + record {} formattedPlatformApplicationAttributes = check formatAttributes(attributes); + setAttributes(parameters, formattedPlatformApplicationAttributes); + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Deletes a platform application endpoint. This action is idempotent. When you delete an endpoint that is also + # subscribed to a topic, then you must also unsubscribe the endpoint from the topic. + # + # + endpointArn - The ARN of the endpoint + # + return - `()` or `sns:Error` in case of failure + isolated remote function deleteEndpoint(string endpointArn) returns Error? { + map parameters = initiateRequest("DeleteEndpoint"); + parameters["EndpointArn"] = endpointArn; + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Adds a destination phone number to an AWS account in the SMS sandbox and sends a one-time password (OTP) to that + # phone number. + # + # + phoneNumber - The destination phone number to verify + # + languageCode - The language to use for sending the OTP + # + return - `()` or `sns:Error` in case of failure + isolated remote function createSMSSandboxPhoneNumber(string phoneNumber, LanguageCode? languageCode = EN_US) + returns Error? { + map parameters = initiateRequest("CreateSMSSandboxPhoneNumber"); + parameters["PhoneNumber"] = phoneNumber; + if languageCode is LanguageCode { + parameters["LanguageCode"] = languageCode.toString(); + } + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Verifies a destination phone number with a one-time password (OTP) for the calling AWS account. + # + # + phoneNumber - The destination phone number to verify + # + otp - The OTP sent to the destination number + # + return - `()` or `sns:Error` in case of failure + isolated remote function verifySMSSandboxPhoneNumber(string phoneNumber, string otp) returns Error? { + map parameters = initiateRequest("VerifySMSSandboxPhoneNumber"); + parameters["PhoneNumber"] = phoneNumber; + parameters["OneTimePassword"] = otp; + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Retrieves the current verified and pending destination phone numbers in the SMS sandbox. + # + # + return - A stream of `SMSSandboxPhoneNumber` records + isolated remote function listSMSSandboxPhoneNumbers() + returns stream { + SMSSandboxPhoneNumberStream SMSSandboxPhoneNumberStreamObject = + new (self.amazonSNSClient, self.generateRequest); + stream SMSSandboxPhoneNumberStream = new (SMSSandboxPhoneNumberStreamObject); + return SMSSandboxPhoneNumberStream; + }; + + # Deletes a verified or pending phone number from the SMS sandbox. + # + # + phoneNumber - The destination phone number to delete + # + return - `()` or `sns:Error` in case of failure + isolated remote function deleteSMSSandboxPhoneNumber(string phoneNumber) returns Error? { + map parameters = initiateRequest("DeleteSMSSandboxPhoneNumber"); + parameters["PhoneNumber"] = phoneNumber; + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Retrieves the SMS sandbox status for the calling AWS account in the target AWS Region. + # + # + return - The SMS sandbox status for the calling AWS account in the target AWS Region or `sns:Error` in case of + # failure + isolated remote function getSMSSandboxAccountStatus() returns boolean|Error { + map parameters = initiateRequest("GetSMSSandboxAccountStatus"); + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return check + (check response.GetSMSSandboxAccountStatusResponse.GetSMSSandboxAccountStatusResult.IsInSandbox) + .ensureType(boolean); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Retrieves the calling AWS account's dedicated origination numbers and their metadata. + # + # + return - A stream of `OriginationPhoneNumber` records + isolated remote function listOriginationNumbers() returns stream { + OriginationPhoneNumberStream originationPhoneNumberStreamObject = + new (self.amazonSNSClient, self.generateRequest); + stream orignationPhoneNumberStream = new (originationPhoneNumberStreamObject); + return orignationPhoneNumberStream; } - //Create a payload. - private isolated function createPayload(map parameters) returns string { - string payload = EMPTY_STRING; - int parameterNumber = 1; - foreach var [key, value] in parameters.entries() { - if (parameterNumber > 1) { - payload = payload + "&"; - } - payload = payload + key + "=" + value; - parameterNumber = parameterNumber + 1; + # Retrieves a list of phone numbers that are opted out, meaning you cannot send SMS messages to them. + # + # + return - A stream of phone numbers that are opted out + isolated remote function listPhoneNumbersOptedOut() returns stream { + OptedOutPhoneNumberStream optedOutPhoneNumberStreamObject = new (self.amazonSNSClient, self.generateRequest); + stream optedOutPhoneNumberStream = new (optedOutPhoneNumberStreamObject); + return optedOutPhoneNumberStream; + } + + # Checks whether a phone number is opted out, meaning you cannot send SMS messages to it. + # + # + phoneNumber - The phone number for which you want to check the opt out status. + # + return - `true` if the phone number is opted out, `false` otherwise or `sns:Error` in case of failure + isolated remote function checkIfPhoneNumberIsOptedOut(string phoneNumber) returns boolean|Error { + map parameters = initiateRequest("CheckIfPhoneNumberIsOptedOut"); + parameters["phoneNumber"] = phoneNumber; + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return check + (check response.CheckIfPhoneNumberIsOptedOutResponse.CheckIfPhoneNumberIsOptedOutResult.isOptedOut) + .ensureType(boolean); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); } - return payload; } - //Create request with headers attached. - private isolated function generateRequest(string payload) - returns http:Request|error { + # Requests to opt in a phone number that is opted out, which enables you to resume sending SMS messages to the + # number. You can opt in a phone number only once every 30 days. + # + # + phoneNumber - The destination phone number to opt in (in E.164 format) + # + return - `()` or `sns:Error` in case of failure + isolated remote function optInPhoneNumber(string phoneNumber) returns Error? { + map parameters = initiateRequest("OptInPhoneNumber"); + parameters["phoneNumber"] = phoneNumber; + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Adds tags to the specified Amazon SNS topic. A new tag with a key identical to that of an existing tag overwrites + # the existing tag. + # + # + topicArn - The ARN of the topic to which to add tags + # + tags - The tags to add to the specified topic + # + return - `()` or `sns:Error` in case of failure + isolated remote function tagResource(string topicArn, *Tags tags) returns Error? { + map parameters = initiateRequest("TagResource"); + parameters["ResourceArn"] = topicArn; + + if tags.length() is 0 { + return error Error("At least one tag must be specified."); + } + setTags(parameters, tags); + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Lists the tags for the specified Amazon SNS topic. + # + # + topicArn - The ARN of the topic for which to list tags + # + return - A `Tags` record consisting of the tags or an `sns:Error` in case of failure + isolated remote function listTags(string topicArn) returns Tags|Error { + map parameters = initiateRequest("ListTagsForResource"); + parameters["ResourceArn"] = topicArn; + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + json[] tags = + check response.ListTagsForResourceResponse.ListTagsForResourceResult.Tags.ensureType(); + return check mapJsonToTags(tags); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Removes tags from the specified Amazon SNS topic. + # + # + topicArn - The ARN of the topic from which to remove tags + # + tagKeys - The list of tag keys to remove from the specified topic + # + return - `()` or `sns:Error` in case of failure + isolated remote function untagResource(string topicArn, string[] tagKeys) returns Error? { + map parameters = initiateRequest("UntagResource"); + parameters["ResourceArn"] = topicArn; + + if tagKeys.length() is 0 { + return error Error("At least one tag key must be specified."); + } + foreach [int, string] [i, tagKey] in tagKeys.enumerate() { + parameters["TagKeys.member." + (i + 1).toString()] = tagKey; + } + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Adds a statement to a topic's access control policy, granting access for the specified AWS accounts to the \ + # specified actions. + # + # + topicArn - The ARN of the topic to which to add the policy + # + actions - The actions to allow for the specified users + # + awsAccountIds - The AWS account IDs of the users who will be given access to the specified actions + # + label - A unique identifier for the new policy statement + # + return - `()` or `sns:Error` in case of failure + isolated remote function addPermission(string topicArn, Action[] actions, string[] awsAccountIds, string label) + returns Error? { + map parameters = initiateRequest("AddPermission"); + parameters["TopicArn"] = topicArn; + parameters["Label"] = label; + + if actions.length() is 0 { + return error Error("At least one action must be specified."); + } + foreach [int, Action] [i, action] in actions.enumerate() { + parameters["ActionName.member." + (i + 1).toString()] = action.toString(); + } + + if awsAccountIds.length() is 0 { + return error Error("At least one AWS account ID must be specified."); + } + foreach [int, string] [i, awsAccountId] in awsAccountIds.enumerate() { + parameters["AWSAccountId.member." + (i + 1).toString()] = awsAccountId; + } + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Removes a statement from a topic's access control policy. + # + # + topicArn - The ARN of the topic from which to remove the policy + # + label - The unique identifier for the policy statement to be removed + # + return - `()` or `sns:Error` in case of failure + isolated remote function removePermission(string topicArn, string label) returns Error? { + map parameters = initiateRequest("RemovePermission"); + parameters["TopicArn"] = topicArn; + parameters["Label"] = label; + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Adds or updates the data protection policy of the specified Amazon SNS topic. + # + # + topicArn - The ARN of the topic to which to add the policy + # + dataProtectionPolicy - The policy document to add to the specified topic + # + return - `()` or `sns:Error` in case of failure + isolated remote function putDataProtectionPolicy(string topicArn, json dataProtectionPolicy) returns Error? { + map parameters = initiateRequest("PutDataProtectionPolicy"); + parameters["ResourceArn"] = topicArn; + parameters["DataProtectionPolicy"] = dataProtectionPolicy.toJsonString(); + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Retrieves the data protection policy for the specified Amazon SNS topic. + # + # + topicArn - The ARN of the topic for which to retrieve the policy + # + return - The data protection policy for the specified topic or `sns:Error` in case of failure + isolated remote function getDataProtectionPolicy(string topicArn) returns json|Error { + map parameters = initiateRequest("GetDataProtectionPolicy"); + parameters["ResourceArn"] = topicArn; + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + return check response.GetDataProtectionPolicyResponse.GetDataProtectionPolicyResult.DataProtectionPolicy; + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + # Sets the default settings for sending SMS messages and receiving daily SMS usage reports. + # + # + attributes - The settings for sending SMS messages and receiving daily SMS usage reports + # + return - `()` or `sns:Error` in case of failure + isolated remote function setSMSAttributes(SMSAttributes attributes) returns Error? { + map parameters = initiateRequest("SetSMSAttributes"); + + record {} formattedSMSAttributes = check formatAttributes(attributes); + setAttributes(parameters, formattedSMSAttributes, true); + + http:Request request = check self.generateRequest(parameters); + _ = check sendRequest(self.amazonSNSClient, request); + }; + + # Retrieves the default settings for sending SMS messages and receiving daily SMS usage reports. + # + # + return - The default settings for sending SMS messages and receiving daily SMS usage reports or `sns:Error` in + # case of failure + isolated remote function getSMSAttributes() returns SMSAttributes|Error { + map parameters = initiateRequest("GetSMSAttributes"); + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + do { + json attributes = check response.GetSMSAttributesResponse.GetSMSAttributesResult.attributes; + return check mapJsonToSMSAttributes(attributes); + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + }; + + private isolated function generateRequest(map parameters) + returns http:Request|Error { [int, decimal] & readonly currentTime = time:utcNow(); string|error xamzDate = utcToString(currentTime, "yyyyMMdd'T'HHmmss'Z'"); string|error dateStamp = utcToString(currentTime, "yyyyMMdd"); + if (xamzDate is string && dateStamp is string) { string contentType = "application/x-www-form-urlencoded"; - string requestParameters = payload; + string|url:Error requestParameters = self.createPayload(parameters); + if requestParameters is url:Error { + return error GenerateRequestFailed(requestParameters.message(), requestParameters); + } + string canonicalQuerystring = EMPTY_STRING; string? availableSecurityToken = self.securityToken; string canonicalHeaders = EMPTY_STRING; string signedHeaders = EMPTY_STRING; + //Create a canonical request for Signature Version 4 if (availableSecurityToken is string) { - canonicalHeaders = "content-type:" + contentType + "\n" + "host:" + self.amazonHost + "\n" + canonicalHeaders = "content-type:" + contentType + "\n" + "host:" + self.amazonHost + "\n" + "x-amz-date:" + xamzDate + "\n" + "x-amz-security-token" + availableSecurityToken + "\n"; signedHeaders = "content-type;host;x-amz-date;x-amz-security-token"; } else { - canonicalHeaders = "content-type:" + contentType + "\n" + "host:" + self.amazonHost + "\n" + canonicalHeaders = "content-type:" + contentType + "\n" + "host:" + self.amazonHost + "\n" + "x-amz-date:" + xamzDate + "\n"; signedHeaders = "content-type;host;x-amz-date"; } string payloadHash = array:toBase16(crypto:hashSha256(requestParameters.toBytes())).toLowerAscii(); - string canonicalRequest = "POST" + "\n" + "/" + "\n" + canonicalQuerystring + "\n" + string canonicalRequest = "POST" + "\n" + "/" + "\n" + canonicalQuerystring + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash; string algorithm = "AWS4-HMAC-SHA256"; - string credentialScope = dateStamp + "/" + self.region + "/" + "sns" + string credentialScope = dateStamp + "/" + self.region + "/" + "sns" + "/" + "aws4_request"; + //Create a string to sign for Signature Version 4 - string stringToSign = algorithm + "\n" + xamzDate + "\n" + credentialScope + "\n" + string stringToSign = algorithm + "\n" + xamzDate + "\n" + credentialScope + "\n" + array:toBase16(crypto:hashSha256(canonicalRequest.toBytes())).toLowerAscii(); + //Calculate the signature for AWS Signature Version 4 - byte[] signingKey = check self.calculateSignature(self.secretAccessKey, dateStamp, self.region, "sns"); - string signature = array:toBase16(check crypto:hmacSha256(stringToSign - .toBytes(), signingKey)).toLowerAscii(); + string signature; + do { + byte[] signingKey = check self.calculateSignature(self.secretAccessKey, dateStamp, self.region, "sns"); + signature = array:toBase16(check crypto:hmacSha256(stringToSign.toBytes(), signingKey)).toLowerAscii(); + } on fail error e { + return error CalculateSignatureFailedError(e.message(), e); + } + //Add the signature to the HTTP request - string authorizationHeader = algorithm + " " + "Credential=" + self.accessKeyId + "/" + string authorizationHeader = algorithm + " " + "Credential=" + self.accessKeyId + "/" + credentialScope + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; map headers = {}; headers["Content-Type"] = contentType; headers["X-Amz-Date"] = xamzDate; headers["Authorization"] = authorizationHeader; - string msgBody = requestParameters; + headers["Accept"] = "application/json"; + http:Request request = new; - request.setTextPayload(msgBody); + request.setTextPayload(requestParameters); + foreach var [key, value] in headers.entries() { request.setHeader(key, value); } + return request; } else { return error GenerateRequestFailed(GENERATE_REQUEST_FAILED_MSG); @@ -369,7 +992,7 @@ public isolated client class Client { } //Calculate the signature for AWS Signature Version 4. - private isolated function calculateSignature(string secretAccessKey, string datestamp, string region, string serviceName) + private isolated function calculateSignature(string secretAccessKey, string datestamp, string region, string serviceName) returns byte[]|error { string kSecret = secretAccessKey; byte[] kDate = check crypto:hmacSha256(datestamp.toBytes(), ("AWS4" + kSecret).toBytes()); @@ -378,4 +1001,34 @@ public isolated client class Client { byte[] kSigning = check crypto:hmacSha256("aws4_request".toBytes(), kService); return kSigning; } + + private isolated function createPayload(map parameters) returns string|url:Error { + string payload = EMPTY_STRING; + int parameterNumber = 1; + foreach var [key, value] in parameters.entries() { + if (parameterNumber > 1) { + payload = payload + "&"; + } + payload = payload + key + "=" + check url:encode(value, "UTF-8"); + parameterNumber = parameterNumber + 1; + } + return payload; + } + } + +# Represents the AWS SNS client connection configuration. +# +# + auth - Do not provide authentication credentials here +# + accessKeyId - AWS access key ID +# + secretAccessKey - AWS secret access key +# + securityToken - AWS security token +# + region - AWS SNS region. Default value is "us-east-1" +public type ConnectionConfig record {| + *config:ConnectionConfig; + never auth?; + string accessKeyId; + string secretAccessKey; + string securityToken?; + string region = DEFAULT_REGION; +|}; diff --git a/ballerina/constants.bal b/ballerina/constants.bal index 7c0111a..2a3faf0 100644 --- a/ballerina/constants.bal +++ b/ballerina/constants.bal @@ -1,6 +1,6 @@ -// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // -// WSO2 Inc. licenses this file to you under the Apache License, +// 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 @@ -14,23 +14,31 @@ // specific language governing permissions and limitations // under the License. -const string AMAZON_AWS_HOST = "sns.amazonaws.com"; const string DEFAULT_REGION = "us-east-1"; const string EMPTY_STRING = ""; const string ACTION = "Action"; const string VERSION = "Version"; const string VERSION_NUMBER = "2010-03-31"; -const string OPERATION_ERROR = "Error has occurred during an operation"; -const string REQUEST_ERROR = "Error has occurred during request"; -public enum AwsProtocol { - HTTP = "http", - HTTPS = "https", - EMAIL = "email", - EMAIL_JSON = "email-json", - SMS = "sms", - SQS = "sqs", - APPLICATION = "application", - LAMBDA = "lambda", - FIREHOSE = "firehose" -} +const map SPECIAL_TOPIC_ATTRIBUTES_MAP = { + "httpMessageDeliveryLogging": "HTTP", + "lambdaMessageDeliveryLogging": "Lambda", + "sqsMessageDeliveryLogging": "SQS", + "firehoseMessageDeliveryLogging": "Firehose", + "applicationMessageDeliveryLogging": "Application" +}; + +const map MESSAGE_RECORD_MAP = { + "emailJson": "email-json", + "apns": "APNS", + "apnsSandbox": "APNS_SANDBOX", + "apnsVoip": "APNS_VOIP", + "apnsVoipSandbox": "APNS_VOIP_SANDBOX", + "macos": "MACOS", + "macosSandbox": "MACOS_SANDBOX", + "gcm": "GCM", + "adm": "ADM", + "baidu": "BAIDU", + "mpns": "MPNS", + "wns": "WNS" +}; diff --git a/ballerina/data_mappings.bal b/ballerina/data_mappings.bal index 1164756..65d342a 100644 --- a/ballerina/data_mappings.bal +++ b/ballerina/data_mappings.bal @@ -1,6 +1,6 @@ -// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // -// WSO2 Inc. licenses this file to you under the Apache License, +// 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 @@ -14,192 +14,333 @@ // specific language governing permissions and limitations // under the License. -xmlns "http://sns.amazonaws.com/doc/2010-03-31/" as namespace; - -isolated function xmlToCreatedTopic(xml response) returns CreateTopicResponse|error { - xml createdTopicResponse = response/; - xml responseMeta = response/; - if (createdTopicResponse.toString() != EMPTY_STRING) { - CreateTopicResult createTopic = { - topicArn : (createdTopicResponse//*).toString() - }; - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - CreateTopicResponse createTopicResponse = { - createTopicResult : createTopic, - responseMetadata : responseMetadata - }; - return createTopicResponse; - } else { - return error(response.toString()); +import ballerina/mime; + +isolated function setAttributes(map parameters, map attributes, boolean lowercaseA = false) { + int attributeNumber = 1; + string prefix = "Attributes.entry."; + + // Lowercase "A" is needed in the case of the "SetSMSAttributes" action + if lowercaseA { + prefix = "attributes.entry."; } -} -isolated function xmlToCreatedSubscription(xml response) returns SubscribeResponse|error { - xml createdSubscriptionResponse = response/; - xml responseMeta = response/; - if (createdSubscriptionResponse.toString() != EMPTY_STRING) { - SubscribeResult subscribtionResult = { - subscriptionArn : (createdSubscriptionResponse//*).toString() - }; - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - SubscribeResponse subscribeResponse = { - subscribeResult : subscribtionResult, - responseMetadata : responseMetadata - }; - return subscribeResponse; - } else { - return error(response.toString()); + foreach [string, anydata] [key, value] in attributes.entries() { + parameters[prefix + attributeNumber.toString() + ".key"] = key; + + if value is record {} { + parameters[prefix + attributeNumber.toString() + ".value"] = value.toJsonString(); + } else { + parameters[prefix + attributeNumber.toString() + ".value"] = value.toString(); + } + + attributeNumber = attributeNumber + 1; } } -isolated function xmlToPublishResponse(xml response) returns PublishResponse|error { - xml publishResponse = response/; - xml responseMeta = response/; - if (publishResponse.toString() != EMPTY_STRING) { - PublishResult publishResult = { - messageId : (publishResponse//*).toString() - }; - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - PublishResponse publishedResponse = { - publishResult : publishResult, - responseMetadata : responseMetadata - }; - return publishedResponse; - } else { - return error(response.toString()); +isolated function setTags(map parameters, map tags) { + int tagNumber = 1; + foreach [string, string] [key, value] in tags.entries() { + parameters["Tags.member." + tagNumber.toString() + ".Key"] = key; + parameters["Tags.member." + tagNumber.toString() + ".Value"] = value; + tagNumber = tagNumber + 1; } } -isolated function xmlToUnsubscribeResponse(xml response) returns UnsubscribeResponse|error { - xml responseMeta = response/; - if (responseMeta.toString() != EMPTY_STRING) { - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - UnsubscribeResponse unsubscriptionResponse = { - responseMetadata : responseMetadata - }; - return unsubscriptionResponse; - } else { - return error(response.toString()); +isolated function setMessageAttributes(map parameters, map attributes, + string prefix = "") returns Error? { + int i = 1; + foreach [string, MessageAttributeValue] [key, value] in attributes.entries() { + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Name"] = key; + + if value is int|float|decimal { + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.DataType"] = "Number"; + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.StringValue"] = value.toString(); + } else if value is byte[] { + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.DataType"] = "Binary"; + do { + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.BinaryValue"] = + (check mime:base64Encode(value)).toString(); + } on fail error e { + return error GenerateRequestFailed(e.message(), e); + } + } else if value is StringArrayElement[] { + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.DataType"] = "String.Array"; + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.StringValue"] = value.toString(); + } else { + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.DataType"] = "String"; + parameters[prefix + "MessageAttributes.entry." + i.toString() + ".Value.StringValue"] = value.toString(); + } + + i = i + 1; } } -isolated function xmlToDeletedTopicResponse(xml response) returns DeleteTopicResponse|error { - xml responseMeta = response/; - if (responseMeta.toString() != EMPTY_STRING) { - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - DeleteTopicResponse deletedTopice = { - responseMetadata : responseMetadata - }; - return deletedTopice; - } else { - return error(response.toString()); +isolated function setPublishBatchEntries(map parameters, PublishBatchRequestEntry[] entries) returns Error? { + int i = 1; + foreach PublishBatchRequestEntry entry in entries { + if entry.id is string { + parameters["PublishBatchRequestEntries.member." + i.toString() + ".Id"] = entry.id; + } else { + parameters["PublishBatchRequestEntries.member." + i.toString() + ".Id"] = i.toString(); + } + + if entry.message is MessageRecord { + MessageRecord messageRecord = entry.message; + if messageRecord.hasKey("subject") { + parameters["PublishBatchRequestEntries.member." + i.toString() + ".Subject"] = + messageRecord["subject"].toString(); + _ = messageRecord.remove("subject"); + } + parameters["PublishBatchRequestEntries.member." + i.toString() + ".MessageStructure"] = "json"; + parameters["PublishBatchRequestEntries.member." + i.toString() + ".Message"] = + mapMessageRecordToJson(messageRecord).toJsonString(); + } else { + parameters["PublishBatchRequestEntries.member." + i.toString() + ".Message"] = + entry.message.toString(); + } + + if entry.deduplicationId is string { + parameters["PublishBatchRequestEntries.member." + i.toString() + ".MessageDeduplicationId"] = + entry.deduplicationId; + } + + if entry.groupId is string { + parameters["PublishBatchRequestEntries.member." + i.toString() + ".MessageGroupId"] = entry.groupId; + } + + if entry.attributes is map { + check setMessageAttributes(parameters, >entry.attributes, + "PublishBatchRequestEntries.member." + i.toString() + "."); + } + + i = i + 1; } } -isolated function xmlToGetTopicAttributes(xml response) returns GetTopicAttributesResponse|error { - xml getTopicAttributesResponses = response/; - xml responseMeta = response/; - if (getTopicAttributesResponses.toString() != EMPTY_STRING) { - GetTopicAttributesResult getTopicAttributesResult = { - attributes: (getTopicAttributesResponses//*).toString() - }; - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - GetTopicAttributesResponse getTopicAttributesResponse = { - getTopicAttributesResult : getTopicAttributesResult, - responseMetadata : responseMetadata - }; - return getTopicAttributesResponse; - } else { - return error(response.toString()); - } +isolated function mapJsonToGettableTopicAttributes(json jsonResponse) returns GettableTopicAttributes|error { + string[] intFields = ["SubscriptionsPending", "SubscriptionsConfirmed", "SubscriptionsDeleted"]; + string[] booleanFields = ["FifoTopic", "ContentBasedDeduplication"]; + string[] jsonFields = ["EffectiveDeliveryPolicy", "Policy", "DeliveryPolicy"]; + string[] skipFields = [ + "HTTPSuccessFeedbackRoleArn", + "HTTPFailureFeedbackRoleArn", + "HTTPSuccessFeedbackSampleRate", + "FirehoseSuccessFeedbackRoleArn", + "FirehoseFailureFeedbackRoleArn", + "FirehoseSuccessFeedbackSampleRate", + "LambdaSuccessFeedbackRoleArn", + "LambdaFailureFeedbackRoleArn", + "LambdaSuccessFeedbackSampleRate", + "SQSSuccessFeedbackRoleArn", + "SQSFailureFeedbackRoleArn", + "SQSSuccessFeedbackSampleRate", + "ApplicationSuccessFeedbackRoleArn", + "ApplicationFailureFeedbackRoleArn", + "ApplicationSuccessFeedbackSampleRate" + ]; + record {} mapped = check mapJsonToRecord(jsonResponse, intFields = intFields, booleanFields = booleanFields, + jsonFields = jsonFields, skipFields = skipFields); + GettableTopicAttributes topicAttributes = check mapped.cloneWithType(); + check addMessageDeliveryLoggingFieldsToTopicAttributes(topicAttributes, jsonResponse); + + return topicAttributes; } -isolated function xmlToGetSmsAttributes(xml response) returns GetSMSAttributesResponse|error { - xml getSMSAttributesResponses = response/; - xml responseMeta = response/; - if (getSMSAttributesResponses.toString() != EMPTY_STRING) { - GetSMSAttributesResult getSMSAttributesResult = { - attributes: (getSMSAttributesResponses//*).toString() - }; - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - GetSMSAttributesResponse getSMSAttributesResponse = { - getSMSAttributesResult : getSMSAttributesResult, - responseMetadata : responseMetadata - }; - return getSMSAttributesResponse; - } else { - return error(response.toString()); +isolated function formatAttributes(record {} r, map formatMap = {}) returns record {}|Error { + record {} flattenedRecord = {}; + string[] elementKeys = formatMap.keys(); + + foreach string key in r.keys() { + if (elementKeys.indexOf(key) is int) { + record {}|error nestedRecord = r[key].ensureType(); + if (nestedRecord is error) { + return error GenerateRequestFailed(nestedRecord.message(), nestedRecord); + } + + foreach string nestedKey in nestedRecord.keys() { + flattenedRecord[formatMap.get(key) + uppercaseFirstLetter(nestedKey)] = nestedRecord[nestedKey]; + } + } else { + flattenedRecord[uppercaseFirstLetter(key)] = r[key]; + } } + + return flattenedRecord; } -isolated function xmlToGetSubscriprionAttributes(xml response) returns GetSubscriptionAttributesResponse|error { - xml getSubscriptionAttributesResponses = response/; - xml responseMeta = response/; - if (getSubscriptionAttributesResponses.toString() != EMPTY_STRING) { - GetSubscriptionAttributesResult getSubscriptionAttributesResult = { - attributes: (getSubscriptionAttributesResponses//*).toString() - }; - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - GetSubscriptionAttributesResponse getSubscriptionAttributesResponse = { - getSubscriptionAttributesResult : getSubscriptionAttributesResult, - responseMetadata : responseMetadata - }; - return getSubscriptionAttributesResponse; - } else { - return error(response.toString()); +isolated function addMessageDeliveryLoggingFieldsToTopicAttributes(GettableTopicAttributes topicAttributes, + json jsonResponse) returns error? { + + record {} response = check jsonResponse.cloneWithType(); + + if (response.hasKey("HTTPSuccessFeedbackRoleArn") || response.hasKey("HTTPFailureFeedbackRoleArn") || + response.hasKey("HTTPSuccessFeedbackSampleRate")) { + MessageDeliveryLoggingConfig httpMessageDeliveryLogging = {}; + if response.hasKey("HTTPSuccessFeedbackRoleArn") { + httpMessageDeliveryLogging.successFeedbackRoleArn = response["HTTPSuccessFeedbackRoleArn"].toString(); + } + if response.hasKey("HTTPFailureFeedbackRoleArn") { + httpMessageDeliveryLogging.failureFeedbackRoleArn = response["HTTPFailureFeedbackRoleArn"].toString(); + } + if response.hasKey("HTTPSuccessFeedbackSampleRate") { + httpMessageDeliveryLogging.successFeedbackSampleRate = + check stringToInt(response["HTTPSuccessFeedbackSampleRate"].toString()); + } + topicAttributes.httpMessageDeliveryLogging = httpMessageDeliveryLogging; + } + + if (response.hasKey("FirehoseSuccessFeedbackRoleArn") || response.hasKey("FirehoseFailureFeedbackRoleArn") || + response.hasKey("FirehoseSuccessFeedbackSampleRate")) { + MessageDeliveryLoggingConfig firehoseMessageDeliveryLogging = {}; + if response.hasKey("FirehoseSuccessFeedbackRoleArn") { + firehoseMessageDeliveryLogging.successFeedbackRoleArn = + response["FirehoseSuccessFeedbackRoleArn"].toString(); + } + if response.hasKey("FirehoseFailureFeedbackRoleArn") { + firehoseMessageDeliveryLogging.failureFeedbackRoleArn = + response["FirehoseFailureFeedbackRoleArn"].toString(); + } + if response.hasKey("FirehoseSuccessFeedbackSampleRate") { + firehoseMessageDeliveryLogging.successFeedbackSampleRate = + check stringToInt(response["FirehoseSuccessFeedbackSampleRate"].toString()); + } + topicAttributes.firehoseMessageDeliveryLogging = firehoseMessageDeliveryLogging; + } + + if (response.hasKey("LambdaSuccessFeedbackRoleArn") || response.hasKey("LambdaFailureFeedbackRoleArn") || + response.hasKey("LambdaSuccessFeedbackSampleRate")) { + MessageDeliveryLoggingConfig lambdaMessageDeliveryLogging = {}; + if response.hasKey("LambdaSuccessFeedbackRoleArn") { + lambdaMessageDeliveryLogging.successFeedbackRoleArn = response["LambdaSuccessFeedbackRoleArn"].toString(); + } + if response.hasKey("LambdaFailureFeedbackRoleArn") { + lambdaMessageDeliveryLogging.failureFeedbackRoleArn = response["LambdaFailureFeedbackRoleArn"].toString(); + } + if response.hasKey("LambdaSuccessFeedbackSampleRate") { + lambdaMessageDeliveryLogging.successFeedbackSampleRate = + check stringToInt(response["LambdaSuccessFeedbackSampleRate"].toString()); + } + topicAttributes.lambdaMessageDeliveryLogging = lambdaMessageDeliveryLogging; + } + + if (response.hasKey("SQSSuccessFeedbackRoleArn") || response.hasKey("SQSFailureFeedbackRoleArn") || + response.hasKey("SQSSuccessFeedbackSampleRate")) { + MessageDeliveryLoggingConfig sqsMessageDeliveryLogging = {}; + if response.hasKey("SQSSuccessFeedbackRoleArn") { + sqsMessageDeliveryLogging.successFeedbackRoleArn = response["SQSSuccessFeedbackRoleArn"].toString(); + } + if response.hasKey("SQSFailureFeedbackRoleArn") { + sqsMessageDeliveryLogging.failureFeedbackRoleArn = response["SQSFailureFeedbackRoleArn"].toString(); + } + if response.hasKey("SQSSuccessFeedbackSampleRate") { + sqsMessageDeliveryLogging.successFeedbackSampleRate = + check stringToInt(response["SQSSuccessFeedbackSampleRate"].toString()); + } + topicAttributes.sqsMessageDeliveryLogging = sqsMessageDeliveryLogging; + } + + if (response.hasKey("ApplicationSuccessFeedbackRoleArn") || response.hasKey("ApplicationFailureFeedbackRoleArn") || + response.hasKey("ApplicationSuccessFeedbackSampleRate")) { + MessageDeliveryLoggingConfig applicationMessageDeliveryLogging = {}; + if response.hasKey("ApplicationSuccessFeedbackRoleArn") { + applicationMessageDeliveryLogging.successFeedbackRoleArn = + response["ApplicationSuccessFeedbackRoleArn"].toString(); + } + if response.hasKey("ApplicationFailureFeedbackRoleArn") { + applicationMessageDeliveryLogging.failureFeedbackRoleArn = + response["ApplicationFailureFeedbackRoleArn"].toString(); + } + if response.hasKey("ApplicationSuccessFeedbackSampleRate") { + applicationMessageDeliveryLogging.successFeedbackSampleRate = + check stringToInt(response["ApplicationSuccessFeedbackSampleRate"].toString()); + } + topicAttributes.applicationMessageDeliveryLogging = applicationMessageDeliveryLogging; } } -isolated function xmlToConfirmedSubscriptionResponse(xml response) returns ConfirmedSubscriptionResponse|error { - xml confirmSubscriptionResponse = response/; - xml responseMeta = response/; - if (confirmSubscriptionResponse.toString() != EMPTY_STRING) { - ConfirmedSubscriptionResult confirmSubscriptionResult = { - subscriptionArn : (confirmSubscriptionResponse//*).toString() - }; - ResponseMetadata responseMetadata = { - requestId: (responseMeta//*).toString() - }; - ConfirmedSubscriptionResponse confirmedSubscriptionResponse = { - confirmedSubscriptionResult : confirmSubscriptionResult, - responseMetadata : responseMetadata - }; - return confirmedSubscriptionResponse; - } else { - return error(response.toString()); +isolated function mapMessageRecordToJson(MessageRecord message) returns json { + record {} mappedMessage = {}; + foreach string key in message.keys() { + if MESSAGE_RECORD_MAP.hasKey(key) { + mappedMessage[MESSAGE_RECORD_MAP.get(key)] = message[key].toString(); + } else { + mappedMessage[key] = message[key].toString(); + } } + return mappedMessage.toJson(); +} + +isolated function mapJsonToSubscriptionAttributes(json jsonResponse) returns GettableSubscriptionAttributes|error { + string[] booleanFields = ["ConfirmationWasAuthenticated", "PendingConfirmation", "RawMessageDelivery"]; + string[] jsonFields = ["DeliveryPolicy", "EffectiveDeliveryPolicy", "FilterPolicy", "RedrivePolicy"]; + + record {} mapped = check mapJsonToRecord(jsonResponse, booleanFields = booleanFields, jsonFields = jsonFields); + return mapped.cloneWithType(); +} + +isolated function mapJsonToPlatformApplicationAttributes(json jsonResponse) + returns RetrievablePlatformApplicationAttributes|error { + string[] booleanFields = ["Enabled"]; + string[] intFields = ["SuccessFeedbackSampleRate"]; + + record {} mapped = check mapJsonToRecord(jsonResponse, booleanFields = booleanFields, intFields = intFields); + return mapped.cloneWithType(); +} + +isolated function mapJsonToEndpointAttributes(json jsonResponse) returns EndpointAttributes|error { + string[] booleanFields = ["Enabled"]; + + record {} mapped = check mapJsonToRecord(jsonResponse, booleanFields = booleanFields); + return mapped.cloneWithType(); +} + +isolated function mapJsonToSMSAttributes(json jsonResponse) returns SMSAttributes|error { + string[] intFields = ["MonthlySpendLimit", "DeliveryStatusSuccessSamplingRate"]; + + record {} mapped = check mapJsonToRecord(jsonResponse, intFields = intFields); + return mapped.cloneWithType(); } -isolated function xmlToConfirmedSubscription(xml response) returns string|error { - string|error subscriptionName = (response///*).toString(); - if (subscriptionName is string) { - return subscriptionName != EMPTY_STRING ? subscriptionName.toString() : EMPTY_STRING; - } else { - return subscriptionName; +isolated function mapJsonToOriginationNumber(json jsonResponse) returns OriginationPhoneNumber|error { + string[] timestampFields = ["CreatedAt"]; + + record {} mapped = check mapJsonToRecord(jsonResponse, timestampFields = timestampFields); + return mapped.cloneWithType(); +} + +isolated function mapJsonToTags(json[] jsonResponse) returns Tags|error { + Tags tags = {}; + foreach json tag in jsonResponse { + tags[check tag.Key] = check tag.Value; } + return tags; } -isolated function xmlToHttpResponse(xml response) returns error? { - string|error httpResponse = (response///*).toString(); - if (httpResponse is string) { - return null; - } else { - return error(OPERATION_ERROR); + +isolated function mapJsonToRecord(json jsonResponse, string[] intFields = [], string[] booleanFields = [], + string[] jsonFields = [], string[] timestampFields = [], string[] skipFields = []) returns record {}|error { + record {} response = check jsonResponse.cloneWithType(); + record {} r = {}; + + foreach [string, anydata] [key, value] in response.entries() { + if (skipFields.indexOf(key) is int) { + continue; + } + + anydata val = value; + if intFields.indexOf(key) is int { + val = check stringToInt(value.toString()); + } else if booleanFields.indexOf(key) is int { + val = check stringToBoolean(value.toString()); + } else if jsonFields.indexOf(key) is int { + val = check value.toString().fromJsonString(); + } else if timestampFields.indexOf(key) is int { + val = check stringToTimestamp(value.toString()); + } + + r[lowercaseFirstLetter(key)] = val; } + + return r; } diff --git a/ballerina/error.bal b/ballerina/error.bal index 6371d85..6800c70 100644 --- a/ballerina/error.bal +++ b/ballerina/error.bal @@ -14,27 +14,24 @@ // specific language governing permissions and limitations // under the License. -public type GenerateRequestFailed distinct error; +# Reperesents the generic error type for the `aws.sns` module. +public type Error distinct error; -public type OperationError distinct error; +# Represents an error that occurs when generating an API request. +public type GenerateRequestFailed distinct Error; -public type DataMappingError distinct error; +# Represents an error that occurs when the API action cannot be completed due to user error. +public type OperationError distinct Error; +# Represents an error that occurs when the API response is in an unexpected format. +public type ResponseHandleFailedError distinct Error; -public type FileReadFailed distinct error; -public type ResponseHandleFailed distinct error; +# Represents an error that occurs when calculating the signature. +public type CalculateSignatureFailedError distinct Error; +# Represents an error that occurs when the API action cannot be completed due to an unknown server error. +public type InternalError distinct Error; -const string CONVERT_XML_TO_INBOUND_MESSAGES_FAILED_MSG = "Error while converting XML to Inbound Messages."; -const string CONVERT_XML_TO_INBOUND_MESSAGE_FAILED_MSG = "Error while converting XML to an Inbound Message."; -const string CONVERT_XML_TO_INBOUND_MESSAGE_MESSAGE_ATTRIBUTES_FAILED_MSG = "Error while converting XML to an Inbound Message's Message Attributes."; -const string CONVERT_XML_TO_INBOUND_MESSAGE_MESSAGE_ATTRIBUTE_FAILED_MSG = "Error while converting XML to an Inbound Message's Message Attribute."; -const string FILE_READ_FAILED_MSG = "Error while reading a file."; -const string CLOSE_CHARACTER_STREAM_FAILED_MSG = "Error occurred while closing character stream."; const string GENERATE_REQUEST_FAILED_MSG = "Error occurred while generating POST request."; const string NO_CONTENT_SET_WITH_RESPONSE_MSG = "No Content was sent with the response."; -const string RESPONSE_PAYLOAD_IS_NOT_XML_MSG = "Response payload is not XML."; const string ERROR_OCCURRED_WHILE_INVOKING_REST_API_MSG = "Error occurred while invoking the REST API."; -const string OUTBOUND_MESSAGE_RESPONSE_EMPTY_MSG = "Outbound Message response is empty."; -const string OPERATION_ERROR_MSG = "Error has occurred during an operation."; -const string UNREACHABLE_STATE = "Response type cannot be http payload"; diff --git a/ballerina/response_types.bal b/ballerina/response_types.bal new file mode 100644 index 0000000..b9cf242 --- /dev/null +++ b/ballerina/response_types.bal @@ -0,0 +1,44 @@ + +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +public type ListTopicsResponse record {| + string[] topicArns; + string? nextToken; +|}; + +public type PublishMessageResponse record {| + string messageId; + string sequenceNumber?; +|}; + +public type PublishBatchResponse record {| + PublishBatchResultEntry[] successful; + BatchResultErrorEntry[] failed; +|}; + +public type PublishBatchResultEntry record {| + string id; + string messageId; + string sequenceNumber?; +|}; + +public type BatchResultErrorEntry record {| + string code; + string id; + boolean senderFault; + string message?; +|}; diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal new file mode 100644 index 0000000..57df571 --- /dev/null +++ b/ballerina/stream_types.bal @@ -0,0 +1,567 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/http; + +# Used to fetch and return a stream of SNS topics. The logic of fetching the topics is abstracted away from the user. +class TopicStream { + + private final http:Client amazonSNSClient; + private final (isolated function(map) returns http:Request|Error) & readonly generateRequest; + + private string[] topics = []; + private string? nextToken = (); + private boolean initialized = false; + + public isolated function init(http:Client amazonSNSClient, + isolated function (map) returns http:Request|Error generateRequest) { + self.amazonSNSClient = amazonSNSClient; + self.generateRequest = generateRequest; + } + + private isolated function fetchTopics() returns Error? { + map parameters = initiateRequest("ListTopics"); + if self.nextToken is string { + parameters["NextToken"] = self.nextToken; + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + json|error nextToken = response.ListTopicsResponse.ListTopicsResult.NextToken; + if nextToken is json && nextToken != () { + self.nextToken = nextToken.toString(); + } else { + self.nextToken = (); + } + + do { + json[] topics = (check response.ListTopicsResponse.ListTopicsResult.Topics); + foreach json topic in topics { + self.topics.push((check topic.TopicArn).toString()); + } + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + } + + public isolated function next() returns record {|string value;|}|Error? { + if self.topics.length() == 0 { + if self.initialized && self.nextToken is () { + return (); + } + + Error? e = self.fetchTopics(); + self.initialized = true; + if e is error { + return e; + } + } + + if self.topics.length() == 0 { + return (); + } + + return {value: self.topics.remove(0)}; + } + + public isolated function close() returns Error? { + return (); + } +} + +# Used to fetch and return a stream of SNS subscriptions. The logic of fetching the subscriptions is abstracted away +# from the user. +class SubscriptionStream { + + private final http:Client amazonSNSClient; + private final (isolated function (map) returns http:Request|Error) & readonly generateRequest; + private final string? topicArn; + + private Subscription[] subscriptions = []; + private string? nextToken = (); + private boolean initialized = false; + + public isolated function init(http:Client amazonSNSClient, + isolated function (map) returns http:Request|Error generateRequest, + string? topicArn) { + self.amazonSNSClient = amazonSNSClient; + self.generateRequest = generateRequest; + self.topicArn = topicArn; + } + + private isolated function fetchSubscriptions() returns Error? { + map parameters; + if self.topicArn is string { + parameters = initiateRequest("ListSubscriptionsByTopic"); + parameters["TopicArn"] = self.topicArn; + } else { + parameters = initiateRequest("ListSubscriptions"); + } + + if self.nextToken is string { + parameters["NextToken"] = self.nextToken; + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + json|error nextToken; + if self.topicArn is string { + nextToken = response.ListSubscriptionsByTopicResponse.ListSubscriptionsByTopicResult.NextToken; + } else { + nextToken = response.ListSubscriptionsResponse.ListSubscriptionsResult.NextToken; + } + + if nextToken is json && nextToken != () { + self.nextToken = nextToken.toString(); + } else { + self.nextToken = (); + } + + do { + json[] subscriptions; + if self.topicArn is string { + subscriptions = + (check response.ListSubscriptionsByTopicResponse.ListSubscriptionsByTopicResult.Subscriptions); + } else { + subscriptions = + (check response.ListSubscriptionsResponse.ListSubscriptionsResult.Subscriptions); + } + + foreach json subscription in subscriptions { + self.subscriptions.push({ + subscriptionArn: (check subscription.SubscriptionArn), + topicArn: (check subscription.TopicArn), + owner: (check subscription.Owner), + protocol: (check subscription.Protocol), + endpoint: (check subscription.Endpoint) + }); + } + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + } + + public isolated function next() returns record {|Subscription value;|}|Error? { + if self.subscriptions.length() == 0 { + if self.initialized && self.nextToken is () { + return (); + } + + Error? e = self.fetchSubscriptions(); + self.initialized = true; + if e is error { + return e; + } + } + + if self.subscriptions.length() == 0 { + return (); + } + + return {value: self.subscriptions.remove(0)}; + } + + public isolated function close() returns Error? { + return (); + } +} + +# Used to fetch and return a stream of SNS platform applications. The logic of fetching the platform applications is +# abstracted away from the user. + class PlatformApplicationStream { + + private final http:Client amazonSNSClient; + private final (isolated function (map) returns http:Request|Error) & readonly generateRequest; + + private PlatformApplication[] platformApplications = []; + private string? nextToken = (); + private boolean initialized = false; + + public isolated function init(http:Client amazonSNSClient, + isolated function (map) returns http:Request|Error generateRequest) { + self.amazonSNSClient = amazonSNSClient; + self.generateRequest = generateRequest; + } + + private isolated function fetchPlatformApplications() returns Error? { + map parameters = initiateRequest("ListPlatformApplications"); + + if self.nextToken is string { + parameters["NextToken"] = self.nextToken; + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + json|error nextToken = response.ListPlatformApplicationsResponse.ListPlatformApplicationsResult.NextToken; + + if nextToken is json && nextToken != () { + self.nextToken = nextToken.toString(); + } else { + self.nextToken = (); + } + + do { + json[] platformApplications = (check response.ListPlatformApplicationsResponse + .ListPlatformApplicationsResult.PlatformApplications); + + foreach json platformApplication in platformApplications { + + RetrievablePlatformApplicationAttributes attributes = + check mapJsonToPlatformApplicationAttributes(check platformApplication.Attributes); + PlatformApplication application = { + platformApplicationArn: (check platformApplication.PlatformApplicationArn), + ...attributes + }; + self.platformApplications.push(application); + + } + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + } + + public isolated function next() returns record {|PlatformApplication value;|}|Error? { + if self.platformApplications.length() == 0 { + if self.initialized && self.nextToken is () { + return (); + } + + Error? e = self.fetchPlatformApplications(); + self.initialized = true; + if e is error { + return e; + } + } + + if self.platformApplications.length() == 0 { + return (); + } + + return {value: self.platformApplications.remove(0)}; + } + + public isolated function close() returns Error? { + return (); + } +} + +# Used to fetch and return a stream of SNS endpoints. The logic of fetching the endpoints is abstracted away from the +# user. +class EndpointStream { + + private final http:Client amazonSNSClient; + private final (isolated function (map) returns http:Request|Error) & readonly generateRequest; + private final string platformApplicationArn; + + private Endpoint[] endpoints = []; + private string? nextToken = (); + private boolean initialized = false; + + public isolated function init(http:Client amazonSNSClient, + isolated function (map) returns http:Request|Error generateRequest, + string platformApplicationArn) { + self.amazonSNSClient = amazonSNSClient; + self.generateRequest = generateRequest; + self.platformApplicationArn = platformApplicationArn; + } + + private isolated function fetchPlatformApplicationEndpoints() returns Error? { + map parameters = initiateRequest("ListEndpointsByPlatformApplication"); + parameters["PlatformApplicationArn"] = self.platformApplicationArn; + + if self.nextToken is string { + parameters["NextToken"] = self.nextToken; + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + json|error nextToken = response.ListEndpointsByPlatformApplicationResponse + .ListEndpointsByPlatformApplicationResult.NextToken; + + if nextToken is json && nextToken != () { + self.nextToken = nextToken.toString(); + } else { + self.nextToken = (); + } + + do { + json[] platformApplicationEndpoints = (check response.ListEndpointsByPlatformApplicationResponse + .ListEndpointsByPlatformApplicationResult.Endpoints); + + foreach json platformApplicationEndpoint in platformApplicationEndpoints { + + EndpointAttributes attributes = check mapJsonToEndpointAttributes( + check platformApplicationEndpoint.Attributes); + Endpoint endpoint = { + endpointArn: check platformApplicationEndpoint.EndpointArn, + ...attributes + }; + self.endpoints.push(endpoint); + + } + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + } + + public isolated function next() returns record {|Endpoint value;|}|Error? { + if self.endpoints.length() == 0 { + if self.initialized && self.nextToken is () { + return (); + } + + Error? e = self.fetchPlatformApplicationEndpoints(); + self.initialized = true; + if e is error { + return e; + } + } + + if self.endpoints.length() == 0 { + return (); + } + + return {value: self.endpoints.remove(0)}; + } + + public isolated function close() returns Error? { + return (); + } +} + +# Used to fetch and return a stream of SNS sandbox phone numbers. The logic of fetching the phone numbers is abstracted +# away from the user. +class SMSSandboxPhoneNumberStream { + + private final http:Client amazonSNSClient; + private final (isolated function (map) returns http:Request|Error) & readonly generateRequest; + + private SMSSandboxPhoneNumber[] phoneNumbers = []; + private string? nextToken = (); + private boolean initialized = false; + + public isolated function init(http:Client amazonSNSClient, + isolated function (map) returns http:Request|Error generateRequest) { + self.amazonSNSClient = amazonSNSClient; + self.generateRequest = generateRequest; + } + + private isolated function fetchSMSSandboxPhoneNumbers() returns Error? { + map parameters = initiateRequest("ListSMSSandboxPhoneNumbers"); + + if self.nextToken is string { + parameters["NextToken"] = self.nextToken; + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + json|error nextToken = response.ListSMSSandboxPhoneNumbersResponse.ListSMSSandboxPhoneNumbersResult.NextToken; + + if nextToken is json && nextToken != () { + self.nextToken = nextToken.toString(); + } else { + self.nextToken = (); + } + + do { + json[] phoneNumbers = (check response.ListSMSSandboxPhoneNumbersResponse + .ListSMSSandboxPhoneNumbersResult.PhoneNumbers); + + foreach json phoneNumber in phoneNumbers { + SMSSandboxPhoneNumber smsSandboxPhoneNumber = { + phoneNumber: check phoneNumber.PhoneNumber, + status: (check phoneNumber.Status) + }; + self.phoneNumbers.push(smsSandboxPhoneNumber); + + } + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + } + + public isolated function next() returns record {|SMSSandboxPhoneNumber value;|}|Error? { + if self.phoneNumbers.length() == 0 { + if self.initialized && self.nextToken is () { + return (); + } + + Error? e = self.fetchSMSSandboxPhoneNumbers(); + self.initialized = true; + if e is error { + return e; + } + } + + if self.phoneNumbers.length() == 0 { + return (); + } + + return {value: self.phoneNumbers.remove(0)}; + } + + public isolated function close() returns Error? { + return (); + } +} + +# Used to fetch and return a stream of SNS origination phone numbers. The logic of fetching the phone numbers is +# abstracted away from the user. +class OriginationPhoneNumberStream { + + private final http:Client amazonSNSClient; + private final (isolated function (map) returns http:Request|Error) & readonly generateRequest; + + private OriginationPhoneNumber[] phoneNumbers = []; + private string? nextToken = (); + private boolean initialized = false; + + public isolated function init(http:Client amazonSNSClient, + isolated function (map) returns http:Request|Error generateRequest) { + self.amazonSNSClient = amazonSNSClient; + self.generateRequest = generateRequest; + } + + private isolated function fetchOriginationPhoneNumbers() returns Error? { + map parameters = initiateRequest("ListOriginationNumbers"); + + if self.nextToken is string { + parameters["NextToken"] = self.nextToken; + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + json|error nextToken = response.ListOriginationNumbersResponse.ListOriginationNumbersResult.NextToken; + + if nextToken is json && nextToken != () { + self.nextToken = nextToken.toString(); + } else { + self.nextToken = (); + } + + do { + json[] phoneNumbers = (check response.ListOriginationNumbersResponse + .ListOriginationNumbersResult.PhoneNumbers); + + foreach json phoneNumber in phoneNumbers { + OriginationPhoneNumber originationPhoneNumber = check mapJsonToOriginationNumber(phoneNumber); + self.phoneNumbers.push(originationPhoneNumber); + + } + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + } + + public isolated function next() returns record {|OriginationPhoneNumber value;|}|Error? { + if self.phoneNumbers.length() == 0 { + if self.initialized && self.nextToken is () { + return (); + } + + Error? e = self.fetchOriginationPhoneNumbers(); + self.initialized = true; + if e is error { + return e; + } + } + + if self.phoneNumbers.length() == 0 { + return (); + } + + return {value: self.phoneNumbers.remove(0)}; + } + + public isolated function close() returns Error? { + return (); + } +} + +# Used to fetch and return a stream of SNS opted out phone numbers. The logic of fetching the phone numbers is +# abstracted away from the user. +class OptedOutPhoneNumberStream { + + private final http:Client amazonSNSClient; + private final (isolated function (map) returns http:Request|Error) & readonly generateRequest; + + private string[] phoneNumbers = []; + private string? nextToken = (); + private boolean initialized = false; + + public isolated function init(http:Client amazonSNSClient, + isolated function (map) returns http:Request|Error generateRequest) { + self.amazonSNSClient = amazonSNSClient; + self.generateRequest = generateRequest; + } + + private isolated function fetchOptedOutPhoneNumbers() returns Error? { + map parameters = initiateRequest("ListPhoneNumbersOptedOut"); + if self.nextToken is string { + parameters["nextToken"] = self.nextToken; + } + + http:Request request = check self.generateRequest(parameters); + json response = check sendRequest(self.amazonSNSClient, request); + + json|error nextToken = response.ListPhoneNumbersOptedOutResponse.ListPhoneNumbersOptedOutResult.nextToken; + if nextToken is json && nextToken != () { + self.nextToken = nextToken.toString(); + } else { + self.nextToken = (); + } + + do { + json[] phoneNumbers = (check response.ListPhoneNumbersOptedOutResponse + .ListPhoneNumbersOptedOutResult.phoneNumbers); + foreach json phoneNumber in phoneNumbers { + self.phoneNumbers.push(phoneNumber.toString()); + } + } on fail error e { + return error ResponseHandleFailedError(e.message(), e); + } + } + + public isolated function next() returns record {|string value;|}|Error? { + if self.phoneNumbers.length() == 0 { + if self.initialized && self.nextToken is () { + return (); + } + + Error? e = self.fetchOptedOutPhoneNumbers(); + self.initialized = true; + if e is error { + return e; + } + } + + if self.phoneNumbers.length() == 0 { + return (); + } + + return {value: self.phoneNumbers.remove(0)}; + } + + public isolated function close() returns Error? { + return (); + } +} diff --git a/ballerina/tests/data-protection-policy-tests.bal b/ballerina/tests/data-protection-policy-tests.bal new file mode 100644 index 0000000..754721e --- /dev/null +++ b/ballerina/tests/data-protection-policy-tests.bal @@ -0,0 +1,65 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +json updatedDataProtectionPolicy = {"Name": "UpdatedDPPName", "Description": "Protect basic types of sensitive data", "Version": "2021-06-01", "Statement": [{"Sid": "basicPII-inbound-protection", "DataDirection": "Inbound", "Principal": ["*"], "DataIdentifier": ["arn:aws:dataprotection::aws:data-identifier/Name", "arn:aws:dataprotection::aws:data-identifier/PhoneNumber-US"], "Operation": {"Deny": {}}}]}; + +@test:Config { + groups: ["data-protection-policy"] +} +function putDataProtectionPolicyTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testDPPTopic1"); + check amazonSNSClient->putDataProtectionPolicy(topic, validDataProtectionPolicy); +} + +@test:Config { + groups: ["data-protection-policy"] +} +function putDataProtectionPolicyInvalidTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testDPPTopic2"); + Error? e = amazonSNSClient->putDataProtectionPolicy(topic, invalidDataProtectionPolicy); + test:assertTrue(e is OperationError, "OperationError expected"); + test:assertEquals((e).message(), "Invalid parameter: DataProtectionPolicy Reason: Statement DataDirection must be either Inbound or Outbound"); +} + +@test:Config { + groups: ["data-protection-policy"] +} +function updateDataProtectionPolicyTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testDPPTopic3", + dataProtectionPolicy = validDataProtectionPolicy); + check amazonSNSClient->putDataProtectionPolicy(topic, updatedDataProtectionPolicy); +} + +@test:Config { + groups: ["data-protection-policy"] +} +function getDataProtectionPolicyTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testDPPTopic4", + dataProtectionPolicy = validDataProtectionPolicy); + json dpp = check amazonSNSClient->getDataProtectionPolicy(topic); + test:assertEquals(dpp, validDataProtectionPolicy.toString()); +} + +@test:Config { + groups: ["data-protection-policy"] +} +function getDataProtectionPolicyDoesNotExistTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testDPPTopic5"); + json dpp = check amazonSNSClient->getDataProtectionPolicy(topic); + test:assertEquals(dpp, ""); +} diff --git a/ballerina/tests/endpoint-tests.bal b/ballerina/tests/endpoint-tests.bal new file mode 100644 index 0000000..a712c04 --- /dev/null +++ b/ballerina/tests/endpoint-tests.bal @@ -0,0 +1,187 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +string applicationArn = ""; + +@test:BeforeGroups {value: ["endpoint"]} +function beforeEndpointTests() returns error? { + applicationArn = check amazonSNSClient->createPlatformApplication(testRunId + "FirebasePlatformApplication", + FIREBASE_CLOUD_MESSAGING, auth = {platformCredential: firebaseServerKey}); +} + +@test:Config { + groups: ["endpoint"] +} +function createEndpointTest() returns error? { + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken"); + test:assertTrue(isArn(arn)); +} + +@test:Config { + groups: ["endpoint"] +} +function createEndpointWithEmptyTokenTest() returns error? { + string|Error arn = amazonSNSClient->createEndpoint(applicationArn, ""); + test:assertTrue(arn is OperationError); + test:assertEquals((arn).message(), "Invalid parameter: Token Reason: cannot be empty"); +} + +@test:Config { + groups: ["endpoint"] +} +function createEndpointWithInvalidArnTest() returns error? { + string|Error arn = amazonSNSClient->createEndpoint(invalidApplicationArn, testRunId + "testDeviceToken2"); + test:assertTrue(arn is OperationError); + test:assertEquals((arn).message(), "Invalid parameter: PlatformApplicationArn Reason: Wrong number of slashes in relative portion of the ARN."); +} + +@test:Config { + groups: ["endpoint"] +} +function createEndpointWithCustomUserDataTest() returns error? { + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken3", + customUserData = "testCustomUserData"); + test:assertTrue(isArn(arn)); +} + +@test:Config { + groups: ["endpoint"] +} +function createEndpointWithAttributes() returns error? { + EndpointAttributes attributes = {enabled: true, token: testRunId + "testToken4", customUserData: "testCustomUserData2"}; + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken4", attributes); + test:assertTrue(isArn(arn)); +} + +@test:Config { + groups: ["endpoint"] +} +function createEndpointWithInvalidAttributes() returns error? { + EndpointAttributes attributes = {enabled: true, token: "", customUserData: "testCustomUserData2"}; + string|Error arn = amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken5", attributes); + test:assertTrue(arn is OperationError); + test:assertEquals((arn).message(), "Invalid parameter: Attributes Reason: Invalid value for attribute: Token: cannot be empty"); +} + +@test:Config { + groups: ["endpoint"] +} +function listPlatformApplicationEndpointsTest() returns error? { + EndpointAttributes attributes = {enabled: false}; + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceTokenNew", attributes, + "CustomDataNew"); + + foreach int i in 0...100 { + _ = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken0" + i.toString()); + } + + stream endpointStream = + amazonSNSClient->listEndpoints(applicationArn); + Endpoint[] endpoints = check from Endpoint endpoint in endpointStream + select endpoint; + + test:assertTrue(endpoints.length() > 100, "Over 100 endpoints should be listed"); + + Endpoint[] findEndpoint = from Endpoint endpoint in endpoints + where arn == endpoint.endpointArn + select endpoint; + test:assertTrue(findEndpoint.length() == 1, "Newly created endpoint not found"); + test:assertEquals(findEndpoint[0].token, testRunId + "testDeviceTokenNew"); + test:assertEquals(findEndpoint[0].customUserData, "CustomDataNew"); + test:assertEquals(findEndpoint[0].enabled, false); +} + +@test:Config { + groups: ["endpoint"] +} +function getEndpointAttributesTest() returns error? { + EndpointAttributes attributes = {enabled: false}; + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken7", attributes, + "CustomData7"); + + EndpointAttributes retrievedAttributes = check amazonSNSClient->getEndpointAttributes(arn); + test:assertEquals(retrievedAttributes.token, testRunId + "testDeviceToken7"); + test:assertEquals(retrievedAttributes.customUserData, "CustomData7"); + test:assertEquals(retrievedAttributes.enabled, false); +} + +@test:Config { + groups: ["endpoint"] +} +function getEndpointAttributesWithInvalidArnTest() returns error? { + EndpointAttributes attributes = {enabled: false}; + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken7", attributes, + "CustomData7"); + + EndpointAttributes|Error retrievedAttributes = amazonSNSClient->getEndpointAttributes(arn + "invalid"); + test:assertTrue(retrievedAttributes is OperationError); + test:assertEquals((retrievedAttributes).message(), "Invalid parameter: EndpointArn Reason: ARN specifies an invalid endpointId: UUID must be encoded in exactly 36 characters."); +} + +@test:Config { + groups: ["endpoint"] +} +function setEndpointAttributesTest() returns error? { + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken8"); + _ = check amazonSNSClient->setEndpointAttributes(arn, { + enabled: false, token: testRunId + "testDeviceToken9", customUserData: "CustomData9" + }); + + EndpointAttributes retrievedAttributes = check amazonSNSClient->getEndpointAttributes(arn); + test:assertEquals(retrievedAttributes.token, testRunId + "testDeviceToken9"); + test:assertEquals(retrievedAttributes.customUserData, "CustomData9"); + test:assertEquals(retrievedAttributes.enabled, false); +} + +@test:Config { + groups: ["endpoint"] +} +function setEndpointAttributesWithInvalidArnTest() returns error? { + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken10"); + Error? e = amazonSNSClient->setEndpointAttributes(arn + "invalid", { + enabled: false, + token: testRunId + "testDeviceToken10", + customUserData: "CustomData10" + }); + + test:assertTrue(e is OperationError); + test:assertEquals((e).message(), "Invalid parameter: EndpointArn Reason: ARN specifies an invalid endpointId: UUID must be encoded in exactly 36 characters."); +} + +@test:Config { + groups: ["endpoin"] +} +function deleteEndpointTest() returns error? { + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken11"); + _ = check amazonSNSClient->deleteEndpoint(arn); + + EndpointAttributes|Error attributes = amazonSNSClient->getEndpointAttributes(arn); + test:assertTrue(attributes is OperationError); + test:assertEquals((attributes).message(), "Endpoint does not exist"); +} + +@test:Config { + groups: ["endpoint"] +} +function deleteEndpointWithInvalidTest() returns error? { + string arn = check amazonSNSClient->createEndpoint(applicationArn, testRunId + "testDeviceToken11"); + Error? e = amazonSNSClient->deleteEndpoint(arn + "invalid"); + + test:assertTrue(e is OperationError); + test:assertEquals((e).message(), "Invalid parameter: EndpointArn Reason: ARN specifies an invalid endpointId: UUID must be encoded in exactly 36 characters."); +} diff --git a/ballerina/tests/init.bal b/ballerina/tests/init.bal new file mode 100644 index 0000000..3b20395 --- /dev/null +++ b/ballerina/tests/init.bal @@ -0,0 +1,45 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/time; +import ballerina/lang.regexp; + +configurable string accessKeyId = ?; +configurable string secretAccessKey = ?; +configurable string region = ?; + +ConnectionConfig config = { + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, + region: region +}; + +Client amazonSNSClient = check new(config); + +string testRunId = regexp:replaceAll(re `[:.]`, time:utcToString(time:utcNow()), ""); + +configurable string testHttp = ?; +configurable string testHttps = ?; +configurable string testEmail = ?; +configurable string testPhoneNumber = ?; +configurable string testApplication = ?; +configurable string testIamRole = ?; +configurable string testAwsAccountId = ?; +configurable string testEndpoint = ?; + +function isArn(string arn) returns boolean { + return arn.startsWith("arn:aws:"); +} diff --git a/ballerina/tests/permission-tests.bal b/ballerina/tests/permission-tests.bal new file mode 100644 index 0000000..a47686a --- /dev/null +++ b/ballerina/tests/permission-tests.bal @@ -0,0 +1,128 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +@test:Config { + groups: ["permission"] +} +function addPermissionBasicTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic1"); + check amazonSNSClient->addPermission(topic, [PUBLISH], [testAwsAccountId], "testLabel"); +} + +@test:Config { + groups: ["permission"] +} +function addPermissionComplexTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic2"); + check amazonSNSClient->addPermission( + topic, + [ADD_PERMISSION, DELETE_TOPIC, GET_DATA_PROTECTION_POLICY, GET_TOPIC_ATTRIBUTES, LIST_SUBSCRIPTIONS, LIST_TAGS, + PUBLISH, PUT_DATA_PROTECTION_POLICY, REMOVE_PERMISSION, SET_TOPIC_ATTRIBUTES, SUBSCRIBE], + [testAwsAccountId], + "testLabel" + ); +} + +@test:Config { + groups: ["permission"] +} +function addPermissionComplexTest2() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic3"); + check amazonSNSClient->addPermission( + topic, + [ADD_PERMISSION, DELETE_TOPIC, GET_DATA_PROTECTION_POLICY, GET_TOPIC_ATTRIBUTES, LIST_SUBSCRIPTIONS, LIST_TAGS, + PUBLISH, PUT_DATA_PROTECTION_POLICY, REMOVE_PERMISSION, SET_TOPIC_ATTRIBUTES, SUBSCRIBE], + [testAwsAccountId, testAwsAccountId], + "testLabel" + ); +} + +@test:Config { + groups: ["permission"] +} +function addPermissionInvalidAccountIdTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic4"); + Error? e = amazonSNSClient->addPermission( + topic, + [ + ADD_PERMISSION, + DELETE_TOPIC, + GET_DATA_PROTECTION_POLICY, + GET_TOPIC_ATTRIBUTES, + LIST_SUBSCRIPTIONS, + LIST_TAGS, + PUBLISH, + PUT_DATA_PROTECTION_POLICY, + REMOVE_PERMISSION, + SET_TOPIC_ATTRIBUTES, + SUBSCRIBE + ], + ["InvalidAccountId"], + "testLabel" + ); + test:assertTrue(e is OperationError, "OperationError expected."); + test:assertEquals((e).message(), "Invalid parameter: A provided account ID is not valid"); +} + +@test:Config { + groups: ["permission"] +} +// SNS allows duplicate permissions to be added. +function addPermissionWithDuplicatesTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic5"); + check amazonSNSClient->addPermission(topic, [PUBLISH, PUBLISH], [testAwsAccountId], "testLabel"); +} + +@test:Config { + groups: ["permission"] +} +function removePermissionTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic6"); + check amazonSNSClient->addPermission(topic, [PUBLISH], [testAwsAccountId], "testLabel"); + check amazonSNSClient->removePermission(topic, "testLabel"); +} + +@test:Config { + groups: ["permission"] +} +function removePermissionTest2() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic7"); + check amazonSNSClient->addPermission(topic, [PUBLISH], [testAwsAccountId], "testLabel"); + check amazonSNSClient->addPermission(topic, [PUBLISH], [testAwsAccountId], "testLabel2"); + check amazonSNSClient->removePermission(topic, "testLabel"); +} + +@test:Config { + groups: ["permission"] +} +// SNS allows removing a permission label that does not exist. +function removePermissionDoesNotExistTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic8"); + check amazonSNSClient->removePermission(topic, "ThisLabelDoesNotExist"); +} + +@test:Config { + groups: ["permission"] +} +function removePermissionTopicDoesNotExistTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testPermissionsTopic9"); + check amazonSNSClient->addPermission(topic, [PUBLISH], [testAwsAccountId], "testLabel"); + Error? e = amazonSNSClient->removePermission(topic + "invalid", "testLabel"); + test:assertTrue(e is OperationError, "OperationError expected."); + test:assertEquals((e).message(), "Topic does not exist"); +} diff --git a/ballerina/tests/phone-number-tests.bal b/ballerina/tests/phone-number-tests.bal new file mode 100644 index 0000000..c7e3f8d --- /dev/null +++ b/ballerina/tests/phone-number-tests.bal @@ -0,0 +1,69 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +@test:Config { + groups: ["phone-number"] +} +function listOrignationPhoneNumbersTest() returns error? { + stream phoneNumberStream = amazonSNSClient->listOriginationNumbers(); + OriginationPhoneNumber[] phoneNumbers = check from OriginationPhoneNumber phoneNumber in phoneNumberStream + select phoneNumber; + test:assertEquals(phoneNumbers.length(), 1, "Invalid number of origination phone numbers"); +} + +@test:Config { + groups: ["phone-number"] +} +function listOptedOutPhoneNumbersTest() returns error? { + stream phoneNumberStream = amazonSNSClient->listPhoneNumbersOptedOut(); + string[] phoneNumbers = check from string phoneNumber in phoneNumberStream + select phoneNumber; + test:assertEquals(phoneNumbers.length(), 0, "Invalid number of opted out phone numbers"); +} + +@test:Config { + groups: ["phone-number"] +} +function checkIfPhoneNumberisOptedOutTest() returns error? { + _ = check amazonSNSClient->checkIfPhoneNumberIsOptedOut(testPhoneNumber); +} + +@test:Config { + groups: ["phone-number"] +} +function checkIfPhoneNumberisOptedOutWithInvalidTest() returns error? { + boolean|Error optedOut = amazonSNSClient->checkIfPhoneNumberIsOptedOut(testPhoneNumber + "x"); + test:assertTrue(optedOut is OperationError, "Operation Error expected"); + test:assertEquals((optedOut).message(), "Invalid parameter: PhoneNumber Reason: input incorrectly formatted"); +} + +@test:Config { + groups: ["phone-number"] +} +function optInPhoneNumberTest() returns error? { + check amazonSNSClient->optInPhoneNumber(testPhoneNumber); +} + +@test:Config { + groups: ["phone-number"] +} +function optInPhoneNumberInvalidTest() returns error? { + Error? e = amazonSNSClient->optInPhoneNumber(testPhoneNumber + "x"); + test:assertTrue(e is OperationError, "Operation Error expected"); + test:assertEquals((e).message(), "Invalid parameter: PhoneNumber Reason: input incorrectly formatted"); +} diff --git a/ballerina/tests/platform-application-tests.bal b/ballerina/tests/platform-application-tests.bal new file mode 100644 index 0000000..7e76354 --- /dev/null +++ b/ballerina/tests/platform-application-tests.bal @@ -0,0 +1,297 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; +import ballerina/lang.runtime as runtime; + +configurable string firebaseServerKey = ?; +configurable string amazonClientId = ?; +configurable string amazonClientSecret = ?; + +@test:Config { + groups: ["platformApplication"] +} +function createFirebasePlatformApplicationTest() returns error? { + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "FirebasePlatformApplication", + FIREBASE_CLOUD_MESSAGING, auth = {platformCredential: firebaseServerKey}); + test:assertTrue(isArn(arn)); +} + +@test:Config { + groups: ["platformApplication"] +} +function createAmazonPlatformApplicationTest() returns error? { + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "AmazonPlatformApplication", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}); + test:assertTrue(isArn(arn)); +} + +@test:Config { + groups: ["platformApplication"] +} +function createPlatformApplicationWithInvalidKeyTest1() returns error? { + string|Error arn = amazonSNSClient->createPlatformApplication(testRunId + "InvalidPlatformApplication", + FIREBASE_CLOUD_MESSAGING, auth = {platformCredential: "invalid"}); + test:assertTrue(arn is OperationError); + test:assertEquals((arn).message(), "Invalid parameter: Attributes Reason: Platform credentials are invalid"); +} + +@test:Config { + groups: ["platformApplication"] +} +function createPlatformApplicationWithInvalidKeyTest2() returns error? { + string|Error arn = amazonSNSClient->createPlatformApplication(testRunId + "InvalidPlatformApplication", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: "invalid"}); + test:assertTrue(arn is OperationError); + test:assertEquals((arn).message(), "Invalid parameter: Attributes Reason: Platform credentials are invalid"); +} + +@test:Config { + groups: ["platformApplication"] +} +function createPlatformApplicationWithAttributesTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "PlatformApplicationTopic"); + PlatformApplicationAttributes attributes = { + eventDeliveryFailure: topicArn, + eventEndpointCreated: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 5 + }; + + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "PlatformApplicationWithAttributes", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}, + attributes = attributes); + test:assertTrue(isArn(arn)); +} + +@test:Config { + groups: ["platformApplication"] +} +function createPlatformApplicationAlreadyExists() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "PlatformApplicationTopic"); + PlatformApplicationAttributes attributes = { + eventDeliveryFailure: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 5 + }; + + _ = check amazonSNSClient->createPlatformApplication(testRunId + "PlatformApplicationAlreadyExists", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}, + attributes = attributes); + + attributes.eventEndpointCreated = topicArn; + string|error arn = amazonSNSClient->createPlatformApplication(testRunId + "PlatformApplicationAlreadyExists", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}, + attributes = attributes); + test:assertTrue(arn is OperationError); + test:assertEquals((arn).message(), "Invalid parameter: Name Reason: An application with the same name but different properties already exists"); +} + +@test:Config { + groups: ["platformApplication"] +} +function createPlatformWithInvalidAttributes() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "PlatformApplicationTopic"); + PlatformApplicationAttributes attributes = { + eventDeliveryFailure: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 101 + }; + + string|error arn = amazonSNSClient->createPlatformApplication(testRunId + "PlatformApplicationAlreadyExists", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}, + attributes = attributes); + test:assertTrue(arn is OperationError); + test:assertEquals((arn).message(), "Invalid parameter: Attributes Reason: Invalid value for attribute: SuccessFeedbackSampleRate: 101 value provided is not an integer between 0-100"); +} + +@test:Config { + groups: ["platformApplication"] +} +function listPlatformApplicationsTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "ListPlatformApplicationsTopic"); + PlatformApplicationAttributes attributes = { + eventEndpointCreated: topicArn, + eventDeliveryFailure: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 5 + }; + + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "ListPlatformApplications", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}, + attributes = attributes); + + // Validate newly created platform application + stream platformApplications = amazonSNSClient->listPlatformApplications(); + PlatformApplication[] retrievedPlatformApplications = + check from PlatformApplication platformApplication in platformApplications + where platformApplication.platformApplicationArn == arn + select platformApplication; + test:assertEquals(retrievedPlatformApplications.length(), 1); + test:assertEquals(retrievedPlatformApplications[0].platformApplicationArn, arn); + test:assertEquals(retrievedPlatformApplications[0].eventEndpointCreated, topicArn); + test:assertEquals(retrievedPlatformApplications[0].eventDeliveryFailure, topicArn); + test:assertEquals(retrievedPlatformApplications[0].eventEndpointDeleted, topicArn); + test:assertEquals(retrievedPlatformApplications[0].eventEndpointUpdated, topicArn); + test:assertEquals(retrievedPlatformApplications[0].successFeedbackRoleArn, testIamRole); + test:assertEquals(retrievedPlatformApplications[0].failureFeedbackRoleArn, testIamRole); + test:assertEquals(retrievedPlatformApplications[0].successFeedbackSampleRate, 5); + test:assertEquals(retrievedPlatformApplications[0].enabled, true); + + platformApplications = amazonSNSClient->listPlatformApplications(); + string[] arns = check from PlatformApplication platformApplication in platformApplications + select platformApplication.platformApplicationArn; + + test:assertTrue(arns.length() > 100, "There should be over 100 platform applications."); + + // Ensure there are no duplicates + foreach string platformApplicationArn in arns { + test:assertEquals(arns.indexOf(platformApplicationArn), arns.lastIndexOf(platformApplicationArn), + "Platform application " + platformApplicationArn + " duplicated in the list."); + } +} + +@test:Config { + groups: ["platformApplication"] +} +function getPlatformApplicationTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "GetPlatformApplicationsTopic"); + PlatformApplicationAttributes attributes = { + eventEndpointCreated: topicArn, + eventDeliveryFailure: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 5 + }; + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "GetPlatformApplication", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}, + attributes = attributes); + + RetrievablePlatformApplicationAttributes retrieved = check amazonSNSClient->getPlatformApplicationAttributes(arn); + test:assertEquals(retrieved.eventEndpointCreated, topicArn); + test:assertEquals(retrieved.eventDeliveryFailure, topicArn); + test:assertEquals(retrieved.eventEndpointDeleted, topicArn); + test:assertEquals(retrieved.eventEndpointUpdated, topicArn); + test:assertEquals(retrieved.successFeedbackRoleArn, testIamRole); + test:assertEquals(retrieved.failureFeedbackRoleArn, testIamRole); + test:assertEquals(retrieved.successFeedbackSampleRate, 5); + test:assertEquals(retrieved.enabled, true); +} + +@test:Config { + groups: ["platformApplication"] +} +function getPlatformApplicationDoesNotExistTest() returns error? { + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "GetPlatformApplicationDNE", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}); + + RetrievablePlatformApplicationAttributes|Error retrieved = amazonSNSClient->getPlatformApplicationAttributes(arn + "invalid"); + test:assertTrue(retrieved is OperationError); + test:assertEquals((retrieved).message(), "PlatformApplication does not exist"); +} + +@test:Config { + groups: ["platformApplication"] +} +function setPlatformApplicationAttributesTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "SetPlatformApplicationsAttrTopic"); + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "SetPlatformApplicationAttr", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}); + + PlatformApplicationAttributes attributes = { + eventEndpointCreated: topicArn, + eventDeliveryFailure: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 5 + }; + _ = check amazonSNSClient->setPlatformApplicationAttributes(arn, attributes); + + RetrievablePlatformApplicationAttributes retrieved = check amazonSNSClient->getPlatformApplicationAttributes(arn); + test:assertEquals(retrieved.eventEndpointCreated, topicArn); + test:assertEquals(retrieved.eventDeliveryFailure, topicArn); + test:assertEquals(retrieved.eventEndpointDeleted, topicArn); + test:assertEquals(retrieved.eventEndpointUpdated, topicArn); + test:assertEquals(retrieved.successFeedbackRoleArn, testIamRole); + test:assertEquals(retrieved.failureFeedbackRoleArn, testIamRole); + test:assertEquals(retrieved.successFeedbackSampleRate, 5); + test:assertEquals(retrieved.enabled, true); +}; + +@test:Config { + groups: ["platformApplication"] +} +function setPlatformApplicationAttributesNegativeTest() returns error? { + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "SetPlatformApplicationNegAttr", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}); + + string topicArn = check amazonSNSClient->createTopic(testRunId + "SetPlatformApplicationsAttrNegTopic"); + PlatformApplicationAttributes attributes = { + eventEndpointCreated: topicArn, + eventDeliveryFailure: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 101 + }; + Error? e = amazonSNSClient->setPlatformApplicationAttributes(arn, attributes); + test:assertTrue(e is OperationError); + test:assertEquals((e).message(), "Invalid parameter: Attributes Reason: Invalid value for attribute: SuccessFeedbackSampleRate: 101 value provided is not an integer between 0-100"); +}; + +@test:Config { + groups: ["platformApplication"] +} +function deletePlatformApplicationTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "SetPlatformApplicationsAttrTopic"); + PlatformApplicationAttributes attributes = { + eventEndpointCreated: topicArn, + eventDeliveryFailure: topicArn, + eventEndpointDeleted: topicArn, + eventEndpointUpdated: topicArn, + successFeedbackRoleArn: testIamRole, + failureFeedbackRoleArn: testIamRole, + successFeedbackSampleRate: 5 + }; + + string arn = check amazonSNSClient->createPlatformApplication(testRunId + "DeletePlatformApplication", + AMAZON_DEVICE_MESSAGING, auth = {platformCredential: amazonClientSecret, platformPrincipal: amazonClientId}, + attributes = attributes); + + _ = check amazonSNSClient->deletePlatformApplication(arn); + _ = runtime:sleep(10); + + RetrievablePlatformApplicationAttributes|Error retrieved = amazonSNSClient->getPlatformApplicationAttributes(arn); + test:assertTrue(retrieved is OperationError); + test:assertEquals((retrieved).message(), "PlatformApplication does not exist"); +} diff --git a/ballerina/tests/publish-tests.bal b/ballerina/tests/publish-tests.bal new file mode 100644 index 0000000..8940ab0 --- /dev/null +++ b/ballerina/tests/publish-tests.bal @@ -0,0 +1,311 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +string standardTopic = ""; +string fifoTopicWithCBD = ""; +string fifoTopicWithoutCBD = ""; +string invalidApplicationArn = testApplication + "x"; + +@test:BeforeGroups {value: ["publish"]} +function beforePublishTests() returns error? { + standardTopic = check amazonSNSClient->createTopic(testRunId + "PublishStandardTopic"); + fifoTopicWithCBD = check amazonSNSClient->createTopic(testRunId + "PublishFifoTopicWithCBD", + {fifoTopic: true, contentBasedDeduplication: true}); + fifoTopicWithoutCBD = check amazonSNSClient->createTopic(testRunId + "PublishFifoTopicWithoutCDB", + {fifoTopic: true}); +} + +@test:Config { + groups: ["publish"] +} +function publishToStandardTopicTest() returns error? { + PublishMessageResponse response = check amazonSNSClient->publish(standardTopic, "Test Message"); + test:assertTrue(response.messageId != "", "MessageID is empty."); +} + +@test:Config { + groups: ["publish"] +} +function publishToFifoTopicWithCBDTest() returns error? { + PublishMessageResponse response = check amazonSNSClient->publish(fifoTopicWithCBD, + "Test Message", groupId = "test"); + test:assertTrue(response.messageId != "", "MessageID is empty."); + test:assertTrue(response.sequenceNumber is string && response.sequenceNumber != "", "SequenceNumber is empty."); +} + +@test:Config { + groups: ["publish"] +} +function publishToFifoTopicWithCBDNegativeTest() returns error? { + PublishMessageResponse|Error response = amazonSNSClient->publish(fifoTopicWithCBD, "Test Message"); + test:assertTrue(response is Error); + test:assertEquals((response).message(), "A message published to a FIFO topic requires a group ID."); +} + + +@test:Config { + groups: ["publish"] +} +function publishToFifoTopicWithoutCBDTest() returns error? { + PublishMessageResponse response = check amazonSNSClient->publish(fifoTopicWithoutCBD, + "Test Message", groupId = "test", deduplicationId = "test"); + test:assertTrue(response.messageId != "", "MessageID is empty."); + test:assertTrue(response.sequenceNumber is string && response.sequenceNumber != "", "SequenceNumber is empty."); +} + +@test:Config { + groups: ["publish"] +} +function publishToFifoTopicWithoutCBDNegativeTest() returns error? { + PublishMessageResponse|Error response = amazonSNSClient->publish(fifoTopicWithoutCBD, + "Test Message", groupId = "test"); + test:assertTrue(response is OperationError); + test:assertEquals((response).message(), "Invalid parameter: The topic should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly"); +} + +@test:Config { + groups: ["publish"] +} +function publishToPhoneNumber() returns error? { + PublishMessageResponse response = check amazonSNSClient->publish("+94771952226", "Test Message", + targetType = PHONE_NUMBER); + test:assertTrue(response.messageId != "", "MessageID is empty."); +} + +@test:Config { + groups: ["publish"] +} +function publishToInvalidPhoneNumber() returns error? { + PublishMessageResponse|Error response = amazonSNSClient->publish("InvalidPhoneNumber", "Test Message", + targetType = PHONE_NUMBER); + test:assertTrue(response is OperationError); + test:assertEquals((response).message(), "Invalid parameter: PhoneNumber Reason: InvalidPhoneNumber is not valid to publish to"); +} + +@test:Config { + groups: ["publish"], + enable: false +} +function publishToApplication() returns error? { + PublishMessageResponse response = check amazonSNSClient->publish(testEndpoint, "Test Message", + targetType = ARN); + test:assertTrue(response.messageId != "", "MessageID is empty."); +} + +@test:Config { + groups: ["publish"] +} +function publishToInvalidApplication() returns error? { + PublishMessageResponse|Error response = amazonSNSClient->publish(invalidApplicationArn, "Test Message", + targetType = ARN); + test:assertTrue(response is OperationError); + test:assertEquals((response).message(), "Invalid parameter: TargetArn Reason: ARN specifies an invalid endpointId: UUID must be encoded in exactly 36 characters."); +} + +@test:Config { + groups: ["publish"] +} +function publishWithComplexPayload() returns error? { + Message message = { + default: "Default message", + subject: "Test message7", + email: "Normal email", + emailJson: "JSON email", + sqs: "SQS", + lambda: "Lambda", + http: "HTTP", + https: "HTTPS", + sms: "SMS", + firehose: "Firehose", + apns: {title: "APNS", body: "APNS Body"}.toString(), + apnsSandbox: {title: "APNS Sandbox", body: "APNS Sandbox Body"}.toString(), + apnsVoip: {title: "APNS Voip", body: "APNS Voip Body"}.toString(), + apnsVoipSandbox: {title: "APNS Voip Sandbox", body: "APNS Voip Sandbox Body"}.toString(), + macos: {title: "MacOS", body: "MacOS Body"}.toString(), + macosSandbox: {title: "MacOS Sandbox", body: "MacOS Sandbox Body"}.toString(), + gcm: {title: "GCM", body: "GCM Body"}.toString(), + adm: {title: "ADM", body: "ADM Body"}.toString(), + baidu: {title: "Baidu", body: "Baidu Body"}.toString(), + mpns: {title: "MPNS", body: "MPNS Body"}.toString(), + wns: {title: "WNS", body: "WNS Body"}.toString() + }; + PublishMessageResponse response = check amazonSNSClient->publish(standardTopic, message); + test:assertTrue(response.messageId != "", "MessageID is empty."); +} + +@test:Config { + groups: ["publish"] +} +function publishWithAttributesTest() returns error? { + map attributes = { + "StringAttribute": "StringAttributeValue", + "IntAttribute": 123, + "FloatAttribute": 123.45, + "BinaryAttribute": "BinaryAttributeValue".toBytes(), + "StringArrayAttribute": ["StringListAttributeValue1", "StringListAttributeValue2", true, false, (), 123, 123.45] + }; + PublishMessageResponse response = check amazonSNSClient->publish(standardTopic, "Test Message", + attributes = attributes); + test:assertTrue(response.messageId != "", "MessageID is empty."); +} + +@test:Config { + groups: ["publish", "batch"] +} +function publishBatchTest() returns error? { + PublishBatchRequestEntry[] entries = [ + {message: "Test Message 1"}, + {message: "Test Message 2"}, + {message: "Test Message 3"} + ]; + PublishBatchResponse response = check amazonSNSClient->publishBatch(standardTopic, entries); + test:assertEquals(response.successful.length(), 3); + test:assertEquals(response.failed.length(), 0); +} + +@test:Config { + groups: ["publish", "batch"] +} +function publishBatchWithIdsTest() returns error? { + PublishBatchRequestEntry[] entries = [ + {id: "id1", message: "Test Message 1"}, + {id: "id2", message: "Test Message 2"}, + {id: "id3", message: "Test Message 3"} + ]; + PublishBatchResponse response = check amazonSNSClient->publishBatch(standardTopic, entries); + test:assertEquals(response.successful.length(), 3); + test:assertEquals(response.failed.length(), 0); + test:assertEquals(response.successful[0].id, "id1"); + test:assertEquals(response.successful[1].id, "id2"); + test:assertEquals(response.successful[2].id, "id3"); + test:assertTrue(response.successful[0].messageId.length() > 0); + test:assertTrue(response.successful[1].messageId.length() > 0); + test:assertTrue(response.successful[2].messageId.length() > 0); +} + +@test:Config { + groups: ["publish", "batch"] +} +function publishBatchToFifoTest() returns error? { + PublishBatchRequestEntry[] entries = [ + {id: "id1", message: "Test Message 1", groupId: "group1", deduplicationId: "dedup1"}, + {id: "id2", message: "Test Message 2", groupId: "group2", deduplicationId: "dedup2"}, + {id: "id3", message: "Test Message 3", groupId: "group3", deduplicationId: "dedup3"} + ]; + PublishBatchResponse response = check amazonSNSClient->publishBatch(fifoTopicWithoutCBD, entries); + test:assertEquals(response.successful.length(), 3); + test:assertEquals(response.failed.length(), 0); + test:assertEquals(response.successful[0].id, "id1"); + test:assertEquals(response.successful[1].id, "id2"); + test:assertEquals(response.successful[2].id, "id3"); + test:assertTrue(response.successful[0].messageId.length() > 0); + test:assertTrue(response.successful[1].messageId.length() > 0); + test:assertTrue(response.successful[2].messageId.length() > 0); + test:assertTrue(response.successful[0].sequenceNumber is string); + test:assertTrue(response.successful[1].sequenceNumber is string); + test:assertTrue(response.successful[2].sequenceNumber is string); +} + +@test:Config { + groups: ["publish", "batch"] +} +function publishBatchToFifoTestNegative() returns error? { + PublishBatchRequestEntry[] entries = [ + {id: "id1", message: "Test Message 1", groupId: "group1", deduplicationId: "dedup1"}, + {id: "id2", message: "Test Message 2", groupId: "group2", deduplicationId: "dedup2"}, + {id: "id3", message: "Test Message 3", groupId: "group3"} + ]; + PublishBatchResponse|error response = amazonSNSClient->publishBatch(fifoTopicWithoutCBD, entries); + test:assertTrue(response is OperationError); + test:assertEquals((response).message(), "Invalid parameter: The topic should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly"); +} + +@test:Config { + groups: ["publish", "batch"] +} +function publishBatchWithComplexPayload() returns error? { + PublishBatchRequestEntry[] entries = [ + {id: "id1", message: {default: "Test Message 1", subject: "Subject"}}, + {id: "id2", message: {default: "Test Message 2", subject: "Subject", email: "Normal email", emailJson: "JSON email", sqs: "SQS", lambda: "Lambda", http: "HTTP", https: "HTTPS", sms: "SMS", firehose: "Firehose", apns: {title: "APNS", body: "APNS Body"}.toString(), apnsSandbox: {title: "APNS Sandbox", body: "APNS Sandbox Body"}.toString(), apnsVoip: {title: "APNS Voip", body: "APNS Voip Body"}.toString(), apnsVoipSandbox: {title: "APNS Voip Sandbox", body: "APNS Voip Sandbox Body"}.toString(), macos: {title: "MacOS", body: "MacOS Body"}.toString(), macosSandbox: {title: "MacOS Sandbox", body: "MacOS Sandbox Body"}.toString(), gcm: {title: "GCM", body: "GCM Body"}.toString(), adm: {title: "ADM", body: "ADM Body"}.toString(), baidu: {title: "Baidu", body: "Baidu Body"}.toString(), mpns: {title: "MPNS", body: "MPNS Body"}.toString(), wns: {title: "WNS", body: "WNS Body"}.toString()}}, + {id: "id3", message: {default: "Test Message 2", subject: "Subject", email: "Normal email", emailJson: "JSON email", sqs: "SQS", lambda: "Lambda", http: "HTTP", https: "HTTPS", sms: "SMS", firehose: "Firehose", apns: {title: "APNS", body: "APNS Body"}.toString(), apnsSandbox: {title: "APNS Sandbox", body: "APNS Sandbox Body"}.toString(), apnsVoip: {title: "APNS Voip", body: "APNS Voip Body"}.toString(), apnsVoipSandbox: {title: "APNS Voip Sandbox", body: "APNS Voip Sandbox Body"}.toString(), macos: {title: "MacOS", body: "MacOS Body"}.toString(), macosSandbox: {title: "MacOS Sandbox", body: "MacOS Sandbox Body"}.toString(), gcm: {title: "GCM", body: "GCM Body"}.toString(), adm: {title: "ADM", body: "ADM Body"}.toString(), baidu: {title: "Baidu", body: "Baidu Body"}.toString(), mpns: {title: "MPNS", body: "MPNS Body"}.toString(), wns: {title: "WNS", body: "WNS Body"}.toString()}} + ]; + PublishBatchResponse response = check amazonSNSClient->publishBatch(standardTopic, entries); + test:assertEquals(response.successful.length(), 3); + test:assertEquals(response.failed.length(), 0); + test:assertEquals(response.successful[0].id, "id1"); + test:assertEquals(response.successful[1].id, "id2"); + test:assertEquals(response.successful[2].id, "id3"); + test:assertTrue(response.successful[0].messageId.length() > 0); + test:assertTrue(response.successful[1].messageId.length() > 0); + test:assertTrue(response.successful[2].messageId.length() > 0); +} + +@test:Config { + groups: ["publish", "batch"] +} +function publishBatchWithAttributes() returns error? { + map attributes = { + "StringAttribute": "StringAttributeValue", + "IntAttribute": 123, + "FloatAttribute": 123.45, + "BinaryAttribute": "BinaryAttributeValue".toBytes(), + "StringArrayAttribute": ["StringListAttributeValue1", "StringListAttributeValue2", true, false, (), 123, 123.45] + }; + + PublishBatchRequestEntry[] entries = [ + {message: "Test Message 1", attributes: attributes}, + {message: "Test Message 2", attributes: attributes}, + {message: "Test Message 3", attributes: attributes} + ]; + PublishBatchResponse response = check amazonSNSClient->publishBatch(standardTopic, entries); + test:assertEquals(response.successful.length(), 3); + test:assertEquals(response.failed.length(), 0); + test:assertEquals(response.successful[0].id, "1"); + test:assertEquals(response.successful[1].id, "2"); + test:assertEquals(response.successful[2].id, "3"); + test:assertTrue(response.successful[0].messageId.length() > 0); + test:assertTrue(response.successful[1].messageId.length() > 0); + test:assertTrue(response.successful[2].messageId.length() > 0); +} + +@test:Config { + groups: ["publish", "batch"] +} +function publishBatchWithFailures() returns error? { + PublishBatchRequestEntry[] entries = [ + {id: "id1", message: {default: "Test Message 1", subject: "Subject"}}, + {id: "id2", message: {default: "Test Message 2", subject: "Invalid\nSubject", email: "Normal email", emailJson: "JSON email", sqs: "SQS", lambda: "Lambda", http: "HTTP", https: "HTTPS", sms: "SMS", firehose: "Firehose", apns: {title: "APNS", body: "APNS Body"}.toString(), apnsSandbox: {title: "APNS Sandbox", body: "APNS Sandbox Body"}.toString(), apnsVoip: {title: "APNS Voip", body: "APNS Voip Body"}.toString(), apnsVoipSandbox: {title: "APNS Voip Sandbox", body: "APNS Voip Sandbox Body"}.toString(), macos: {title: "MacOS", body: "MacOS Body"}.toString(), macosSandbox: {title: "MacOS Sandbox", body: "MacOS Sandbox Body"}.toString(), gcm: {title: "GCM", body: "GCM Body"}.toString(), adm: {title: "ADM", body: "ADM Body"}.toString(), baidu: {title: "Baidu", body: "Baidu Body"}.toString(), mpns: {title: "MPNS", body: "MPNS Body"}.toString(), wns: {title: "WNS", body: "WNS Body"}.toString()}}, + {id: "id3", message: {default: "Test Message 2", subject: "Subject", email: "Normal email", emailJson: "JSON email", sqs: "SQS", lambda: "Lambda", http: "HTTP", https: "HTTPS", sms: "SMS", firehose: "Firehose", apns: {title: "APNS", body: "APNS Body"}.toString(), apnsSandbox: {title: "APNS Sandbox", body: "APNS Sandbox Body"}.toString(), apnsVoip: {title: "APNS Voip", body: "APNS Voip Body"}.toString(), apnsVoipSandbox: {title: "APNS Voip Sandbox", body: "APNS Voip Sandbox Body"}.toString(), macos: {title: "MacOS", body: "MacOS Body"}.toString(), macosSandbox: {title: "MacOS Sandbox", body: "MacOS Sandbox Body"}.toString(), gcm: {title: "GCM", body: "GCM Body"}.toString(), adm: {title: "ADM", body: "ADM Body"}.toString(), baidu: {title: "Baidu", body: "Baidu Body"}.toString(), mpns: {title: "MPNS", body: "MPNS Body"}.toString(), wns: {title: "WNS", body: "WNS Body"}.toString()}}, + {id: "id4", message: {default: "Test Message 2", subject: "Invalid\nSubject", email: "Normal email", emailJson: "JSON email", sqs: "SQS", lambda: "Lambda", http: "HTTP", https: "HTTPS", sms: "SMS", firehose: "Firehose", apns: {title: "APNS", body: "APNS Body"}.toString(), apnsSandbox: {title: "APNS Sandbox", body: "APNS Sandbox Body"}.toString(), apnsVoip: {title: "APNS Voip", body: "APNS Voip Body"}.toString(), apnsVoipSandbox: {title: "APNS Voip Sandbox", body: "APNS Voip Sandbox Body"}.toString(), macos: {title: "MacOS", body: "MacOS Body"}.toString(), macosSandbox: {title: "MacOS Sandbox", body: "MacOS Sandbox Body"}.toString(), gcm: {title: "GCM", body: "GCM Body"}.toString(), adm: {title: "ADM", body: "ADM Body"}.toString(), baidu: {title: "Baidu", body: "Baidu Body"}.toString(), mpns: {title: "MPNS", body: "MPNS Body"}.toString(), wns: {title: "WNS", body: "WNS Body"}.toString()}} + ]; + PublishBatchResponse response = check amazonSNSClient->publishBatch(standardTopic, entries); + test:assertEquals(response.successful.length(),2); + test:assertEquals(response.failed.length(), 2); + test:assertEquals(response.successful[0].id, "id1"); + test:assertEquals(response.successful[1].id, "id3"); + test:assertTrue(response.successful[0].messageId.length() > 0); + test:assertTrue(response.successful[1].messageId.length() > 0); + test:assertEquals(response.failed[0].id, "id2"); + test:assertEquals(response.failed[1].id, "id4"); + test:assertEquals(response.failed[0].code , "InvalidParameter"); + test:assertEquals(response.failed[1].code, "InvalidParameter"); + test:assertEquals(response.failed[0].message, "Invalid parameter: Subject"); + test:assertEquals(response.failed[1].message, "Invalid parameter: Subject"); + test:assertTrue(response.failed[0].senderFault); + test:assertTrue(response.failed[1].senderFault); +} diff --git a/ballerina/tests/sms-attributes-tests.bal b/ballerina/tests/sms-attributes-tests.bal new file mode 100644 index 0000000..3478a52 --- /dev/null +++ b/ballerina/tests/sms-attributes-tests.bal @@ -0,0 +1,96 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +@test:Config { + groups: ["sms-attributes"] +} +function setSMSAttributesTest() returns error? { + check amazonSNSClient->setSMSAttributes({ + monthlySpendLimit: 1, + deliveryStatusIAMRole: testIamRole, + deliveryStatusSuccessSamplingRate: 5, + defaultSenderID: "test", + defaultSMSType: TRANSACTIONAL + }); +} + +@test:Config { + groups: ["sms-attributes"] +} +function setSMSAttributesWithInvalidSenderIDTest() returns error? { + Error? e = amazonSNSClient->setSMSAttributes({ + monthlySpendLimit: 1, + deliveryStatusIAMRole: testIamRole, + deliveryStatusSuccessSamplingRate: 5, + defaultSenderID: "testSenderID", // too long + defaultSMSType: TRANSACTIONAL + }); + test:assertTrue(e is OperationError, "OperationError expected."); + test:assertEquals((e).message(), "DefaultSenderID is invalid"); +} + +@test:Config { + groups: ["sms-attributes"] +} +function setSMSAttributesWithInvalidS3BucketTest() returns error? { + Error? e = amazonSNSClient->setSMSAttributes({ + monthlySpendLimit: 1, + deliveryStatusIAMRole: testIamRole, + deliveryStatusSuccessSamplingRate: 5, + defaultSenderID: "test", + defaultSMSType: TRANSACTIONAL, + usageReportS3Bucket: "thisS3BucketDoesNotExist" + }); + test:assertTrue(e is OperationError, "OperationError expected."); + test:assertEquals((e).message(), "Invalid parameter: UsageReportS3Bucket Reason: The bucket you provided does not exist"); +} + +@test:Config { + groups: ["sms-attributes"] +} +function setSMSAttributesWithInvalidSampleRateTest() returns error? { + Error? e = amazonSNSClient->setSMSAttributes({ + monthlySpendLimit: 1, + deliveryStatusIAMRole: testIamRole, + deliveryStatusSuccessSamplingRate: 101, // max value is 100 + defaultSenderID: "test", + defaultSMSType: TRANSACTIONAL + }); + test:assertTrue(e is OperationError, "OperationError expected."); + test:assertEquals((e).message(), "Invalid parameter: UsageReportS3Bucket Reason: The bucket you provided does not exist"); +} + +@test:Config { + groups: ["sms-attributes"] +} +function getSMSAttributesTest() returns error? { + check amazonSNSClient->setSMSAttributes({ + monthlySpendLimit: 1, + deliveryStatusIAMRole: testIamRole, + deliveryStatusSuccessSamplingRate: 50, + defaultSenderID: "test2", + defaultSMSType: PROMOTIONAL + }); + + SMSAttributes attributes = check amazonSNSClient->getSMSAttributes(); + test:assertEquals(attributes.monthlySpendLimit, 1); + test:assertEquals(attributes.deliveryStatusIAMRole, testIamRole); + test:assertEquals(attributes.deliveryStatusSuccessSamplingRate, 50); + test:assertEquals(attributes.defaultSenderID, "test2"); + test:assertEquals(attributes.defaultSMSType, PROMOTIONAL); +} diff --git a/ballerina/tests/sms-sandbox-tests.bal b/ballerina/tests/sms-sandbox-tests.bal new file mode 100644 index 0000000..ea9666d --- /dev/null +++ b/ballerina/tests/sms-sandbox-tests.bal @@ -0,0 +1,153 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; +import ballerina/lang.runtime; +import ballerina/random; + +int random = check random:createIntInRange(1, 100000); + +@test:Config { + groups: ["sms-sandbox"] +} +function createSMSSandboxPhoneNumberTest1() returns error? { + check amazonSNSClient->createSMSSandboxPhoneNumber("+947719" + random.toString()); +} + +@test:Config { + groups: ["sms-sandbox"] +} +function createSMSSandboxPhoneNumberTest2() returns error? { + check amazonSNSClient->createSMSSandboxPhoneNumber("+9411223344667788990"); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9411223344667788990"); +} + +@test:Config { + groups: ["sms-sandbox"] +} +function createSMSSandboxPhoneNumberTest3() returns error? { + check amazonSNSClient->createSMSSandboxPhoneNumber("123456789"); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("123456789"); + +} + +@test:Config { + groups: ["sms-sandbox"] +} +function creatSMSSandboxPhoneNumberInvalidTest1() returns error? { + Error? e = amazonSNSClient->createSMSSandboxPhoneNumber("invalid phone number"); + test:assertTrue(e is OperationError); + test:assertEquals((e).message(), "1 validation error detected: Value 'invalid phone number' at 'phoneNumber' failed to satisfy constraint: Member must satisfy regular expression pattern: ^(\\+[0-9]{8,}|[0-9]{0,9})$"); +} + +@test:Config { + groups: ["sms-sandbox"] +} +function creatSMSSandboxPhoneNumberInvalidTest2() returns error? { + Error? e = amazonSNSClient->createSMSSandboxPhoneNumber("1234567890"); + test:assertTrue(e is OperationError); + test:assertEquals((e).message(), "1 validation error detected: Value '1234567890' at 'phoneNumber' failed to satisfy constraint: Member must satisfy regular expression pattern: ^(\\+[0-9]{8,}|[0-9]{0,9})$"); +} + +@test:Config { + groups: ["sms-sandbox"] +} +function creatSMSSandboxPhoneNumberInvalidTest3() returns error? { + Error? e = amazonSNSClient->createSMSSandboxPhoneNumber("+94489347594376598435346594365943695348562987"); + test:assertTrue(e is OperationError); + test:assertEquals((e).message(), "1 validation error detected: Value '+94489347594376598435346594365943695348562987' at 'phoneNumber' failed to satisfy constraint: Member must have length less than or equal to 20"); +} + +@test:Config { + groups: ["sms-sandbox"] +} +function createSMSSandboxPhoneNumberWithLanugageCodeTest() returns error? { + check amazonSNSClient->createSMSSandboxPhoneNumber("+9477123456701", EN_US); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+941234401"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412344502", EN_GB); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412344502"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412344503", ES_419); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678903"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678904", ES_ES); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678904"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678905", DE_DE); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678905"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678906", FR_FR); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678906"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678907", FR_CA); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678907"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678908", IT_IT); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678908"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678909", JA_JP); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678909"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678910", PT_BR); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678910"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678911", KR_KR); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678911"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678912", ZH_CN); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678912"); + + check amazonSNSClient->createSMSSandboxPhoneNumber("+9412345678913", ZH_TW); + runtime:sleep(2); + check amazonSNSClient->deleteSMSSandboxPhoneNumber("+9412345678913"); +} + +@test:Config { + groups: ["sms-sandbox"] +} +function listSMSSandboxPhoneNumbersTest() returns error? { + stream phoneNumberStream = amazonSNSClient->listSMSSandboxPhoneNumbers(); + SMSSandboxPhoneNumber[] phoneNumbers = check from SMSSandboxPhoneNumber phoneNumber in phoneNumberStream + select phoneNumber; + + test:assertEquals(phoneNumbers.length(), 10); + _ = from SMSSandboxPhoneNumber phoneNumber in phoneNumbers + do { + test:assertTrue(phoneNumber.phoneNumber.startsWith("+947719")); + test:assertTrue(phoneNumber.status == PENDING || phoneNumber.status == VERIFIED); + }; +} + +@test:Config { + groups: ["sms-sandboxx"] +} +function getSMSSanboxAccountStatusTest() returns error? { + boolean status = check amazonSNSClient->getSMSSandboxAccountStatus(); + test:assertTrue(status); +} diff --git a/ballerina/tests/subscription-tests.bal b/ballerina/tests/subscription-tests.bal new file mode 100644 index 0000000..834a6b7 --- /dev/null +++ b/ballerina/tests/subscription-tests.bal @@ -0,0 +1,441 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +string topic = ""; +string fakeTopic = ""; + +@test:BeforeGroups {value: ["subscribe"]} +function beforeSubscribeTests() returns error? { + topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic"); + fakeTopic = check amazonSNSClient->createTopic(testRunId + "FakeSubscribeTopic"); + _ = check amazonSNSClient->deleteTopic(fakeTopic); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeWithoutReturnArnTest() returns error? { + string subsriptionArn = check amazonSNSClient->subscribe(topic, testEmail, EMAIL); + test:assertEquals(subsriptionArn, "pending confirmation"); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeWithReturnArnTest() returns error? { + string subsriptionArn = + check amazonSNSClient->subscribe(topic, testEmail, EMAIL, returnSubscriptionArn = true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeHttpTest() returns error? { + string subsriptionArn = + check amazonSNSClient->subscribe(topic, testHttp, HTTP, returnSubscriptionArn = true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeHttpsTest() returns error? { + string subsriptionArn = + check amazonSNSClient->subscribe(topic, testHttps, HTTPS, returnSubscriptionArn = true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeEmailTest() returns error? { + string subsriptionArn = + check amazonSNSClient->subscribe(topic, testEmail, EMAIL, returnSubscriptionArn = true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeEmailJsonTest() returns error? { + string subsriptionArn = + check amazonSNSClient->subscribe(topic, testEmail, EMAIL_JSON, returnSubscriptionArn = true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeSmsTest()returns error? { + string subsriptionArn = + check amazonSNSClient->subscribe(topic, "+94771952226", SMS, returnSubscriptionArn = true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeApplicationTest() returns error? { + string subsriptionArn = + check amazonSNSClient->subscribe(topic, testApplication, APPLICATION, returnSubscriptionArn = true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeToNonExistantTopicTest() returns error? { + string|Error subsriptionArn = + amazonSNSClient->subscribe(fakeTopic, testApplication, APPLICATION, returnSubscriptionArn = true); + + test:assertTrue(subsriptionArn is OperationError, "Expected error."); + test:assertEquals((subsriptionArn).message(), "Topic does not exist"); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeWithInvalidEndpointTest() returns error? { + string|Error subsriptionArn = + amazonSNSClient->subscribe(topic, "this is not an email", EMAIL, returnSubscriptionArn = true); + test:assertTrue(subsriptionArn is OperationError, "Expected error."); + test:assertEquals((subsriptionArn).message(), "Invalid parameter: Email address"); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeWithInvalidArnTest() returns error? { + string|Error subsriptionArn = + amazonSNSClient->subscribe(topic, "invalid arn", APPLICATION, returnSubscriptionArn = true); + test:assertTrue(subsriptionArn is OperationError, "Expected error."); + test:assertTrue((subsriptionArn).message().startsWith("Invalid parameter: Application endpoint arn invalid:invalid arn")); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeWithAttributesTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic2"); + SubscriptionAttributes attributes = { + deliveryPolicy: {healthyRetryPolicy: {numRetries: 3, minDelayTarget: 5, maxDelayTarget: 10}}, + filterPolicy: {store:["example_corp"]}, + filterPolicyScope: MESSAGE_BODY, + //TODO: test redrive policy and subscription role ARN + rawMessageDelivery: true + }; + string subsriptionArn = + check amazonSNSClient->subscribe(topic, testHttp, HTTP, attributes, true); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"] +} +function subscribeWithInvalidAttributeTest() returns error? { + SubscriptionAttributes attributes = { + deliveryPolicy: {healthyRetryPolicy: {numRetries: 3, minDelayTarget: 5, maxDelayTarget: 10}}, + filterPolicy: {store: ["example_corp"]}, + filterPolicyScope: MESSAGE_BODY, + rawMessageDelivery: true + }; + string|Error subsriptionArn = + amazonSNSClient->subscribe(topic, testEmail, EMAIL, attributes, true); + test:assertTrue(subsriptionArn is Error, "Expected error."); + test:assertEquals((subsriptionArn).message(), "Invalid parameter: Attributes Reason: Delivery protocol [email] does not support raw message delivery."); +} + +@test:Config { + groups: ["subscribe"], + enable: false +} +// Unable to write a test for this scenario +function confirmSubscriptionTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic3"); + _ = check amazonSNSClient->subscribe(topic, testEmail, EMAIL); + string token = "token"; + string subsriptionArn = check amazonSNSClient->confirmSubscription(topic, token); + test:assertTrue(isArn(subsriptionArn), "Returned value is not an ARN."); +} + +@test:Config { + groups: ["subscribe"], + enable: true +} +function confirmSubscriptionWithInvalidTokenTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic4"); + _ = check amazonSNSClient->subscribe(topic, testEmail, EMAIL); + string token = "invalidToken"; + string|error subsriptionArn = amazonSNSClient->confirmSubscription(topic, token); + test:assertTrue(subsriptionArn is OperationError, "Expected error."); + test:assertEquals((subsriptionArn).message(), "Invalid token"); +} + +@test:Config { + groups: ["subscribe"] +} +function listSubscriptionsTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic6"); + string subscriptionArn = + check amazonSNSClient->subscribe(topic, testPhoneNumber, SMS); + + stream subscriptionsStream = amazonSNSClient->listSubscriptions(); + Subscription[] subscriptions = check from Subscription subscription in subscriptionsStream + select subscription; + + string[] subscriptionArns = from Subscription subscription in subscriptions + select subscription.subscriptionArn; + + // Validate newly created subscription + Subscription[] retrievedSubscription = from Subscription subscription in subscriptions + where subscription.subscriptionArn == subscriptionArn + limit 1 + select subscription; + test:assertEquals(retrievedSubscription.length(), 1, "Subscription not found in the list."); + test:assertEquals(retrievedSubscription[0].endpoint, testPhoneNumber); + test:assertEquals(retrievedSubscription[0].protocol, SMS); + test:assertEquals(retrievedSubscription[0].topicArn, topic); + test:assertEquals(retrievedSubscription[0].owner.length(), 12); + + test:assertTrue(subscriptions.length() > 100, "There should be over 100 subscriptions."); + + // Ensure there are no duplicates + foreach string subscriptionArn1 in subscriptionArns { + if (subscriptionArn1 == "PendingConfirmation") { + continue; + } + + test:assertEquals(subscriptionArns.indexOf(subscriptionArn1), subscriptionArns.lastIndexOf(subscriptionArn1), + "Subscription " + subscriptionArn1 + " duplicated in the list."); + } +} + +@test:Config { + groups: ["subscribe"] +} +function listSubscriptionsByTopicTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic7"); + string subscriptionArn = check amazonSNSClient->subscribe(topic, testPhoneNumber, SMS); + _ = check amazonSNSClient->subscribe(topic, testHttp, HTTP); + _ = check amazonSNSClient->subscribe(topic, testHttps, HTTPS); + _ = check amazonSNSClient->subscribe(topic, testEmail, EMAIL); + + stream subscriptionsStream = amazonSNSClient->listSubscriptions(topic); + Subscription[] subscriptions = check from Subscription subscription in subscriptionsStream + order by subscription.protocol + select subscription; + + test:assertEquals(subscriptions.length(), 4); + + test:assertEquals(subscriptions[0].subscriptionArn, "PendingConfirmation"); + test:assertEquals(subscriptions[0].endpoint, testEmail); + test:assertEquals(subscriptions[0].protocol, EMAIL); + test:assertEquals(subscriptions[0].topicArn, topic); + test:assertEquals(subscriptions[0].owner.length(), 12); + + test:assertEquals(subscriptions[1].subscriptionArn, "PendingConfirmation"); + test:assertEquals(subscriptions[1].endpoint, testHttp); + test:assertEquals(subscriptions[1].protocol, HTTP); + test:assertEquals(subscriptions[1].topicArn, topic); + test:assertEquals(subscriptions[1].owner.length(), 12); + + test:assertEquals(subscriptions[2].subscriptionArn, "PendingConfirmation"); + test:assertEquals(subscriptions[2].endpoint, testHttps); + test:assertEquals(subscriptions[2].protocol, HTTPS); + test:assertEquals(subscriptions[2].topicArn, topic); + test:assertEquals(subscriptions[2].owner.length(), 12); + + test:assertEquals(subscriptions[3].subscriptionArn, subscriptionArn); + test:assertEquals(subscriptions[3].endpoint, testPhoneNumber); + test:assertEquals(subscriptions[3].protocol, SMS); + test:assertEquals(subscriptions[3].topicArn, topic); + test:assertEquals(subscriptions[3].owner.length(), 12); +} + +@test:Config { + groups: ["subscribe"] +} +function listSubscriptionsByTopicEmptyTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic8"); + + stream subscriptionsStream = amazonSNSClient->listSubscriptions(topic); + Subscription[] subscriptions = check from Subscription subscription in subscriptionsStream + select subscription; + test:assertEquals(subscriptions.length(), 0); +} + +@test:Config { + groups: ["subscribe"] +} +function listSubscriptionsByTopicDoesNotExist() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic9"); + _ = check amazonSNSClient->deleteTopic(topic); + + stream subscriptionsStream = amazonSNSClient->listSubscriptions(topic); + Subscription[]|Error? e = from Subscription subscription in subscriptionsStream + select subscription; + test:assertTrue(e is OperationError, "Expected error."); + test:assertEquals((e).message(), "Topic does not exist"); +} + +@test:Config { + groups: ["subscribe"] +} +function getSubscriptionAttributesTest1() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic5"); + string subscription = check amazonSNSClient->subscribe(topic, testEmail, EMAIL, returnSubscriptionArn = true); + + GettableSubscriptionAttributes attributes = check amazonSNSClient->getSubscriptionAttributes(subscription); + test:assertEquals(attributes.subscriptionArn, subscription); + test:assertEquals(attributes.endpoint, testEmail); + test:assertEquals(attributes.protocol, EMAIL); + test:assertEquals(attributes.topicArn, topic); + test:assertTrue(isArn(attributes.subscriptionPrincipal)); + test:assertEquals(attributes.confirmationWasAuthenticated, false); + test:assertEquals(attributes.pendingConfirmation, true); + test:assertEquals(attributes.rawMessageDelivery, false); + test:assertEquals(attributes.owner.length(), 12); +} + +@test:Config { + groups: ["subscribe"] +} +function getSubscriptionAttributesTest2() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic5"); + + SubscriptionAttributes setAttributes = { + deliveryPolicy: {healthyRetryPolicy: {numRetries: 3, minDelayTarget: 5, maxDelayTarget: 10}}, + filterPolicy: {store: ["example_corp"]}, + filterPolicyScope: MESSAGE_BODY, + //TODO: test redrive policy and subscription role ARN + rawMessageDelivery: false + }; + string subscription = check amazonSNSClient->subscribe(topic, testHttp, HTTP, setAttributes, true); + + GettableSubscriptionAttributes attributes = check amazonSNSClient->getSubscriptionAttributes(subscription); + test:assertEquals(attributes.subscriptionArn, subscription); + test:assertEquals(attributes.endpoint, testHttp); + test:assertEquals(attributes.protocol, HTTP); + test:assertEquals(attributes.topicArn, topic); + test:assertTrue(isArn(attributes.subscriptionPrincipal)); + test:assertEquals(attributes.confirmationWasAuthenticated, false); + test:assertEquals(attributes.pendingConfirmation, true); + test:assertEquals(attributes.rawMessageDelivery, false); + test:assertEquals(attributes.owner.length(), 12); + test:assertFalse(attributes?.deliveryPolicy is ()); + test:assertEquals(attributes?.filterPolicy, setAttributes?.filterPolicy); + test:assertEquals(attributes.filterPolicyScope, setAttributes.filterPolicyScope); +} + +@test:Config { + groups: ["subscribex"] +} +function setSubscriptionAttributesTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic10"); + string subscription = check amazonSNSClient->subscribe(topic, testHttp, HTTP, returnSubscriptionArn = true); + + _ = check amazonSNSClient->setSubscriptionAttributes(subscription, DELIVERY_POLICY, + {healthyRetryPolicy: {numRetries: 3, minDelayTarget: 5, maxDelayTarget: 10}}); + _ = check amazonSNSClient->setSubscriptionAttributes(subscription, FILTER_POLICY, {store: ["example_corp"]}); + _ = check amazonSNSClient->setSubscriptionAttributes(subscription, FILTER_POLICY_SCOPE, MESSAGE_BODY); + _ = check amazonSNSClient->setSubscriptionAttributes(subscription, RAW_MESSAGE_DELIVERY, true); + + GettableSubscriptionAttributes attributes = check amazonSNSClient->getSubscriptionAttributes(subscription); + test:assertEquals(attributes.subscriptionArn, subscription); + test:assertEquals(attributes.endpoint, testHttp); + test:assertEquals(attributes.protocol, HTTP); + test:assertEquals(attributes.topicArn, topic); + test:assertTrue(isArn(attributes.subscriptionPrincipal)); + test:assertEquals(attributes.confirmationWasAuthenticated, false); + test:assertEquals(attributes.pendingConfirmation, true); + test:assertEquals(attributes.rawMessageDelivery, true); + test:assertEquals(attributes.owner.length(), 12); + test:assertFalse(attributes?.deliveryPolicy is ()); + test:assertEquals(attributes?.filterPolicy, {store: ["example_corp"]}); + test:assertEquals(attributes.filterPolicyScope, MESSAGE_BODY); +}; + +@test:Config { + groups: ["subscribex"] +} +function setSubscriptionAttributesWithInvalidTypes() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic10"); + string subscription = check amazonSNSClient->subscribe(topic, testHttp, HTTP, returnSubscriptionArn = true); + + Error? e = amazonSNSClient->setSubscriptionAttributes(subscription, RAW_MESSAGE_DELIVERY, "invalid"); + test:assertTrue(e is Error, "Expected error."); + test:assertEquals((e).message(), "The raw message delivery must be of type boolean."); + + e = amazonSNSClient->setSubscriptionAttributes(subscription, FILTER_POLICY_SCOPE, "invalid"); + test:assertTrue(e is Error, "Expected error."); + test:assertEquals((e).message(), "The filter policy scope must be of type FilterPolicyScope."); + + e = amazonSNSClient->setSubscriptionAttributes(subscription, SUBSCRIPTION_ROLE_ARN, false); + test:assertTrue(e is Error, "Expected error."); + test:assertEquals((e).message(), "The subscription role ARN must be of type string."); +}; + +@test:Config { + groups: ["subscribex"] +} +function setSubscriptionAttributesTestNegative() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic10"); + string subscription = check amazonSNSClient->subscribe(topic, testPhoneNumber, SMS, returnSubscriptionArn = true); + + Error? e = amazonSNSClient->setSubscriptionAttributes(subscription, RAW_MESSAGE_DELIVERY, true); + test:assertTrue(e is OperationError, "Expected error."); + test:assertEquals((e).message(), "Invalid parameter: Delivery protocol [sms] does not support raw message delivery."); +}; + +@test:Config { + groups: ["subscribe"] +} +function unsubscribeTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic11"); + string subscription = check amazonSNSClient->subscribe(topic, testPhoneNumber, SMS, returnSubscriptionArn = true); + string[] subscriptions = check from Subscription subscripion in amazonSNSClient->listSubscriptions(topic) + select subscripion.subscriptionArn; + test:assertTrue(subscriptions.indexOf(subscription) != ()); + + _ = check amazonSNSClient->unsubscribe(subscription); + subscriptions = check from Subscription subscripion in amazonSNSClient->listSubscriptions(topic) + select subscripion.subscriptionArn; + test:assertTrue(subscriptions.indexOf(subscription) is ()); +} + +@test:Config { + groups: ["subscribe"] +} +function unsubscribeWithInvalidArnTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "SubscribeTopic13"); + string subscription = check amazonSNSClient->subscribe(topic, testEmail, EMAIL); // Valid ARN is not returned + + Error? e = amazonSNSClient->unsubscribe(subscription); + test:assertTrue(e is OperationError, "Expected error."); + test:assertEquals((e).message(), "Invalid parameter: SubscriptionArn Reason: An ARN must have at least 6 elements, not 1"); +} + +@test:Config { + groups: ["subscribe"] +} +function usubscribeUnauthorizedTest() returns error? { + Error? e = amazonSNSClient->unsubscribe("arn:aws:sns:us-east-1:invalid:2023-10-11T103913903939ZSubscribeTopic12:45f03920-f890-4a2f-bd77-32f9689b8013"); + test:assertTrue(e is OperationError, "Expected error."); + test:assertTrue((e).message().includes("is not authorized to perform: SNS:Unsubscribe on resource")); +} diff --git a/ballerina/tests/tag-tests.bal b/ballerina/tests/tag-tests.bal new file mode 100644 index 0000000..314af8f --- /dev/null +++ b/ballerina/tests/tag-tests.bal @@ -0,0 +1,156 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +@test:Config { + groups: ["tag"] +} +function tagResourceInlineTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic"); + check amazonSNSClient->tagResource(topic, testKey = "testValue", hello = "world"); +} + +@test:Config { + groups: ["tag"] +} +function tagResourceRecordTest() returns error? { + Tags tags = { + "testKey": "testValue", + "hello": "world" + }; + + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic2"); + check amazonSNSClient->tagResource(topic, tags); +} + +@test:Config { + groups: ["tag"] +} +function tagResourceEmptyTest1() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic3"); + Error? e = amazonSNSClient->tagResource(topic, {}); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "At least one tag must be specified."); +} + +@test:Config { + groups: ["tag"] +} +function tagResourceEmptyTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic4"); + Error? e = amazonSNSClient->tagResource(topic); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "At least one tag must be specified."); +} + +@test:Config { + groups: ["tag"] +} +function tagResourceTooLongTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic5"); + Error? e = amazonSNSClient->tagResource(topic, tag1hkfhksdhfkjhdsfkhskfhdskjfbdskjfhsdkfhsdkfhdsjkhfkjdshfkdshfkjdshfksdhkfshdkfhdskjfhsdkfhsdkjfhdkjsfhskdjhfkjsdhfkjdshfkjsdhfkjsdhfkjhsdkfjhsdkjfsdkjhfkj = "testTag"); + test:assertTrue(e is Error, "Error expected."); + test:assertTrue((e).message().endsWith("Member must have length less than or equal to 128")); +} + +@test:Config { + groups: ["tag"] +} +function tagResourceTooManyTest() returns error? { + Tags tags = {"tag1": "value1", "tag2": "value2", "tag3": "value3", "tag4": "value4", "tag5": "value5", "tag6": "value6", "tag7": "value7", "tag8": "value8", "tag9": "value9", "tag10": "value10", "tag11": "value11", "tag12": "value12", "tag13": "value13", "tag14": "value14", "tag15": "value15", "tag16": "value16", "tag17": "value17", "tag18": "value18", "tag19": "value19", "tag20": "value20", "tag21": "value21", "tag22": "value22", "tag23": "value23", "tag24": "value24", "tag25": "value25", "tag26": "value26", "tag27": "value27", "tag28": "value28", "tag29": "value29", "tag30": "value30", "tag31": "value31", "tag32": "value32", "tag33": "value33", "tag34": "value34", "tag35": "value35", "tag36": "value36", "tag37": "value37", "tag38": "value38", "tag39": "value39", "tag40": "value40", "tag41": "value41", "tag42": "value42", "tag43": "value43", "tag44": "value44", "tag45": "value45", "tag46": "value46", "tag47": "value47", "tag48": "value48", "tag49": "value49", "tag50": "value50", "tag51": "value51"}; + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic6"); + Error? e = amazonSNSClient->tagResource(topic, tags); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "Could not complete request: tag quota of per resource exceeded"); +} + +@test:Config { + groups: ["tag"] +} +function tagResourceDoesNotExistTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic7"); + Error? e = amazonSNSClient->tagResource(topic + "x", tag1 = "tag1"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "Resource does not exist"); +} + +@test:Config { + groups: ["tag"] +} +function listTagsTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic8"); + check amazonSNSClient->tagResource(topic, testKey = "testValue", hello = "world"); + + Tags tags = check amazonSNSClient->listTags(topic); + test:assertEquals(tags, {"testKey": "testValue", "hello": "world"}); +} + +@test:Config { + groups: ["tag"] +} +function listTagsDoesNotExistTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic9"); + Tags|Error e = amazonSNSClient->listTags(topic + "x"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "Resource does not exist"); +} + +@test:Config { + groups: ["tag"] +} +function listTagsEmptyTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic10"); + Tags tags = check amazonSNSClient->listTags(topic); + test:assertEquals(tags, {}); +} + +@test:Config { + groups: ["tag"] +} +function untagResourceTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic11"); + check amazonSNSClient->tagResource(topic, testKey = "testValue", hello = "world"); + check amazonSNSClient->untagResource(topic, ["testKey"]); + + Tags tags = check amazonSNSClient->listTags(topic); + test:assertEquals(tags, {"hello": "world"}); +} + +@test:Config { + groups: ["tag"] +} +// UntagResource API does not throw an error is the tag does not exist. +function untagResourceInvalidTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic11"); + check amazonSNSClient->tagResource(topic, testKey = "testValue", hello = "world"); + check amazonSNSClient->untagResource(topic, ["invalidKey"]); + + Tags tags = check amazonSNSClient->listTags(topic); + test:assertEquals(tags, {"testKey": "testValue", "hello": "world"}); +} + +@test:Config { + groups: ["tagx"] +} +function untagResourceEmptyTest() returns error? { + string topic = check amazonSNSClient->createTopic(testRunId + "testTagsTopic11"); + check amazonSNSClient->tagResource(topic, testKey = "testValue", hello = "world"); + + Error? e = amazonSNSClient->untagResource(topic, []); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "At least one tag key must be specified."); +} diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal deleted file mode 100644 index ca575e2..0000000 --- a/ballerina/tests/test.bal +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. -// -// WSO2 Inc. 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. - -import ballerina/test; -import ballerina/os; -import ballerina/log; - -configurable string accessKeyId = os:getEnv("ACCESS_KEY_ID"); -configurable string secretAccessKey = os:getEnv("SECRET_ACCESS_KEY"); -configurable string region = os:getEnv("REGION"); - -string testTopic = "TestTopic"; -string topicArn = ""; -string subscriptionArn = ""; - -AwsCredentials awsCredentials = { - accessKeyId: accessKeyId, - secretAccessKey: secretAccessKey -}; - -ConnectionConfig config = { - credentials: awsCredentials, - region: region -}; - -Client amazonSNSClient = check new(config); - -@test:Config{} -function testCreateTopic() returns error? { - TopicAttributes attributes = { - "displayName" : "Test" - }; - CreateTopicResponse response = check amazonSNSClient->createTopic(testTopic, attributes); - topicArn = response.createTopicResult.topicArn.toString(); -} - -@test:Config{dependsOn: [testCreateTopic]} -function testSubscribe() returns error? { - SubscribeResponse response = check amazonSNSClient->subscribe(topicArn, SMS, "+94776718102", true); - subscriptionArn = response.subscribeResult.subscriptionArn.toString(); - log:printInfo(response.toString()); -} - -@test:Config{dependsOn: [testSubscribe]} -function testPublish() returns error? { - PublishResponse response = check amazonSNSClient->publish("Notification Message", topicArn); - log:printInfo(response.toString()); -} - -@test:Config{dependsOn: [testPublish]} -function testGetSMSAttributes() returns error? { - GetSMSAttributesResponse response = check amazonSNSClient->getSMSAttributes(); - log:printInfo(response.toString()); -} - -@test:Config{dependsOn: [testGetSMSAttributes]} -function testGetTopicAttributes() returns error? { - GetTopicAttributesResponse response = check amazonSNSClient->getTopicAttributes(topicArn); - log:printInfo(response.toString()); -} - -@test:Config{dependsOn: [testGetTopicAttributes]} -function testGetSubscriptionAttributes() returns error? { - GetSubscriptionAttributesResponse response = check amazonSNSClient->getSubscriptionAttributes(subscriptionArn); - log:printInfo(response.toString()); -} - -@test:Config{dependsOn: [testGetSubscriptionAttributes]} -function testUnsubscribe() returns error? { - UnsubscribeResponse response = check amazonSNSClient->unsubscribe(subscriptionArn); - log:printInfo(response.toString()); -} - -@test:AfterSuite {} -function testDeleteTopic() returns error? { - DeleteTopicResponse response = check amazonSNSClient->deleteTopic(topicArn); - log:printInfo(response.toString()); -} \ No newline at end of file diff --git a/ballerina/tests/topics-tests.bal b/ballerina/tests/topics-tests.bal new file mode 100644 index 0000000..ac45db8 --- /dev/null +++ b/ballerina/tests/topics-tests.bal @@ -0,0 +1,607 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +import ballerina/test; + +json validPolicy = {"Version": "2008-10-17", "Id": "__default_policy_ID", "Statement": [{"Sid": "__default_statement_ID", "Effect": "Allow", "Principal": {"AWS": "*"}, "Action": ["SNS:Publish", "SNS:RemovePermission", "SNS:SetTopicAttributes", "SNS:DeleteTopic", "SNS:ListSubscriptionsByTopic", "SNS:GetTopicAttributes", "SNS:AddPermission", "SNS:Subscribe"], "Resource": "", "Condition": {"StringEquals": {"AWS:SourceOwner": "482724125666"}}}, {"Sid": "__console_sub_0", "Effect": "Allow", "Principal": {"AWS": "*"}, "Action": "SNS:Subscribe", "Resource": ""}]}; +json invalidPolicy = {"Version": "2008-10-17", "Id": "__default_policy_ID", "Statement": [{"Sid": "__default_statement_ID", "Effect": "Allow", "Principal": {"AWS": "*"}, "Action": ["SNS:Publishx", "SNS:RemovePermission", "SNS:SetTopicAttributes", "SNS:DeleteTopic", "SNS:ListSubscriptionsByTopic", "SNS:GetTopicAttributes", "SNS:AddPermission", "SNS:Subscribe"], "Resource": "", "Condition": {"StringEquals": {"AWS:SourceOwner": "482724125666"}}}, {"Sid": "__console_sub_0", "Effect": "Allow", "Principal": {"AWS": "*"}, "Action": ["SNS:Subscribe"], "Resource": ""}]}; + +record {} validDeliveryPolicy = { + "http": { + defaultHealthyRetryPolicy: { + minDelayTarget: 10, + maxDelayTarget: 20, + numRetries: 3, + numNoDelayRetries: 1, + numMinDelayRetries: 1, + numMaxDelayRetries: 1, + backoffFunction: LINEAR + }, + disableSubscriptionOverrides: true, + defaultRequestPolicy: { + headerContentType: APPLICATION_JSON + } + } +}; + +record {} invalidDeliveryPolicy = { + "http": { + defaultHealthyRetryPolicy: { + minDelayTarget: 10, + maxDelayTarget: 20, + numRetries: 300, + numNoDelayRetries: 1, + numMinDelayRetries: 1, + numMaxDelayRetries: 1, + backoffFunction: LINEAR + }, + disableSubscriptionOverrides: true, + defaultRequestPolicy: { + headerContentType: APPLICATION_JSON + } + } +}; + +json validDataProtectionPolicy = {"Name": "basicPII-protection", "Description": "Protect basic types of sensitive data", "Version": "2021-06-01", "Statement": [{"Sid": "basicPII-inbound-protection", "DataDirection": "Inbound", "Principal": ["*"], "DataIdentifier": ["arn:aws:dataprotection::aws:data-identifier/Name", "arn:aws:dataprotection::aws:data-identifier/PhoneNumber-US"], "Operation": {"Deny": {}}}]}; +json invalidDataProtectionPolicy = {"Name": "basicPII-protection", "Description": "Protect basic types of sensitive data", "Version": "2021-06-01", "Statement": [{"Sid": "basicPII-inbound-protection", "DataDirection": "Inbxound", "Principal": ["*"], "DataIdentifier": ["arn:aws:dataprotection::aws:data-identifier/Name", "arn:aws:dataprotection::aws:data-identifier/PhoneNumber-US"], "Operation": {"Deny": {}}}]}; + +@test:Config { + groups: ["topics"] +} +function createTopicBasicTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "FirstTopic"); + test:assertNotEquals(topicArn, "", "Topic ARN should not be empty."); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithInvalidNameTest() returns error? { + string|Error response = amazonSNSClient->createTopic(testRunId + "@TopicWithInvalidCharacters#"); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "Invalid parameter: Topic Name"); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithAttributesTest() returns error? { + InitializableTopicAttributes attributes = { + deliveryPolicy: validDeliveryPolicy.toJson(), + displayName: "Test4", + fifoTopic: true, + signatureVersion: SignatureVersion1, + policy: validPolicy, + tracingConfig: ACTIVE, + kmsMasterKeyId: "testxyz", + contentBasedDeduplication: false, + httpMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + lambdaMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + firehoseMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + applicationMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + sqsMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + } + }; + + string topicArn = check amazonSNSClient->createTopic(testRunId + "TopicWithAttributes.fifo", attributes); + test:assertNotEquals(topicArn, "", "Topic ARN should not be empty."); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithInvalidAttributesTest() returns error? { + InitializableTopicAttributes attributes = { + deliveryPolicy: validDeliveryPolicy.toJson(), + displayName: "Test4", + fifoTopic: true, + signatureVersion: SignatureVersion1, + policy: validPolicy, + tracingConfig: ACTIVE, + kmsMasterKeyId: "testxyz", + contentBasedDeduplication: false, + httpMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + lambdaMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + firehoseMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + applicationMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 5 + }, + sqsMessageDeliveryLogging: { + successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", + failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", + successFeedbackSampleRate: 500 + } + }; + + string|Error topicArn = amazonSNSClient->createTopic(testRunId + "TopicWithAttributes.fifo", attributes); + test:assertTrue(topicArn is Error, "Error expected."); + test:assertEquals((topicArn).message(), "Invalid parameter: Attributes Reason: SQSSuccessFeedbackSampleRate: 500 value provided is not an integer between 0-100"); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithInvalidDeliveryPolicyTest() returns error? { + InitializableTopicAttributes attributes = { + deliveryPolicy: invalidDeliveryPolicy.toJson(), + displayName: "Test4", + fifoTopic: true, + signatureVersion: SignatureVersion1, + policy: validPolicy, + tracingConfig: ACTIVE, + kmsMasterKeyId: "testxyz", + contentBasedDeduplication: false + }; + + string|error response = amazonSNSClient->createTopic(testRunId + "TopicWithInvalidDeliveryPolicy.fifo", attributes); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "Invalid parameter: Attributes Reason: DeliveryPolicy: http.defaultHealthyRetryPolicy.numRetries must be less than or equal to 100"); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithInvalidPolicy() returns error? { + InitializableTopicAttributes attributes = { + deliveryPolicy: validDeliveryPolicy.toJson(), + displayName: "Test4", + fifoTopic: true, + signatureVersion: SignatureVersion1, + policy: invalidPolicy, + tracingConfig: ACTIVE, + kmsMasterKeyId: "testxyz", + contentBasedDeduplication: false + }; + + string|error response = amazonSNSClient->createTopic(testRunId + "TopicWithInvalidPolicy.fifo", attributes); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "Invalid parameter: Attributes Reason: Policy statement action out of service scope!"); +} + +@test:Config { + groups: ["topics"] +} +function createTopicAlreadyExistsWithDifferentAttributesTest() returns error? { + InitializableTopicAttributes attributes = { + deliveryPolicy: validDeliveryPolicy.toJson(), + displayName: "Test4", + fifoTopic: true, + signatureVersion: SignatureVersion1, + policy: validPolicy, + tracingConfig: ACTIVE, + kmsMasterKeyId: "testxyz", + contentBasedDeduplication: false + }; + _ = check amazonSNSClient->createTopic(testRunId + "TopicAlreadyExists.fifo", attributes); + + attributes.displayName = "Test5"; + string|Error response = amazonSNSClient->createTopic(testRunId + "TopicAlreadyExists.fifo", attributes); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "Invalid parameter: Attributes Reason: Topic already exists with different attributes"); +} + +@test:Config { + groups: ["topics"] +} +function createFifoTopicWithoutSuffix() returns error? { + InitializableTopicAttributes attributes = { + fifoTopic: true + }; + + string topicArn = check amazonSNSClient->createTopic(testRunId + "FifoTopicWithoutSuffix", attributes); + test:assertNotEquals(topicArn, "", "Topic ARN should not be empty."); +} + +@test:Config { + groups: ["topics"] +} +function createStandardTopicWithContentBasedDeduplicationEnabled() returns error? { + InitializableTopicAttributes attributes = { + fifoTopic: false, + contentBasedDeduplication: true + }; + + string|error response = amazonSNSClient->createTopic(testRunId + "StandardTopicWithContentBasedDeduplicationEnabled", attributes); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "If content-based deduplication is enabled, it must also be a FIFO topic."); +} + +@test:Config { + groups: ["topics"] +} +function createStandardTopicWithContentBasedDeduplicationEnabled2() returns error? { + InitializableTopicAttributes attributes = { + contentBasedDeduplication: true + }; + + string|error response = amazonSNSClient->createTopic(testRunId + "StandardTopicWithContentBasedDeduplicationEnabled", attributes); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "If content-based deduplication is enabled, it must also be a FIFO topic."); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithDataProtectionPolicy() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "TopicWithDataProtectionPolicy", + dataProtectionPolicy = validDataProtectionPolicy); + test:assertNotEquals(topicArn, "", "Topic ARN should not be empty."); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithInvalidDataProtectionPolicy() returns error? { + string|error response = amazonSNSClient->createTopic(testRunId + "TopicWithInvalidDataProtectionPolicy", + dataProtectionPolicy = invalidDataProtectionPolicy); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "Invalid parameter: DataProtectionPolicy Reason: Statement DataDirection must be either Inbound or Outbound"); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithTags() returns error? { + map tags = {"tag1": "value1", "tag2": "value2"}; + string topicArn = check amazonSNSClient->createTopic(testRunId + "TopicWithTags", tags = tags); + test:assertNotEquals(topicArn, "", "Topic ARN should not be empty."); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithTagsNegative1() returns error? { + map tags = {"tag1hkfhksdhfkjhdsfkhskfhdskjfbdskjfhsdkfhsdkfhdsjkhfkjdshfkdshfkjdshfksdhkfshdkfhdskjfhsdkfhsdkjfhdkjsfhskdjhfkjsdhfkjdshfkjsdhfkjsdhfkjhsdkfjhsdkjfsdkjhfkj": "value1", "tag2": "value2"}; + string|error response = amazonSNSClient->createTopic(testRunId + "TopicWithTagsNegative1", tags = tags); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "1 validation error detected: Value 'tag1hkfhksdhfkjhdsfkhskfhdskjfbdskjfhsdkfhsdkfhdsjkhfkjdshfkdshfkjdshfksdhkfshdkfhdskjfhsdkfhsdkjfhdkjsfhskdjhfkjsdhfkjdshfkjsdhfkjsdhfkjhsdkfjhsdkjfsdkjhfkj' at 'tags.1.member.key' failed to satisfy constraint: Member must have length less than or equal to 128"); +} + +@test:Config { + groups: ["topics"] +} +function createTopicWithTagsNegative2() returns error? { + map tags = {"tag1": "value1", "tag2": "value2", "tag3": "value3", "tag4": "value4", "tag5": "value5", "tag6": "value6", "tag7": "value7", "tag8": "value8", "tag9": "value9", "tag10": "value10", "tag11": "value11", "tag12": "value12", "tag13": "value13", "tag14": "value14", "tag15": "value15", "tag16": "value16", "tag17": "value17", "tag18": "value18", "tag19": "value19", "tag20": "value20", "tag21": "value21", "tag22": "value22", "tag23": "value23", "tag24": "value24", "tag25": "value25", "tag26": "value26", "tag27": "value27", "tag28": "value28", "tag29": "value29", "tag30": "value30", "tag31": "value31", "tag32": "value32", "tag33": "value33", "tag34": "value34", "tag35": "value35", "tag36": "value36", "tag37": "value37", "tag38": "value38", "tag39": "value39", "tag40": "value40", "tag41": "value41", "tag42": "value42", "tag43": "value43", "tag44": "value44", "tag45": "value45", "tag46": "value46", "tag47": "value47", "tag48": "value48", "tag49": "value49", "tag50": "value50", "tag51": "value51"}; + string|error response = amazonSNSClient->createTopic(testRunId + "TopicWithTagsNegative2", tags = tags); + test:assertTrue(response is Error, "Error expected."); + test:assertEquals((response).message(), "Could not complete request: tag quota of per resource exceeded"); +} + +@test:Config { + groups: ["topics"] +} +function listTopicsTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "ListTopicsTest"); + + stream topicsStream = amazonSNSClient->listTopics(); + string[] topics = check from string topic in topicsStream + select topic; + + test:assertTrue(topics.indexOf(topicArn) is int, topicArn + " not found in the list."); + test:assertTrue(topics.length() > 100, "There should be over 100 topics."); + + // Ensure there are no duplicates + foreach string topic in topics { + test:assertEquals(topics.indexOf(topic), topics.lastIndexOf(topic), + "Topic " + topic + " duplicated in the list."); + } +} + +@test:Config { + groups: ["topics"] +} +function deleteTopicTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "TopicToDelete"); + _ = check amazonSNSClient->deleteTopic(topicArn); +} + +@test:Config { + groups: ["topics"] +} +function deleteTopicWithInvalidArnTest() returns error? { + Error? e = amazonSNSClient->deleteTopic(testRunId + "Invalid:Topic:Arn"); + test:assertTrue(e is OperationError, "Error expected."); + test:assertEquals((e).message(), "Invalid parameter: TopicArn Reason: An ARN must have at least 6 elements, not 3"); +} + +@test:Config { + groups: ["topics"] +} +function deleteTopicWithArnThatDoesNotExistTest() returns error? { + // This action is idempotent, so deleting a topic that does not exist does not result in an error. + _ = check amazonSNSClient->deleteTopic("arn:aws:sns:us-east-1:482724125666:2023-10-03T102648743022ZArnDoesNotExist"); +} + +@test:Config { + groups: ["topics"] +} +function getTopicAttributesTest1() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "TopicToRetrieve1"); + GettableTopicAttributes attributes = check amazonSNSClient->getTopicAttributes(topicArn); + + test:assertEquals(attributes.topicArn, topicArn); + test:assertEquals(attributes.displayName, ""); + test:assertNotEquals(attributes.effectiveDeliveryPolicy, {}); + test:assertNotEquals(attributes.owner, ""); + test:assertNotEquals(attributes.policy, {}); + test:assertEquals(attributes.subscriptionsConfirmed, 0); + test:assertEquals(attributes.subscriptionsDeleted, 0); + test:assertEquals(attributes.subscriptionsPending, 0); +} + +@test:Config { + groups: ["topics"] +} +function getTopicAttributesTest2() returns error? { + InitializableTopicAttributes setAttributes = { + deliveryPolicy: validDeliveryPolicy.toJson(), + displayName: "Test4", + fifoTopic: true, + signatureVersion: SignatureVersion1, + policy: validPolicy, + tracingConfig: ACTIVE, + kmsMasterKeyId: "testxyz", + contentBasedDeduplication: false + }; + string topicArn = check amazonSNSClient->createTopic(testRunId + "TopicToRetrieve2", attributes = setAttributes, + tags = {"tag1": "value1", "tag2": "value2"}); + + GettableTopicAttributes attributes = check amazonSNSClient->getTopicAttributes(topicArn); + test:assertEquals(attributes.topicArn, topicArn); + test:assertEquals(attributes.displayName, setAttributes.displayName); + test:assertNotEquals(attributes.effectiveDeliveryPolicy, {}); + test:assertNotEquals(attributes.owner, ""); + test:assertEquals(attributes.policy, validPolicy); + test:assertEquals(attributes.subscriptionsConfirmed, 0); + test:assertEquals(attributes.subscriptionsDeleted, 0); + test:assertEquals(attributes.subscriptionsPending, 0); + test:assertEquals(attributes?.deliveryPolicy, validDeliveryPolicy.toJson()); + test:assertEquals(attributes?.fifoTopic, true); + test:assertEquals(attributes?.signatureVersion, SignatureVersion1); + test:assertEquals(attributes?.tracingConfig, ACTIVE); + test:assertEquals(attributes?.kmsMasterKeyId, "testxyz"); + test:assertEquals(attributes?.contentBasedDeduplication, false); +} + +@test:Config { + groups: ["topics"] +} +function setTopicAttributesTest1() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "SetTopicAttributes1", { + fifoTopic: true, + contentBasedDeduplication: true + }); + GettableTopicAttributes attributes = check amazonSNSClient->getTopicAttributes(topicArn); + test:assertEquals(attributes.topicArn, topicArn); + test:assertEquals(attributes.displayName, ""); + test:assertNotEquals(attributes.effectiveDeliveryPolicy, {}); + test:assertNotEquals(attributes.owner, ""); + test:assertNotEquals(attributes.policy, {}); + test:assertEquals(attributes.subscriptionsConfirmed, 0); + test:assertEquals(attributes.subscriptionsDeleted, 0); + test:assertEquals(attributes.subscriptionsPending, 0); + test:assertEquals(attributes.fifoTopic, true); + test:assertEquals(attributes.contentBasedDeduplication, true); + + _ = check amazonSNSClient->setTopicAttributes(topicArn, CONTENT_BASED_DEDUPLICATION, false); + attributes = check amazonSNSClient->getTopicAttributes(topicArn); + test:assertEquals(attributes.topicArn, topicArn); + test:assertEquals(attributes.displayName, ""); + test:assertNotEquals(attributes.effectiveDeliveryPolicy, {}); + test:assertNotEquals(attributes.owner, ""); + test:assertNotEquals(attributes.policy, {}); + test:assertEquals(attributes.subscriptionsConfirmed, 0); + test:assertEquals(attributes.subscriptionsDeleted, 0); + test:assertEquals(attributes.subscriptionsPending, 0); + test:assertEquals(attributes.fifoTopic, true); + test:assertEquals(attributes.contentBasedDeduplication, false); +}; + +@test:Config { + groups: ["topics"] +} +function setTopicAttributesTest2() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "SetTopicAttributes2"); + + _ = check amazonSNSClient->setTopicAttributes(topicArn, DELIVERY_POLICY, validDeliveryPolicy.toJson()); + _ = check amazonSNSClient->setTopicAttributes(topicArn, DISPLAY_NAME, "Test4"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, SIGNATURE_VERSION, SignatureVersion1); + _ = check amazonSNSClient->setTopicAttributes(topicArn, POLICY, validPolicy); + _ = check amazonSNSClient->setTopicAttributes(topicArn, TRACING_CONFIG, ACTIVE); + _ = check amazonSNSClient->setTopicAttributes(topicArn, KMS_MASTER_KEY_ID, "testxyz"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, HTTP_SUCCESS_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSSuccessFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, HTTP_FAILURE_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSFailureFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, HTTP_SUCCESS_FEEDBACK_SAMPLE_RATE, 5); + _ = check amazonSNSClient->setTopicAttributes(topicArn, LAMBDA_SUCCESS_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSSuccessFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, LAMBDA_FAILURE_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSFailureFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, LAMBDA_SUCCESS_FEEDBACK_SAMPLE_RATE, 5); + _ = check amazonSNSClient->setTopicAttributes(topicArn, FIREHOSE_SUCCESS_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSSuccessFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, FIREHOSE_FAILURE_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSFailureFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, FIREHOSE_SUCCESS_FEEDBACK_SAMPLE_RATE, 5); + _ = check amazonSNSClient->setTopicAttributes(topicArn, APPLICATION_SUCCESS_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSSuccessFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, APPLICATION_FAILURE_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSFailureFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, APPLICATION_SUCCESS_FEEDBACK_SAMPLE_RATE, 5); + _ = check amazonSNSClient->setTopicAttributes(topicArn, SQS_SUCCESS_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSSuccessFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, SQS_FAILURE_FEEDBACK_ROLE_ARN, "arn:aws:iam::482724125666:role/SNSFailureFeedback"); + _ = check amazonSNSClient->setTopicAttributes(topicArn, SQS_SUCCESS_FEEDBACK_SAMPLE_RATE, 5); + + GettableTopicAttributes attributes = check amazonSNSClient->getTopicAttributes(topicArn); + test:assertEquals(attributes.topicArn, topicArn); + test:assertEquals(attributes?.deliveryPolicy, validDeliveryPolicy.toJson()); + test:assertEquals(attributes.displayName, "Test4"); + test:assertEquals(attributes.signatureVersion, SignatureVersion1); + test:assertEquals(attributes.policy, validPolicy); + test:assertEquals(attributes.tracingConfig, ACTIVE); + test:assertEquals(attributes.kmsMasterKeyId, "testxyz"); + test:assertEquals(attributes.httpMessageDeliveryLogging, {successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", successFeedbackSampleRate: 5}); + test:assertEquals(attributes.lambdaMessageDeliveryLogging, {successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", successFeedbackSampleRate: 5}); + test:assertEquals(attributes.firehoseMessageDeliveryLogging, {successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", successFeedbackSampleRate: 5}); + test:assertEquals(attributes.applicationMessageDeliveryLogging, {successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", successFeedbackSampleRate: 5}); + test:assertEquals(attributes.sqsMessageDeliveryLogging, {successFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSSuccessFeedback", failureFeedbackRoleArn: "arn:aws:iam::482724125666:role/SNSFailureFeedback", successFeedbackSampleRate: 5}); +} + +@test:Config { + groups: ["topics"] +} +function setTopicAttributesNegativeTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "SetTopicAttributesNegative"); + GettableTopicAttributes attributes = check amazonSNSClient->getTopicAttributes(topicArn); + test:assertEquals(attributes.topicArn, topicArn); + test:assertEquals(attributes.displayName, ""); + test:assertNotEquals(attributes.effectiveDeliveryPolicy, {}); + test:assertNotEquals(attributes.owner, ""); + test:assertNotEquals(attributes.policy, {}); + test:assertEquals(attributes.subscriptionsConfirmed, 0); + test:assertEquals(attributes.subscriptionsDeleted, 0); + test:assertEquals(attributes.subscriptionsPending, 0); + + Error? e = amazonSNSClient->setTopicAttributes(topicArn, DELIVERY_POLICY, invalidDeliveryPolicy.toJson()); + test:assertTrue(e is OperationError, "Error expected."); + test:assertEquals((e).message(), "Invalid parameter: DeliveryPolicy: http.defaultHealthyRetryPolicy.numRetries must be less than or equal to 100"); +} + +@test:Config { + groups: ["topics"] +} +function setTopicAttributesInvalidTest() returns error? { + string topicArn = check amazonSNSClient->createTopic(testRunId + "SetTopicAttributesInvalid"); + GettableTopicAttributes attributes = check amazonSNSClient->getTopicAttributes(topicArn); + test:assertEquals(attributes.topicArn, topicArn); + test:assertEquals(attributes.displayName, ""); + test:assertNotEquals(attributes.effectiveDeliveryPolicy, {}); + test:assertNotEquals(attributes.owner, ""); + test:assertNotEquals(attributes.policy, {}); + test:assertEquals(attributes.subscriptionsConfirmed, 0); + test:assertEquals(attributes.subscriptionsDeleted, 0); + test:assertEquals(attributes.subscriptionsPending, 0); + + Error? e = amazonSNSClient->setTopicAttributes(topicArn, DISPLAY_NAME, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The display name must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, SIGNATURE_VERSION, "v1"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The signature version must be of type SignatureVersion."); + + e = amazonSNSClient->setTopicAttributes(topicArn, POLICY, "policy"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "Invalid parameter: Policy Error: null"); + + e = amazonSNSClient->setTopicAttributes(topicArn, TRACING_CONFIG, "invalid"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The tracing config must be of type TracingConfig."); + + e = amazonSNSClient->setTopicAttributes(topicArn, KMS_MASTER_KEY_ID, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The KMS master key ID must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, CONTENT_BASED_DEDUPLICATION, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The content-based deduplication must be of type boolean."); + + e = amazonSNSClient->setTopicAttributes(topicArn, HTTP_SUCCESS_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The HTTP success feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, HTTP_FAILURE_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The HTTP failure feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, HTTP_SUCCESS_FEEDBACK_SAMPLE_RATE, "1"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The HTTP success feedback sample rate must be of type int."); + + e = amazonSNSClient->setTopicAttributes(topicArn, LAMBDA_SUCCESS_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The Lambda success feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, LAMBDA_FAILURE_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The Lambda failure feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, LAMBDA_SUCCESS_FEEDBACK_SAMPLE_RATE, "1"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The Lambda success feedback sample rate must be of type int."); + + e = amazonSNSClient->setTopicAttributes(topicArn, FIREHOSE_SUCCESS_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The Firehose success feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, FIREHOSE_FAILURE_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The Firehose failure feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, FIREHOSE_SUCCESS_FEEDBACK_SAMPLE_RATE, "1"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The Firehose success feedback sample rate must be of type int."); + + e = amazonSNSClient->setTopicAttributes(topicArn, APPLICATION_SUCCESS_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The application success feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, APPLICATION_FAILURE_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The application failure feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, APPLICATION_SUCCESS_FEEDBACK_SAMPLE_RATE, "1"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The application success feedback sample rate must be of type int."); + + e = amazonSNSClient->setTopicAttributes(topicArn, SQS_SUCCESS_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The SQS success feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, SQS_FAILURE_FEEDBACK_ROLE_ARN, 1); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The SQS failure feedback role ARN must be of type string."); + + e = amazonSNSClient->setTopicAttributes(topicArn, SQS_SUCCESS_FEEDBACK_SAMPLE_RATE, "1"); + test:assertTrue(e is Error, "Error expected."); + test:assertEquals((e).message(), "The SQS success feedback sample rate must be of type int."); +} diff --git a/ballerina/types.bal b/ballerina/types.bal index 56d079a..a9c496b 100644 --- a/ballerina/types.bal +++ b/ballerina/types.bal @@ -1,6 +1,6 @@ -// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // -// WSO2 Inc. licenses this file to you under the Apache License, +// 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 @@ -14,172 +14,550 @@ // specific language governing permissions and limitations // under the License. -import ballerinax/'client.config; +import ballerina/time; -# Represents the AWS SNS Connection Configuration. -@display {label: "Connection Config"} -public type ConnectionConfig record {| - *config:ConnectionConfig; - never auth?; - # Credentials to authenticate client - AwsCredentials|AwsTemporaryCredentials credentials; - # Region of SNS resource - string region = "us-east-1"; -|}; +# The hashing algorithm used while creating the signature of the notifications, subscription confirmations, or +# unsubscribe confirmation messages sent by Amazon SNS. +# +# + SignatureVersion1 - Amazon SNS creates the signature based on the SHA1 hash of the message +# + SignatureVersion2 - Amazon SNS creates the signature based on the SHA256 hash of the message +public enum SignatureVersion { + SignatureVersion1 = "1", + SignatureVersion2 = "2" +} -# AWS temporary credentials. -# -# + accessKeyId - Access key Id -# + secretAccessKey - Security access key -# + securityToken - Security token -public type AwsTemporaryCredentials record { - string accessKeyId; - @display { - label: "", - kind: "password" - } - string secretAccessKey; - @display { - label: "", - kind: "password" - } - string securityToken; -}; +# The function that Amazon SNS uses to calculate the time to wait between retries. +public enum BackoffFunction { + ARITHMETIC = "arithmetic", + EXPONENTIAL = "exponential", + GEOMETRIC = "geometric", + LINEAR = "linear" +} -# AWS credentials. -# -# + accessKeyId - Access key Id -# + secretAccessKey - Security access key -public type AwsCredentials record { - string accessKeyId; - @display { - label: "", - kind: "password" - } - string secretAccessKey; -}; +# The content type of the notification being sent to HTTP/HTTPS endpoints. +public enum HeaderContentType { + TEXT_CSS = "text/css", + TEXT_CSV = "text/csv", + TEXT_HTML = "text/html", + TEXT_PLAIN = "text/plain", + TEXT_XML = "text/xml", + APPLICATION_ATOM_XML = "application/atom+xml", + APPLICATION_JSON = "application/json", + APPLICATION_OCTET_STREAM = "application/octet-stream", + APPLICATION_SOAP_XML = "application/soap+xml", + APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded", + APPLICATION_XHTML_XML = "application/xhtml+xml", + APPLICATION_XML = "application/xml" +} -public type TopicAttribute record { - string deliveryPolicy?; - string displayName?; - boolean fifoTopic?; - boolean contentBasedDeduplication?; - string kmsMasterKeyId?; - string policy?; -}; +# The possible values for the TracingConfig attribute of a topic. +public enum TracingConfig { + PASS_THROUGH = "PassThrough", + ACTIVE = "Active" +} -public type CreateTopicResponse record { - CreateTopicResult createTopicResult; - ResponseMetadata responseMetadata; -}; +# The possible values for topic attributes. +public enum TopicAttributeName { + DELIVERY_POLICY = "DeliveryPolicy", + DISPLAY_NAME = "DisplayName", + POLICY = "Policy", + SIGNATURE_VERSION = "SignatureVersion", + TRACING_CONFIG = "TracingConfig", + KMS_MASTER_KEY_ID = "KmsMasterKeyId", + CONTENT_BASED_DEDUPLICATION = "ContentBasedDeduplication", + HTTP_SUCCESS_FEEDBACK_ROLE_ARN = "HTTPSuccessFeedbackRoleArn", + HTTP_SUCCESS_FEEDBACK_SAMPLE_RATE = "HTTPSuccessFeedbackSampleRate", + HTTP_FAILURE_FEEDBACK_ROLE_ARN = "HTTPFailureFeedbackRoleArn", + FIREHOSE_SUCCESS_FEEDBACK_ROLE_ARN = "FirehoseSuccessFeedbackRoleArn", + FIREHOSE_SUCCESS_FEEDBACK_SAMPLE_RATE = "FirehoseSuccessFeedbackSampleRate", + FIREHOSE_FAILURE_FEEDBACK_ROLE_ARN = "FirehoseFailureFeedbackRoleArn", + LAMBDA_SUCCESS_FEEDBACK_ROLE_ARN = "LambdaSuccessFeedbackRoleArn", + LAMBDA_SUCCESS_FEEDBACK_SAMPLE_RATE = "LambdaSuccessFeedbackSampleRate", + LAMBDA_FAILURE_FEEDBACK_ROLE_ARN = "LambdaFailureFeedbackRoleArn", + APPLICATION_SUCCESS_FEEDBACK_ROLE_ARN = "ApplicationSuccessFeedbackRoleArn", + APPLICATION_SUCCESS_FEEDBACK_SAMPLE_RATE = "ApplicationSuccessFeedbackSampleRate", + APPLICATION_FAILURE_FEEDBACK_ROLE_ARN = "ApplicationFailureFeedbackRoleArn", + SQS_SUCCESS_FEEDBACK_ROLE_ARN = "SQSSuccessFeedbackRoleArn", + SQS_SUCCESS_FEEDBACK_SAMPLE_RATE = "SQSSuccessFeedbackSampleRate", + SQS_FAILURE_FEEDBACK_ROLE_ARN = "SQSFailureFeedbackRoleArn" +} -public type CreateTopicResult record { - string topicArn; -}; +# The types of targets to which a message can be published. +public enum TargetType { + TOPIC, + ARN, + PHONE_NUMBER +} -public type SubscribeResponse record { - SubscribeResult subscribeResult; - ResponseMetadata responseMetadata; -}; +# The scopes to which a subscription filter policy can be applied to. +public enum FilterPolicyScope { + MESSAGE_ATTRIBUTES = "MessageAttributes", + MESSAGE_BODY = "MessageBody" +} -public type SubscribeResult record { - string subscriptionArn; -}; +# The possible subscription protocols. +public enum SubscriptionProtocol { + HTTP = "http", + HTTPS = "https", + EMAIL = "email", + EMAIL_JSON = "email-json", + SMS = "sms", + SQS = "sqs", + APPLICATION = "application", + LAMBDA = "lambda", + FIREHOSE = "firehose" +} -public type PublishResponse record { - PublishResult publishResult; - ResponseMetadata responseMetadata; -}; +# The possible values for the `AttributeName` parameter of the `setSubscriptionAttributes` operation. +public enum SubscriptionAttributeName { + DELIVERY_POLICY = "DeliveryPolicy", + FILTER_POLICY = "FilterPolicy", + FILTER_POLICY_SCOPE = "FilterPolicyScope", + RAW_MESSAGE_DELIVERY = "RawMessageDelivery", + REDRIVE_POLICY = "RedrivePolicy", + SUBSCRIPTION_ROLE_ARN = "SubscriptionRoleArn" +} -public type PublishResult record { - string messageId; +# The types of application platforms supported. +public enum Platform { + AMAZON_DEVICE_MESSAGING = "ADM", + APPLE_PUSH_NOTIFICATION_SERVICE = "APNS", + APPLE_PUSH_NOTIFICATION_SERVICE_SANDBOX = "APNS_SANDBOX", + FIREBASE_CLOUD_MESSAGING = "GCM", + BAIDU_CLOUD_PUSH = "BAIDU", + MICROSOFT_PUSH_NOTIFICATION_SERVICE = "MPNS", + WINDOWS_NOTIFICATION_SERVICE = "WNS" }; -public type ConfirmedSubscriptionResponse record { - ConfirmedSubscriptionResult confirmedSubscriptionResult; - ResponseMetadata responseMetadata; +# The types of actions that can be performed on a topic. +# https://docs.aws.amazon.com/sns/latest/dg/sns-access-policy-language-api-permissions-reference.html +public enum Action { + ADD_PERMISSION = "AddPermission", + DELETE_TOPIC = "DeleteTopic", + GET_DATA_PROTECTION_POLICY = "GetDataProtectionPolicy", + GET_TOPIC_ATTRIBUTES = "GetTopicAttributes", + LIST_SUBSCRIPTIONS = "ListSubscriptionsByTopic", + LIST_TAGS = "ListTagsForResource", + PUBLISH = "Publish", + PUT_DATA_PROTECTION_POLICY = "PutDataProtectionPolicy", + REMOVE_PERMISSION = "RemovePermission", + SET_TOPIC_ATTRIBUTES = "SetTopicAttributes", + SUBSCRIBE = "Subscribe" }; -public type ConfirmedSubscriptionResult record { - string subscriptionArn; +# The languages supported by Amazon SNS for sending SMS OTP messages. +public enum LanguageCode { + EN_US = "en-US", + EN_GB = "en-GB", + ES_419 = "es-419", + ES_ES = "es-ES", + DE_DE = "de-DE", + FR_CA = "fr-CA", + FR_FR = "fr-FR", + IT_IT = "it-IT", + JA_JP = "ja-JP", + PT_BR = "pt-BR", + KR_KR = "kr-KR", + ZH_CN = "zh-CN", + ZH_TW = "zh-TW" }; -public type UnsubscribeResponse record { - ResponseMetadata responseMetadata; +# The types of phone number verification status. +public enum Status { + PENDING = "Pending", + VERIFIED = "Verified" }; -public type DeleteTopicResponse record { - ResponseMetadata responseMetadata; +# The types of capabilities supported by an origination phone number. +public enum NumberCapabilities { + _SMS = "SMS", + MMS, + VOICE }; -public type GetTopicAttributesResponse record { - GetTopicAttributesResult getTopicAttributesResult; - ResponseMetadata responseMetadata; +# The types of routes supported by an origination phone number. +public enum RouteType { + TRANSACTIONAL = "Transactional", + PROMOTIONAL = "Promotional", + PREMIUM = "Premium" }; -public type GetTopicAttributesResult record { - json attributes; +# The types of SMS messages that may be sent. +public enum SMSMessageType { + PROMOTIONAL = "Promotional", + TRANSACTIONAL = "Transactional" }; -public type GetSMSAttributesResponse record { - GetSMSAttributesResult getSMSAttributesResult; - ResponseMetadata responseMetadata; -}; +# Represents the attributes of an Amazon SNS topic. +# +# + deliveryPolicy - The policy that defines how Amazon SNS retries failed deliveries to HTTP/S endpoints +# + displayName - The display name to use for a topic with SMS subscriptions +# + fifoTopic - Set to true to create a FIFO topic +# + policy - The policy that defines who can access your topic +# + signatureVersion - The signature version corresponds to the hashing algorithm used while creating the signature +# of the notifications, subscription confirmations, or unsubscribe confirmation messages sent by +# Amazon SNS +# + tracingConfig - Tracing mode of an Amazon SNS topic +# + kmsMasterKeyId - The ID of an AWS-managed customer master key (CMK) for Amazon SNS or a custom CMK +# + contentBasedDeduplication - Enables content-based deduplication for FIFO topics. Applies only to FIFO topics +# + httpMessageDeliveryLogging - The configurations for message delivery logging for the HTTP delivery protocol +# + firehoseMessageDeliveryLogging - The configurations for message delivery logging for the Amazon Kinesis Data +# Firehose delivery stream delivery protocol +# + lambdaMessageDeliveryLogging - The configurations for message delivery logging for the Lambda delivery protocol +# + applicationMessageDeliveryLogging - The configurations for message delivery logging for the application delivery +# + sqsMessageDeliveryLogging - The configurations for message delivery logging for the Amazon SQS delivery protocol +public type InitializableTopicAttributes record {| + json deliveryPolicy?; + string displayName?; + boolean fifoTopic?; + json policy?; + SignatureVersion signatureVersion?; + TracingConfig tracingConfig?; + string kmsMasterKeyId?; + boolean contentBasedDeduplication?; + MessageDeliveryLoggingConfig httpMessageDeliveryLogging?; + MessageDeliveryLoggingConfig firehoseMessageDeliveryLogging?; + MessageDeliveryLoggingConfig lambdaMessageDeliveryLogging?; + MessageDeliveryLoggingConfig applicationMessageDeliveryLogging?; + MessageDeliveryLoggingConfig sqsMessageDeliveryLogging?; +|}; -public type GetSMSAttributesResult record { - json attributes; -}; +# Represents the configurations to be used for message delivery logging for a particular protocol. +# +# + successFeedbackRoleArn - Indicates successful message delivery status for an Amazon SNS topic that is subscribed to +# an endpoint +# + failureFeedbackRoleArn - Indicates failed message delivery status for an Amazon SNS topic that is subscribed to an +# endpoint +# + successFeedbackSampleRate - Indicates percentage of successful messages to sample for an Amazon SNS topic that is +# subscribed to an endpoint +# +public type MessageDeliveryLoggingConfig record {| + string successFeedbackRoleArn?; + string failureFeedbackRoleArn?; + int successFeedbackSampleRate?; +|}; -public type GetSubscriptionAttributesResponse record { - GetSubscriptionAttributesResult getSubscriptionAttributesResult; - ResponseMetadata responseMetadata; +# Represents an Amazon SNS topic. +# +# + topicArn - The topic's ARN +# + effectiveDeliveryPolicy - The policy that defines how Amazon SNS retries failed deliveries to HTTP/S endpoints, +# taking system defaults into account. +# + owner - The AWS account ID of the topic's owner +# + policy - The policy that defines who can access your topic +# + subscriptionsConfirmed - The number of confirmed subscriptions for the topic +# + subscriptionsDeleted - The number of deleted subscriptions for the topic +# + subscriptionsPending - The number of subscriptions pending confirmation for the topic +public type GettableTopicAttributes record { + *InitializableTopicAttributes; + string topicArn; + json effectiveDeliveryPolicy; + string owner; + json policy; + int subscriptionsConfirmed; + int subscriptionsDeleted; + int subscriptionsPending; }; -public type GetSubscriptionAttributesResult record { - json attributes; -}; +# Represents a message that is published to an Amazon SNS topic. If you are publishing to a topic and you want to send +# the same message to all transport protocols, include the text of the message as a `string` value. If you want to send +# different messages for each transport protocol use a `MessageRecord` value. +public type Message string|MessageRecord; -public type ResponseMetadata record { - string requestId; -}; +# Contains the messages to be published for each transport protocol. +# +# + default - The default message that's used for all transport protocols if no individual message is specified +# + subject - Optional parameter to be used as the "Subject" line when the message is delivered to email endpoints +# + email - The message to be sent to email endpoints +# + emailJson - The message to be sent to email endpoints formatted as a JSON object +# + sqs - The message to be sent to Amazon SQS endpoints +# + lambda - The message to be sent to AWS Lambda (Lambda) endpoints +# + http - The message to be sent to HTTP endpoints +# + https - The message to be sent to HTTPS endpoints +# + sms - The message to be sent to SMS endpoints +# + firehose - The message to be sent to Amazon Kinesis Data Firehose endpoints +# + apns - The payload to be sent to APNS endpoints +# + apnsSandbox - The payload to be sent to APNS sandbox endpoints +# + apnsVoip - The payload to be sent to APNS VoIP endpoints +# + apnsVoipSandbox - The payload to be sent to APNS VoIP sandbox endpoints +# + macos - The payload to be sent to MacOS endpoints +# + macosSandbox - The payload to be sent to MacOS sandbox endpoints +# + gcm - The payload to be sent to GCM endpoints +# + adm - The payload to be sent to ADM endpoints +# + baidu - The payload to be sent to Baidu endpoints +# + mpns - The payload to be sent to MPNS endpoints +# + wns - The payload to be sent to WNS endpoints +public type MessageRecord record {| + string default; + string subject?; + string email?; + string emailJson?; + string sqs?; + string lambda?; + string http?; + string https?; + string sms?; + string firehose?; + string apns?; + string apnsSandbox?; + string apnsVoip?; + string apnsVoipSandbox?; + string macos?; + string macosSandbox?; + string gcm?; + string adm?; + string baidu?; + string mpns?; + string wns?; + string...; +|}; + +# Represents an attribute value of a message. +public type MessageAttributeValue string|StringArrayElement[]|int|float|decimal|byte[]; -public type SubscriptionAttribute record { - string deliveryPolicy?; - string filterPolicy?; +# Represents an element of the String.Array type of a message attribute value. +public type StringArrayElement string|int|float|decimal|boolean|(); + +# Represents the details of a single message in a publish batch request. +# +# + id - A unique identifier for the message in the batch +# + message - The message to send +# + attributes - The attributes of the message +# + deduplicationId - Every message must have a unique `deduplicationId`, which is a token used for deduplication +# of sent messages. If a message with a particular `deduplicationId` is sent successfully, any +# message sent with the same `deduplicationId` during the 5-minute deduplication interval is +# treated as a duplicate. If the topic has `contentBasedDeduplication` set, the system +# generates a `deduplicationId` based on the contents of the message. Your `deduplicationId` +# overrides the generated one. Applies to FIFO topics only +# + groupId - Specifies the message group to which a message belongs to. Messages that belong to the same message +# group are processed in a FIFO manner (however, messages in different message groups might be processed +# out of order). Every message must include a `groupId`. Applies to FIFO topics only +public type PublishBatchRequestEntry record {| + string id?; + Message message; + map attributes?; + string deduplicationId?; + string groupId?; +|}; + +# Represents the attributes that can be set when creating a subscription. +# +# + deliveryPolicy - The policy that defines how Amazon SNS retries failed deliveries to HTTP/S endpoints +# + filterPolicy - The filter policy that is assigned to the subscription which lets the subscriber receive only a +# subset of the messages published to the topic +# + filterPolicyScope - Defines whether the filter policy is applied to the message attributes or the message body +# + rawMessageDelivery - When set to true, enables raw message delivery to Amazon SQS or HTTP/S endpoints +# + redrivePolicy - When specified, sends undeliverable messages to the specified Amazon SQS dead-letter queue +# + subscriptionRoleArn - The ARN of the IAM role that has permission to write to the Kinesis Data Firehose delivery +# stream and has Amazon SNS listed as a trusted entity. Applies only to Amazon Kinesis Data +# Firehose delivery stream subscriptions. +public type SubscriptionAttributes record {| + json deliveryPolicy?; + json filterPolicy?; + FilterPolicyScope filterPolicyScope?; boolean rawMessageDelivery?; - boolean redrivePolicy?; + json redrivePolicy?; string subscriptionRoleArn?; -}; +|}; -public type SmsAttribute record { - string monthlySpendLimit?; - string deliveryStatusIAMRole?; - string deliveryStatusSuccessSamplingRate?; - string defaultSenderID?; - string defaultSMSType?; - string usageReportS3Bucket?; -}; +# Represents an Amazon SNS subscription object returned when calling the `listSubscriptions` operation. +# +# + subscriptionArn - The subscription's ARN +# + owner - The subscription's owner +# + protocol - The subscription's protocol +# + endpoint - The subscription's endpoint (format depends on the protocol) +# + topicArn - The ARN of the subscription's topic +public type Subscription record {| + string subscriptionArn; + string owner; + SubscriptionProtocol protocol; + string endpoint; + string topicArn; +|}; -public type SmsAttributeArray record { - string key?; - string value?; +# Represents an Amazon SNS subscription object returned when calling the `getSubscription` operation. +# +# + subscriptionArn - The subscription's ARN +# + endpoint - The subscription's endpoint (format depends on the protocol) +# + protocol - The subscription's protocol +# + topicArn - The ARN of the subscription's topic +# + subscriptionPrincipal - The subscription's principal +# + confirmationWasAuthenticated - Whether the subscription confirmation request was authenticated +# + deliveryPolicy - The policy that defines how Amazon SNS retries failed deliveries to HTTP/S endpoints +# + effectiveDeliveryPolicy - The policy that defines how Amazon SNS retries failed deliveries to HTTP/S endpoints, +# taking system defaults into account +# + filterPolicy - The filter policy that is assigned to the subscription which lets the subscriber receive only a +# subset of the messages published to the topic +# + filterPolicyScope - Defines whether the filter policy is applied to the message attributes or the message body +# + owner - The AWS account ID of the owner of the subscription +# + pendingConfirmation - Whether the subscription has been confirmed +# + rawMessageDelivery - Whether raw message delivery is enabled for the subscription +# + redrivePolicy - The redrive policy attached to the subscription +# + subscriptionRoleArn - The ARN of the IAM role that has permission to write to the Kinesis Data Firehose delivery and +# has Amazon SNS listed as a trusted entity. Applies only to Amazon Kinesis Data Firehose +# delivery stream subscriptions. +public type GettableSubscriptionAttributes record { + string subscriptionArn; + string endpoint; + SubscriptionProtocol protocol; + string topicArn; + string subscriptionPrincipal; + boolean confirmationWasAuthenticated; + json deliveryPolicy?; + json effectiveDeliveryPolicy?; + json filterPolicy?; + FilterPolicyScope filterPolicyScope?; + string owner; + boolean pendingConfirmation; + boolean rawMessageDelivery; + json redrivePolicy?; + string subscriptionRoleArn?; }; -public type SmsAttributes record { - SmsAttributeArray[] smsAttribute?; -}; +# Represents the attributes of an Amazon SNS platform appication. +# +# + eventEndpointCreated - The topic ARN to which `EndpointCreated` event notifications should be sent +# + eventEndpointDeleted - The topic ARN to which `EndpointDeleted` event notifications should be sent +# + eventEndpointUpdated - The topic ARN to which `EndpointUpdated` event notifications should be sent +# + eventDeliveryFailure - The topic ARN to which `DeliveryFailure` event notifications should be sent upon Direct +# Publish delivery failure (permanent) to one of the application's endpoints +# + successFeedbackRoleArn - The IAM role ARN used to give Amazon SNS write access to use CloudWatch Logs on your behalf +# + failureFeedbackRoleArn - The IAM role ARN used to give Amazon SNS write access to use CloudWatch Logs on your behalf +# + successFeedbackSampleRate - The percentage of success to sample (0-100) +public type PlatformApplicationAttributes record {| + string eventEndpointCreated?; + string eventEndpointDeleted?; + string eventEndpointUpdated?; + string eventDeliveryFailure?; + string successFeedbackRoleArn?; + string failureFeedbackRoleArn?; + int successFeedbackSampleRate?; +|}; -public type TopicAttributes record { - TopicAttributeArray[] topicAttribute?; -}; +# Represents the authentication attributes of an Amazon SNS platform appication that can be set. +# +# + platformCredential - The credential received from the notification service +# + platformPrincipal - The principal received from the notification service +# + applePlatformTeamId - The identifier that's assigned to your Apple developer account team +# + applePlatformBundleId - The bundle identifier that's used for APNs tokens +public type PlatformApplicationAuthentication record {| + string platformCredential; + string platformPrincipal?; + string applePlatformTeamId?; + string applePlatformBundleId?; +|}; -public type TopicAttributeArray record { - string key?; - string value?; -}; +# Represents the attributes of an Amazon SNS platform appication that can be set using the +# `setPlatformApplicationAttributes` action. +# +# + platformCredential - The credential received from the notification service +# + platformPrincipal - The principal received from the notification service +# + applePlatformTeamId - The identifier that's assigned to your Apple developer account team +# + applePlatformBundleId - The bundle identifier that's used for APNs tokens +public type SettablePlatformApplicationAttributes record {| + *PlatformApplicationAttributes; + string platformCredential?; + string platformPrincipal?; + string applePlatformTeamId?; + string applePlatformBundleId?; +|}; -public type MessageAttribute record { - string key?; - string value?; -}; +# Represents the attributes of an Amazon SNS platform application that can be retrieved. +# +# + enabled - Whether the platform application is enabled for direct publishing from Amazon SNS +# + appleCertificateExpiryDate - The expiry date of the SSL certificate used to configure certificate-based +# authentication +# + applePlatformTeamId - The Apple developer account ID used to configure token-based authentication +# + applePlatformBundleId - The bundle identifier used to configure token-based authentication +public type RetrievablePlatformApplicationAttributes record {| + boolean enabled; + string appleCertificateExpiryDate?; + string applePlatformTeamId?; + string applePlatformBundleId?; + *PlatformApplicationAttributes; +|}; + +# Represents an Amazon SNS platform appication. +# +# + platformApplicationArn - The ARN of the platform application object +# Publish delivery failure (permanent) to one of the application's endpoints +public type PlatformApplication record {| + string platformApplicationArn; + *RetrievablePlatformApplicationAttributes; +|}; + +# Represents the attributes of an Amazon SNS platform application endpoint. +# +# + customUserData - Arbitrary user data to associate with the endpoint +# + enabled - flag that enables/disables delivery to the endpoint +# + token - Unique identifier created by the notification service for an app on a device. The specific name for +# the token will vary, depending on which notification service is being used +public type EndpointAttributes record {| + string customUserData?; + boolean enabled?; + string token?; +|}; + +# Represents an Amazon SNS platform appication endpoint. +# +# + endpointArn - The endpoint's ARN +public type Endpoint record {| + string endpointArn; + *EndpointAttributes; +|}; + +# Represent an SMS sandbox phone number. +# +# + phoneNumber - The destination phone number +# + status - The destination phone number's verification status +public type SMSSandboxPhoneNumber record {| + string phoneNumber; + Status status; +|}; + +# Represents an origination phone number. +# +# + createdAt - The date and time when the origination phone number was created +# + iso2CountryCode - The two-character code, in ISO 3166-1 alpha-2 format, for the country or region where the +# origination phone number was originally registered +# + numberCapabilities - The capabilities of the origination phone number +# + phoneNumber - The phone number +# + routeType - The route type +# + status - The status of the origination phone number +public type OriginationPhoneNumber record {| + time:Civil createdAt; + string iso2CountryCode; + NumberCapabilities[] numberCapabilities; + string phoneNumber; + RouteType routeType; + string status; +|}; + +# Represents the attributes for sending SMS messages with Amazon SNS. +# +# + monthlySpendLimit - The maximum amount in USD that you are willing to spend each month to send SMS messages. When +# Amazon SNS determines that sending an SMS message would incur a cost that exceeds this limit, +# it stops sending SMS messages within minutes +# + deliveryStatusIAMRole - The ARN of the IAM role that allows Amazon SNS to write logs about SMS deliveries in +# CloudWatch logs +# + deliveryStatusSuccessSamplingRate - The percentage of successful SMS deliveries for which Amazon SNS will write +# logs in CloudWatch Logs +# + defaultSenderID - A string that is displayed as the sender on the receiving device +# + defaultSMSType - The type of SMS message that you will send by default +# + usageReportS3Bucket - The name of the Amazon S3 bucket to receive daily SMS usage reports from Amazon SNS +public type SMSAttributes record {| + int monthlySpendLimit?; + string deliveryStatusIAMRole?; + int deliveryStatusSuccessSamplingRate?; + string defaultSenderID?; + SMSMessageType defaultSMSType?; + string usageReportS3Bucket?; +|}; + +# Represents the tags associated with an Amazon SNS topic. +# +# + topicArn - The ARN of the topic to which the tags are added +public type Tags record {| + never topicArn?; + string...; +|}; diff --git a/ballerina/utils.bal b/ballerina/utils.bal index 10c117f..de89b11 100644 --- a/ballerina/utils.bal +++ b/ballerina/utils.bal @@ -14,100 +14,61 @@ // specific language governing permissions and limitations // under the License. +import ballerina/time; import ballerina/jballerina.java; import ballerina/http; -import ballerina/time; +import ballerina/lang.'int as langint; +import ballerina/lang.'boolean as langboolean; -isolated function buildQueryString(string actionName, map parameterMap, string... parameterValues) returns map { - int index = 0; - parameterMap[ACTION] = actionName; - parameterMap[VERSION] = VERSION_NUMBER; - foreach string? parameterValue in parameterValues { - parameterMap[getAttributeName(parameterValue)] = parameterValue; - index += 1; +isolated function sendRequest(http:Client amazonSNSClient, http:Request request) returns json|Error { + do { + http:Response httpResponse = check amazonSNSClient->post("/", request); + return handleResponse(httpResponse); + } on fail error e { + return error Error(ERROR_OCCURRED_WHILE_INVOKING_REST_API_MSG, e); } - return parameterMap; } -isolated function createQueryString(string actionName, map parameterMap) returns map { - parameterMap[ACTION] = actionName; - parameterMap[VERSION] = VERSION_NUMBER; - return parameterMap; -} - -isolated function addTopicOptionalParameters(map parameterMap, TopicAttributes? attributes = (), map? tags = ()) returns map|error { - if (attributes is TopicAttributes) { - setTopicAttributes(parameterMap, attributes); - } - if (tags is map) { - setTags(parameterMap, tags); +isolated function handleResponse(http:Response httpResponse) returns json|Error { + if httpResponse.statusCode == http:STATUS_NO_CONTENT { + return error ResponseHandleFailedError(NO_CONTENT_SET_WITH_RESPONSE_MSG); } - return parameterMap; -} -isolated function addSubscriptionOptionalParameters(map parameterMap, string? endpoint = (), boolean? returnSubscriptionArn = (), SubscriptionAttribute? attributes = ()) returns map { - if (endpoint is string) { - parameterMap["Endpoint"] = endpoint.toString(); - } - if (returnSubscriptionArn is boolean) { - parameterMap["ReturnSubscriptionArn"] = returnSubscriptionArn.toString(); + json|http:ClientError response = httpResponse.getJsonPayload(); + if response is http:ClientError { + return error ResponseHandleFailedError(response.toString()); } - if (attributes is SubscriptionAttribute) { - setSubscriptionAttributes(parameterMap, attributes); - } - return parameterMap; -} -isolated function addPublishOptionalParameters(map parameterMap, string? topicArn = (), string? targetArn = (), string? subject = (), string? phoneNumber = (), string? messageStructure = (), string? messageDeduplicationId = (), string? messageGroupId = (), MessageAttribute? messageAttributes = ()) { - if (topicArn is string) { - parameterMap["TopicArn"] = topicArn.toString(); - } - if (targetArn is string) { - parameterMap["TargetArn"] = targetArn.toString(); - } - if (subject is string) { - parameterMap["Subject"] = subject.toString(); - } - if (phoneNumber is string) { - parameterMap["PhoneNumber"] = phoneNumber.toString(); - } - if (messageStructure is string) { - parameterMap["MessageStructure"] = messageStructure.toString(); - } - if (messageDeduplicationId is string) { - parameterMap["MessageDeduplicationId"] = messageDeduplicationId.toString(); + if httpResponse.statusCode == http:STATUS_OK { + return response; } - if (messageGroupId is string) { - parameterMap["MessageGroupId"] = messageGroupId.toString(); - } - if (messageAttributes is MessageAttribute) { - setMessageAttributes(parameterMap, messageAttributes); + + do { + return error OperationError(check response.Error.Message); + } on fail { + // Unreachable code - not testable + return error InternalError(response.toString()); } } -isolated function addOptionalStringParameters(map parameterMap, string?... optionalParameterValues) returns map|error { - int index = 0; - foreach string? optionalParameterValue in optionalParameterValues { - if(optionalParameterValue is string) { - parameterMap[getAttributeName(optionalParameterValue)] = optionalParameterValue; - } - index += 1; - } +isolated function initiateRequest(string actionName) returns map { + map parameterMap = {}; + parameterMap[ACTION] = actionName; + parameterMap[VERSION] = VERSION_NUMBER; return parameterMap; } -isolated function sendRequest(http:Client amazonSNSClient, http:Request|error request) returns xml|error { - if (request is http:Request) { - http:Response|error httpResponse = amazonSNSClient->post("/", request); - return handleResponse(httpResponse); - } else { - return error(REQUEST_ERROR); +isolated function validateCredentails(string accessKeyId, string secretAccessKey) returns error? { + if ((accessKeyId == EMPTY_STRING) && (secretAccessKey == EMPTY_STRING)) { + return error("Access Key Id and Secret Access Key credential is empty"); + } + + if (accessKeyId == EMPTY_STRING) { + return error("Access Key Id credential is empty"); } -} -isolated function validateCredentails(string accessKeyId, string secretAccessKey) returns error? { - if ((accessKeyId == EMPTY_STRING) || (secretAccessKey == EMPTY_STRING)) { - return error("Access Key Id or Secret Access Key credential is empty"); + if (secretAccessKey == EMPTY_STRING) { + return error("Secret Access Key credential is empty"); } } @@ -122,133 +83,27 @@ isolated function utcToString(time:Utc utc, string pattern) returns string|error return formatString.toBalString(); } -# Handles the HTTP response. -# -# + httpResponse - Http response or error -# + return - If successful returns `xml` response. Else returns error -isolated function handleResponse(http:Response|error httpResponse) returns xml|error { - if httpResponse is http:Response { - if httpResponse.statusCode == http:STATUS_NO_CONTENT { - return error ResponseHandleFailed(NO_CONTENT_SET_WITH_RESPONSE_MSG); - } - var xmlResponse = httpResponse.getXmlPayload(); - if xmlResponse is xml { - if httpResponse.statusCode == http:STATUS_OK { - return xmlResponse; - } else { - xmlns "http://sns.amazonaws.com/doc/2010-03-31/" as ns; - string xmlResponseErrorCode = httpResponse.statusCode.toString(); - string responseErrorMessage = (xmlResponse///*).toString(); - string errorMsg = "status code" + ":" + xmlResponseErrorCode + - ";" + " " + "message" + ":" + " " + - responseErrorMessage; - return error(errorMsg); - } - } else { - return error(RESPONSE_PAYLOAD_IS_NOT_XML_MSG); - } - } else { - return error(ERROR_OCCURRED_WHILE_INVOKING_REST_API_MSG, httpResponse); - } -} - -# Set topic attributes to a map of string to add as query parameters. -# -# + parameters - Parameter map -# + attributes - TopicAttributes to convert to a map of string -isolated function setTopicAttributes(map parameters, TopicAttributes attributes) { - int attributeNumber = 1; - map attributeMap = >attributes; - foreach var [key, value] in attributeMap.entries() { - string attributeName = getAttributeName(key); - parameters["Attributes.entry." + attributeNumber.toString() + ".Name"] = attributeName.toString(); - parameters["Attributes.entry." + attributeNumber.toString() + ".Value"] = value.toString(); - attributeNumber = attributeNumber + 1; - } +isolated function uppercaseFirstLetter(string str) returns string { + string firstLetter = str.substring(0, 1); + string remainingLetters = str.substring(1); + return firstLetter.toUpperAscii() + remainingLetters; } -# Set tags to a map of string to add as query parameters. -# -# + parameters - Parameter map -# + tags - Tags to convert to a map of string -isolated function setTags(map parameters, map tags) { - int tagNumber = 1; - foreach var [key, value] in tags.entries() { - parameters["Tag." + tagNumber.toString() + ".Key"] = key; - parameters["Tag." + tagNumber.toString() + ".Value"] = value; - tagNumber = tagNumber + 1; - } +isolated function lowercaseFirstLetter(string str) returns string { + string firstLetter = str.substring(0, 1); + string remainingLetters = str.substring(1); + return firstLetter.toLowerAscii() + remainingLetters; } -# Set subscription attributes to a map of string to add as query parameters. -# -# + parameters - Parameter map -# + attributes - SubscriptionAttribute to convert to a map of string -isolated function setSubscriptionAttributes(map parameters, SubscriptionAttribute attributes) { - int attributeNumber = 1; - map attributeMap = >attributes; - foreach var [key, value] in attributeMap.entries() { - string attributeName = getAttributeName(key); - parameters["Attributes.entry." + attributeNumber.toString() + ".Name"] = attributeName.toString(); - parameters["Attributes.entry." + attributeNumber.toString() + ".Value"] = value.toString(); - attributeNumber = attributeNumber + 1; - } +isolated function stringToInt(string str) returns int|error { + return check langint:fromString(str.toString()); } -# Set message attributes to a map of string to add as query parameters. -# -# + parameters - Parameter map -# + attributes - MessageAttribute to convert to a map of string -isolated function setMessageAttributes(map parameters, MessageAttribute attributes) { - int attributeNumber = 1; - map attributeMap = >attributes; - foreach var [key, value] in attributeMap.entries() { - string attributeName = getAttributeName(key); - parameters["Attributes.entry." + attributeNumber.toString() + ".Name"] = attributeName.toString(); - parameters["Attributes.entry." + attributeNumber.toString() + ".Value"] = value.toString(); - attributeNumber = attributeNumber + 1; - } -} - -# Set SMS attributes to a map of string to add as query parameters. -# -# + parameters - Parameter map -# + attributes - SmsAttribute to convert to a map of string -# + return - If successful returns `map` response. Else returns error -isolated function setSmsAttributes(map parameters, SmsAttributes attributes) returns map { - int attributeNumber = 1; - map attributeMap = >attributes; - foreach var [key, value] in attributeMap.entries() { - string attributeName = getAttributeName(key); - parameters["Attributes.entry." + attributeNumber.toString() + ".Name"] = attributeName.toString(); - parameters["Attributes.entry." + attributeNumber.toString() + ".Value"] = value.toString(); - attributeNumber = attributeNumber + 1; - } - return parameters; -} - -# Add SMS attributes. -# -# + parameters - Parameter map -# + attributes - Array of attributes to convert to a map of string -# + return - If successful returns `map` response. Else returns error. -isolated function addSmsAttributes(map parameters, string[] attributes) returns map { - int attributeNumber = 1; - foreach var attribute in attributes { - parameters["attributes.member." + attributeNumber.toString()] = attribute.toString(); - attributeNumber = attributeNumber + 1; - } - return parameters; +isolated function stringToBoolean(string str) returns boolean|error { + return check langboolean:fromString(str.toString()); } -# Returns attribute name from field of record. This capitalizes the first letter of the attribute. -# -# + attribute - Field name of record -# + return - If successful returns attribute name string. Else returns error -isolated function getAttributeName(string attribute) returns string { - string firstLetter = attribute.substring(0, 1); - string otherLetters = attribute.substring(1); - string upperCaseFirstLetter = firstLetter.toUpperAscii(); - string attributeName = upperCaseFirstLetter + otherLetters; - return attributeName; +isolated function stringToTimestamp(string str) returns time:Civil|error { + time:Utc utc = [check stringToInt(str), 0]; + return time:utcToCivil(utc); } diff --git a/ballerina/validators.bal b/ballerina/validators.bal new file mode 100644 index 0000000..8316d04 --- /dev/null +++ b/ballerina/validators.bal @@ -0,0 +1,131 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// 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. + +isolated function validateInitializableTopicAttributes(InitializableTopicAttributes attributes) returns Error? { + // If content-based deduplication is enabled, then it must also be a FIFO topic + if (attributes.contentBasedDeduplication is boolean && attributes.contentBasedDeduplication) && + (!(attributes.fifoTopic is boolean) || !attributes.fifoTopic) { + return error Error("If content-based deduplication is enabled, it must also be a FIFO topic."); + } +} + +isolated function validateTopicAttribute(TopicAttributeName attributeName, + json|string|int|boolean value) returns Error? { + + if attributeName is DISPLAY_NAME && !(value is string) { + return error Error("The display name must be of type string."); + } + + if attributeName is SIGNATURE_VERSION && !(value is SignatureVersion) { + return error Error("The signature version must be of type SignatureVersion."); + } + + if attributeName is TRACING_CONFIG && !(value is TracingConfig) { + return error Error("The tracing config must be of type TracingConfig."); + } + + if attributeName is KMS_MASTER_KEY_ID && !(value is string) { + return error Error("The KMS master key ID must be of type string."); + } + + if attributeName is CONTENT_BASED_DEDUPLICATION && !(value is boolean) { + return error Error("The content-based deduplication must be of type boolean."); + } + + if attributeName is HTTP_SUCCESS_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The HTTP success feedback role ARN must be of type string."); + } + + if attributeName is HTTP_FAILURE_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The HTTP failure feedback role ARN must be of type string."); + } + + if attributeName is HTTP_SUCCESS_FEEDBACK_SAMPLE_RATE && !(value is int) { + return error Error("The HTTP success feedback sample rate must be of type int."); + } + + if attributeName is FIREHOSE_SUCCESS_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The Firehose success feedback role ARN must be of type string."); + } + + if attributeName is FIREHOSE_FAILURE_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The Firehose failure feedback role ARN must be of type string."); + } + + if attributeName is FIREHOSE_SUCCESS_FEEDBACK_SAMPLE_RATE && !(value is int) { + return error Error("The Firehose success feedback sample rate must be of type int."); + } + + if attributeName is LAMBDA_SUCCESS_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The Lambda success feedback role ARN must be of type string."); + } + + if attributeName is LAMBDA_FAILURE_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The Lambda failure feedback role ARN must be of type string."); + } + + if attributeName is LAMBDA_SUCCESS_FEEDBACK_SAMPLE_RATE && !(value is int) { + return error Error("The Lambda success feedback sample rate must be of type int."); + } + + if attributeName is APPLICATION_SUCCESS_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The application success feedback role ARN must be of type string."); + } + + if attributeName is APPLICATION_FAILURE_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The application failure feedback role ARN must be of type string."); + } + + if attributeName is APPLICATION_SUCCESS_FEEDBACK_SAMPLE_RATE && !(value is int) { + return error Error("The application success feedback sample rate must be of type int."); + } + + if attributeName is SQS_SUCCESS_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The SQS success feedback role ARN must be of type string."); + } + + if attributeName is SQS_FAILURE_FEEDBACK_ROLE_ARN && !(value is string) { + return error Error("The SQS failure feedback role ARN must be of type string."); + } + + if attributeName is SQS_SUCCESS_FEEDBACK_SAMPLE_RATE && !(value is int) { + return error Error("The SQS success feedback sample rate must be of type int."); + } +} + +isolated function validatePublishParameters(string topicArn, TargetType targetType, string? groupId) returns Error? { + // If the topic is a FIFO topic, then a group ID must be provided + if (targetType is TOPIC && topicArn.endsWith(".fifo") && groupId == ()) { + return error Error("A message published to a FIFO topic requires a group ID."); + } +} + +isolated function validateSubscriptionAttribute(SubscriptionAttributeName attributeName, + json|FilterPolicyScope|boolean|string value) returns Error? { + + if attributeName is FILTER_POLICY_SCOPE && !(value is FilterPolicyScope) { + return error Error("The filter policy scope must be of type FilterPolicyScope."); + } + + if attributeName is RAW_MESSAGE_DELIVERY && !(value is boolean) { + return error Error("The raw message delivery must be of type boolean."); + } + + if attributeName is SUBSCRIPTION_ROLE_ARN && !(value is string) { + return error Error("The subscription role ARN must be of type string."); + } +} + diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..f8f43b2 --- /dev/null +++ b/changelog.md @@ -0,0 +1,12 @@ +# Changelog +This file contains all the notable changes done to the Ballerina SQL package through the releases. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +### Changed +- [Revamp of the connector](https://github.com/ballerina-platform/ballerina-standard-library/issues/4846) \ No newline at end of file diff --git a/examples/CreateTopic.bal b/examples/CreateTopic.bal index ef4f334..5af37f5 100644 --- a/examples/CreateTopic.bal +++ b/examples/CreateTopic.bal @@ -17,13 +17,9 @@ import ballerinax/aws.sns; import ballerina/log; -sns:AwsCredentials longTermCredentials = { +sns:ConnectionConfig config = { accessKeyId: "", secretAccessKey: "" -}; - -sns:ConnectionConfig config = { - credentials: longTermCredentials, region: "" }; diff --git a/examples/DeleteTopic.bal b/examples/DeleteTopic.bal index 158788c..4ae33b8 100644 --- a/examples/DeleteTopic.bal +++ b/examples/DeleteTopic.bal @@ -17,13 +17,9 @@ import ballerinax/aws.sns; import ballerina/log; -sns:AwsCredentials longTermCredentials = { +sns:ConnectionConfig config = { accessKeyId: "", secretAccessKey: "" -}; - -sns:ConnectionConfig config = { - credentials: longTermCredentials, region: "" }; diff --git a/examples/Publish.bal b/examples/Publish.bal index 31e5ad1..bfb7b69 100644 --- a/examples/Publish.bal +++ b/examples/Publish.bal @@ -17,13 +17,9 @@ import ballerinax/aws.sns; import ballerina/log; -sns:AwsCredentials longTermCredentials = { +sns:ConnectionConfig config = { accessKeyId: "", secretAccessKey: "" -}; - -sns:ConnectionConfig config = { - credentials: longTermCredentials, region: "" }; diff --git a/examples/Subscribe.bal b/examples/Subscribe.bal index a722564..e3ba3a4 100644 --- a/examples/Subscribe.bal +++ b/examples/Subscribe.bal @@ -17,13 +17,9 @@ import ballerinax/aws.sns; import ballerina/log; -sns:AwsCredentials longTermCredentials = { +sns:ConnectionConfig config = { accessKeyId: "", secretAccessKey: "" -}; - -sns:ConnectionConfig config = { - credentials: longTermCredentials, region: "" };