forked from open-runtimes/examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
74ae4db
commit c48382e
Showing
4 changed files
with
224 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import Foundation | ||
//an enum of the implementations we provide, anything else is rejected | ||
enum MessageType: String { | ||
case sms = "sms" | ||
case email = "email" | ||
case twitter = "twitter" | ||
case discord = "discord" | ||
case none | ||
} | ||
|
||
//error types which we want to define, handy for making sure all implementations provide the same errors | ||
enum MessengerError: Error { | ||
case validationError(error: String) //the message request | ||
case providerError(error: String) //what if things appear in good order to us but discord is down? This error type signifies that there is a downstream error. | ||
case misconfigurationError(error: String)// something is wrong with config like the API token for discord being wrong etc | ||
} | ||
|
||
//this is our interface for users to interact with us, input to this example function must conform to this | ||
struct Message { | ||
var type: MessageType | ||
var recipient: String | ||
var content: String | ||
var subject: String? | ||
} | ||
|
||
protocol Messenger { | ||
//make sure this is synchronous and you handle error handling | ||
func sendMessage(messageRequest: Message) async -> Error? | ||
} | ||
|
||
func main(req: RequestValue, res: RequestResponse) async throws -> RequestResponse { | ||
|
||
guard !req.payload.isEmpty, | ||
let data = req.payload.data(using: .utf8), | ||
let payload: [String: Any?] = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], | ||
let typeString = payload["type"] as? String, | ||
let recipient = payload["recipient"] as? String, | ||
let content = payload["content"] as? String else { | ||
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: Invalid payload."]) | ||
} | ||
|
||
let type:MessageType = MessageType(rawValue: typeString) ?? .none | ||
let messenger: Messenger | ||
|
||
//initialize messenger with environment variables | ||
do{ | ||
switch type{ | ||
case .sms: | ||
messenger = try SMSMessenger(req.variables) | ||
case .email: | ||
messenger = try EmailMessenger(req.variables) | ||
case .twitter: | ||
messenger = try TwitterMessenger(req.variables) | ||
case .discord: | ||
messenger = try DiscordMessenger(req.variables) | ||
default: | ||
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: Invalid Type"]) | ||
} | ||
} catch{ | ||
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: Missing environment variables."]) | ||
} | ||
|
||
let subject = payload["subject"] as? String | ||
let messageRequest = Message(type: type, recipient: recipient, content: content, subject: subject) | ||
|
||
let result = await messenger.sendMessage(messageRequest: messageRequest) | ||
|
||
if result == nil{ | ||
return res.json(data: ["success": true]) | ||
} else { | ||
let messengerError = result as? MessengerError | ||
switch messengerError{ | ||
case let .validationError(error): | ||
return res.json(data: ["success": false, "message" :"Validation Error: \(error)"]) | ||
case let .providerError(error): | ||
return res.json(data: ["success": false, "message" :"Provider Error: \(error)"]) | ||
case let .misconfigurationError(error): | ||
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: \(error)"]) | ||
default: | ||
return res.json(data: ["success": false, "message" :"Unknown Error"]) | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
swift/send-message/message-types/email/Sources/email_message.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import AsyncHTTPClient | ||
import Foundation | ||
import NIO | ||
|
||
class EmailMessenger: Messenger{ | ||
|
||
private let mailgunAPIKey: String | ||
private let mailgunDomain: String | ||
private let httpClient: HTTPClient | ||
|
||
init(){ | ||
mailgunAPIKey = ProcessInfo.processInfo.environment["MAILGUN_API_KEY"]! | ||
mailgunDomain = ProcessInfo.processInfo.environment["MAILGUN_DOMAIN"]! | ||
self.httpClient = HTTPClient(eventLoopGroupProvider: .createNew) | ||
} | ||
|
||
public func sendMessage(messageRequest: Message) async -> Error? { | ||
|
||
do{ | ||
|
||
let targetURL:String = "https://api.mailgun.net/v3/\(mailgunDomain)/messages" | ||
let auth:String = "api:\(mailgunAPIKey)".data(using: .utf8)!.base64EncodedString() | ||
|
||
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) | ||
var request = HTTPClientRequest(url: targetURL) | ||
request.method = .POST | ||
request.headers.add(name: "Content-Type", value: "application/x-www-form-urlencoded") //same as sms | ||
request.headers.add(name: "Authorization", value: "Basic \(auth)") | ||
|
||
let bodyString: String = "from=me@samples.mailgun.org&to=\(messageRequest.recipient)&subject=\(messageRequest.subject ?? "")&text=\(messageRequest.content)" | ||
let bodyData:Data = Data(bodyString.utf8) | ||
request.body = .bytes(ByteBuffer(bytes: bodyData)) | ||
|
||
let response = try await httpClient.execute(request, timeout: .seconds(30)) | ||
|
||
if response.status == .ok { | ||
try await httpClient.shutdown() | ||
return nil | ||
} else { | ||
try await httpClient.shutdown() | ||
return MessengerError.misconfigurationError(error: "\(response.status)") | ||
} | ||
}catch{ | ||
return MessengerError.misconfigurationError(error: "Error with credentials: \(error)") | ||
} | ||
|
||
} | ||
} | ||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// swift-tools-version:5.8 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "SMS", | ||
dependencies: [ | ||
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), | ||
], | ||
targets: [ | ||
.executableTarget( | ||
name: "SMS", | ||
dependencies: [ | ||
.product(name: "AsyncHTTPClient", package: "async-http-client"), | ||
], | ||
path: "Sources" | ||
) | ||
] | ||
) |
67 changes: 67 additions & 0 deletions
67
swift/send-message/message-types/sms/Sources/sms_message.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import AsyncHTTPClient | ||
import Foundation | ||
import NIO | ||
import NIOFoundationCompat | ||
|
||
class SMSMessenger: Messenger{ | ||
private let authToken: String | ||
private let accountSid: String | ||
private let twilioNumber: String | ||
private let httpClient: HTTPClient | ||
|
||
init(_ env_vars: [String: String]) throws{ | ||
guard let accountSid = env_vars["TWILIO_ACCOUNT_SID"], | ||
let authToken = env_vars["TWILIO_AUTH_TOKEN"], | ||
let twilioNumber = env_vars["TWILIO_SENDER"] else { | ||
throw MessengerError.misconfigurationError(error: "Missing environment variables.") | ||
} | ||
self.authToken = authToken | ||
self.accountSid = accountSid | ||
self.twilioNumber = twilioNumber | ||
self.httpClient = HTTPClient(eventLoopGroupProvider: .createNew) | ||
|
||
} | ||
/* | ||
Creates an HTTPClientRequest to send an SMS message via Twilio API. | ||
*/ | ||
private func createRequest(message: Message) async throws-> HTTPClientRequest { | ||
let targetURL: String = "https://api.twilio.com/2010-04-01/Accounts/\(accountSid)/Messages" | ||
let credentials: String = "\(accountSid):\(authToken)" | ||
var request = HTTPClientRequest(url: targetURL) | ||
request.method = .POST | ||
request.headers.add(name: "Content-Type", value: "application/x-www-form-urlencoded") | ||
request.headers.add(name:"Authorization", value: "Basic \(Data(credentials.utf8).base64EncodedString())") // credentials | ||
let bodyString: String = "From=\(twilioNumber)&Body=\(message.content)&To=\(message.recipient)" | ||
let bodyData = Data(bodyString.utf8) | ||
request.body = .bytes(ByteBuffer(bytes: bodyData)) | ||
return request | ||
} | ||
/* | ||
Sends an SMS message using the Twilio API and handles potential errors during the process. | ||
*/ | ||
public func sendMessage(messageRequest: Message) async -> Error?{ | ||
|
||
do{ | ||
let request = try await createRequest(message: messageRequest) | ||
let response: HTTPClientResponse = try await httpClient.execute(request, timeout: .seconds(30)) | ||
//messege created successfully | ||
switch response.status.code{ | ||
case 201: | ||
return nil | ||
case 400: | ||
return MessengerError.misconfigurationError(error: "Bad request in request construction") | ||
case 401: | ||
return MessengerError.validationError(error: "Unauthorized credentials, Ensure credentials are correct") | ||
case 404: | ||
return MessengerError.providerError(error: "Message couldnt be found") | ||
case 500,503,429: | ||
return MessengerError.providerError(error: "Connection timed out, try again later") | ||
default: | ||
return MessengerError.providerError(error: "Error: \(response.status)") | ||
} | ||
} | ||
catch{ | ||
return MessengerError.providerError(error: "Connection error: \(error)") | ||
} | ||
} | ||
} |