Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bindings for EndpointsRuleEngine #79

Merged
merged 33 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ jobs:
- uses: actions/checkout@v1
- name: GitHub Action for SwiftLint
uses: norio-nomura/action-swiftlint@3.1.0
- name: GitHub Action for SwiftLint with --strict
uses: norio-nomura/action-swiftlint@3.1.0
with:
args: --strict
Comment on lines 20 to -26
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strict is ignoring the .swiftlint config file and redundant to the line 20 after removing strict.

- name: GitHub Action for SwiftLint (Only files changed in the PR)
uses: norio-nomura/action-swiftlint@3.1.0
env:
Expand Down
4 changes: 4 additions & 0 deletions Source/AwsCommonRuntimeKit/crt/AwsError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ public struct AWSError {
self.errorMessage = nil
}
}

public static func makeFromLastError() -> AWSError {
return AWSError(errorCode: aws_last_error())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0.

import AwsCSdkUtils

/// Request context used for resolving endpoint
public class CRTAWSEndpointsRequestContext {
let rawValue: UnsafeMutablePointer<aws_endpoints_request_context>

/// Initialize a new request context
/// - Parameter allocator: Allocator to use for request context creation
public init(allocator: Allocator = defaultAllocator) throws {
guard let rawValue = aws_endpoints_request_context_new(allocator.rawValue) else {
throw CRTError.crtError(AWSError.makeFromLastError())
}

self.rawValue = rawValue
}

/// Add a string endpoint parameter to the request context
/// - Parameters:
/// - name: The name of the parameter
/// - value: The value of the parameter
/// - allocator: The allocator to use for the parameter
public func add(name: String, value: String?, allocator: Allocator = defaultAllocator) throws {
guard let value = value else {
return
}
waahm7 marked this conversation as resolved.
Show resolved Hide resolved
let success = aws_endpoints_request_context_add_string(allocator.rawValue,
rawValue,
name.awsByteCursor,
value.awsByteCursor)
if success != 0 {
throw CRTError.crtError(AWSError.makeFromLastError())
}
}

/// Add a bool endpoint parameter to the request context
/// - Parameters:
/// - name: The name of the parameter
/// - value: The value of the parameter
/// - allocator: The allocator to use for the parameter
public func add(name: String, value: Bool?, allocator: Allocator = defaultAllocator) throws {
guard let value = value else {
return
}
let success = aws_endpoints_request_context_add_boolean(allocator.rawValue, rawValue, name.awsByteCursor, value)
if success != 0 {
throw CRTError.crtError(AWSError.makeFromLastError())
}
}

deinit {
aws_endpoints_request_context_release(rawValue)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0.

import AwsCSdkUtils
import Foundation

/// Resolved endpoint
public class CRTAWSEndpointResolvedEndpoint {
let rawValue: OpaquePointer

/// Initialize a new resolved endpoint
/// - Parameter rawValue: The raw value of the resolved endpoint
internal init(rawValue: OpaquePointer) {
self.rawValue = rawValue

aws_endpoints_resolved_endpoint_acquire(rawValue)
}

/// Get the type of the resolved endpoint
/// - Returns: The type of the resolved endpoint
public func getType() -> CRTAWSEndpointsResolvedEndpointType {
let type = aws_endpoints_resolved_endpoint_get_type(rawValue)
return CRTAWSEndpointsResolvedEndpointType(rawValue: type)
}

/// Get the URL of the resolved endpoint
/// - Returns: The URL of the resolved endpoint
public func getURL() throws -> String? {
ganeshnj marked this conversation as resolved.
Show resolved Hide resolved
let urlOut = UnsafeMutablePointer<aws_byte_cursor>.allocate(capacity: 1)
defer {
urlOut.deallocate()
}
let success = aws_endpoints_resolved_endpoint_get_url(rawValue, urlOut)
if success != 0 {
throw CRTError.crtError(AWSError.makeFromLastError())
}
return urlOut.pointee.toString()
}

/// Get the properties of the resolved endpoint
/// - Returns: The properties of the resolved endpoint
public func getProperties() throws -> [String: AnyHashable]? {
ganeshnj marked this conversation as resolved.
Show resolved Hide resolved
let propsOut = UnsafeMutablePointer<aws_byte_cursor>.allocate(capacity: 1)
ganeshnj marked this conversation as resolved.
Show resolved Hide resolved
defer {
propsOut.deallocate()
}
let success = aws_endpoints_resolved_endpoint_get_properties(rawValue, propsOut)
if success != 0 {
throw CRTError.crtError(AWSError.makeFromLastError())
}
guard let data = propsOut.pointee.toString()?.data(using: .utf8) else {
return nil
}
return try JSONDecoder().decode([String: EndpointProperty].self, from: data).toStringHashableDictionary()
}

/// Get the error of the resolved endpoint
/// - Parameter allocator: The allocator to use for the error
public func getError() throws -> String? {
let errorOut = UnsafeMutablePointer<aws_byte_cursor>.allocate(capacity: 1)
ganeshnj marked this conversation as resolved.
Show resolved Hide resolved
defer {
errorOut.deallocate()
}
let success = aws_endpoints_resolved_endpoint_get_error(rawValue, errorOut)
if success != 0 {
throw CRTError.crtError(AWSError.makeFromLastError())
}
return errorOut.pointee.toString()
}

/// Get headers of the resolved endpoint
/// - Returns: The headers of the resolved endpoint
public func getHeaders() throws -> [String: [String]]? {
ganeshnj marked this conversation as resolved.
Show resolved Hide resolved
let headersOut: UnsafeMutablePointer<UnsafePointer<aws_hash_table>?>
= UnsafeMutablePointer<UnsafePointer<aws_hash_table>?>.allocate(capacity: 1)
ganeshnj marked this conversation as resolved.
Show resolved Hide resolved
defer {
headersOut.deallocate()
}
let success = aws_endpoints_resolved_endpoint_get_headers(rawValue, headersOut)
if success != 0 {
throw CRTError.crtError(AWSError.makeFromLastError())
}

var headers: [String: [String]] = [:]
var iter = aws_hash_iter_begin(headersOut.pointee)

while !aws_hash_iter_done(&iter) {
// Get the key
let keyPtr = iter.element.key.bindMemory(to: aws_string.self, capacity: 1)
guard let key = String(awsString: keyPtr) else {
throw CRTError.stringConversionError(keyPtr)
}

// Get the value
let arrayPtr = iter.element.value.bindMemory(to: aws_array_list.self, capacity: 1)
var array: [String] = []
for index in 0..<aws_array_list_length(arrayPtr) {
var valPtr: UnsafeMutableRawPointer! = nil
defer {
valPtr?.deallocate()
}
aws_array_list_get_at(arrayPtr, &valPtr, index)
let strPtr = valPtr.bindMemory(to: aws_string.self, capacity: 1)
guard let val = String(awsString: strPtr) else {
throw CRTError.stringConversionError(strPtr)
}
array.append(val)
}

headers[key] = array
aws_hash_iter_next(&iter)
}

return headers
}

deinit {
aws_endpoints_resolved_endpoint_release(rawValue)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0.

import AwsCSdkUtils

/// Resolved endpoint type
public enum CRTAWSEndpointsResolvedEndpointType {
/// Used for endpoints that are resolved successfully
case endpoint
/// Used for endpoints that resolve to an error
case error
}

extension CRTAWSEndpointsResolvedEndpointType: RawRepresentable, CaseIterable {
public init(rawValue: aws_endpoints_resolved_endpoint_type) {
let value = Self.allCases.first(where: {$0.rawValue == rawValue})
self = value ?? .endpoint
}

public var rawValue: aws_endpoints_resolved_endpoint_type {
switch self {
case .endpoint:
return AWS_ENDPOINTS_RESOLVED_ENDPOINT
case .error:
return AWS_ENDPOINTS_RESOLVED_ERROR
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0.

import AwsCSdkUtils

/// Rule engine for matching endpoint rules
public class CRTAWSEndpointsRuleEngine {
let partitions: UnsafeMutablePointer<aws_partitions_config>
let ruleSet: UnsafeMutablePointer<aws_endpoints_ruleset>
let rawValue: OpaquePointer

/// Initialize a new rule engine
/// - Parameters:
/// - partitions: JSON string containing partition data
/// - ruleSet: The rule set string to use for the rule engine
/// - allocator: The allocator to use for creating rule engine
public init(partitions: String, ruleSet: String, allocator: Allocator = defaultAllocator) throws {
guard let partitions = aws_partitions_config_new_from_string(allocator.rawValue, partitions.newByteCursor().rawValue),
let ruleSet = aws_endpoints_ruleset_new_from_string(allocator.rawValue, ruleSet.newByteCursor().rawValue),
let rawValue = aws_endpoints_rule_engine_new(allocator.rawValue, ruleSet, partitions) else {
throw CRTError.crtError(AWSError.makeFromLastError())
}

self.partitions = partitions
self.ruleSet = ruleSet
self.rawValue = rawValue
}

/// Resolve an endpoint from the rule engine using the provided request context
/// - Parameter context: The request context to use for endpoint resolution
/// - Returns: The resolved endpoint
public func resolve(context: CRTAWSEndpointsRequestContext) throws -> CRTAWSEndpointResolvedEndpoint? {
let resolvedEndpoint: UnsafeMutablePointer<OpaquePointer?>? = UnsafeMutablePointer.allocate(capacity: 1)
ganeshnj marked this conversation as resolved.
Show resolved Hide resolved
defer {
resolvedEndpoint?.deallocate()
}
let success = aws_endpoints_rule_engine_resolve(rawValue, context.rawValue, resolvedEndpoint)
if success != 0 {
throw CRTError.crtError(AWSError.makeFromLastError())
}

guard let pointee = resolvedEndpoint?.pointee else {
return nil
}

return CRTAWSEndpointResolvedEndpoint(rawValue: pointee)
}

deinit {
aws_partitions_config_release(partitions)
aws_endpoints_rule_engine_release(rawValue)
aws_endpoints_ruleset_release(ruleSet)
}
}
110 changes: 110 additions & 0 deletions Source/AwsCommonRuntimeKit/sdkutils/EndpointProperty.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0.

/// Struct that represents endpoint property which can be a boolean, string or array of endpoint properties
internal enum EndpointProperty {
case bool(Bool)
case string(String)
indirect case array([EndpointProperty])
indirect case dictionary([String: EndpointProperty])

func toAnyHashable() -> AnyHashable {
switch self {
case .bool(let value):
return AnyHashable(value)
case .string(let value):
return AnyHashable(value)
case .array(let value):
return AnyHashable(value.map { $0.toAnyHashable() })
case .dictionary(let value):
return AnyHashable(value.mapValues { $0.toAnyHashable() })
}
}
}

/// Decodable conformance
extension EndpointProperty: Decodable {
init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: EndpointPropertyCodingKeys.self) {
self = EndpointProperty(from: container)
} else if let container = try? decoder.unkeyedContainer() {
self = EndpointProperty(from: container)
} else if let container = try? decoder.singleValueContainer() {
self = EndpointProperty(from: container)
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: ""))
}
}

init(from container: KeyedDecodingContainer<EndpointPropertyCodingKeys>) {
var dict: [String: EndpointProperty] = [:]
for key in container.allKeys {
if let value = try? container.decode(Bool.self, forKey: key) {
dict[key.stringValue] = .bool(value)
} else if let value = try? container.decode(String.self, forKey: key) {
dict[key.stringValue] = .string(value)
} else if let value = try? container.nestedContainer(keyedBy: EndpointPropertyCodingKeys.self, forKey: key) {
dict[key.stringValue] = EndpointProperty(from: value)
} else if let value = try? container.nestedUnkeyedContainer(forKey: key) {
dict[key.stringValue] = EndpointProperty(from: value)
}
}
self = .dictionary(dict)
}

init(from container: UnkeyedDecodingContainer) {
var container = container
var arr: [EndpointProperty] = []
while !container.isAtEnd {
if let value = try? container.decode(Bool.self) {
arr.append(.bool(value))
} else if let value = try? container.decode(String.self) {
arr.append(.string(value))
} else if let value = try? container.nestedContainer(keyedBy: EndpointPropertyCodingKeys.self) {
arr.append(EndpointProperty(from: value))
} else if let value = try? container.nestedUnkeyedContainer() {
arr.append(EndpointProperty(from: value))
}
}
self = .array(arr)
}

init(from container: SingleValueDecodingContainer) {
if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else {
assertionFailure("Invalid EndpointProperty")
self = .string("")
}
}
}

extension Dictionary where Key == String, Value == EndpointProperty {
/// Converts EndpointProperty to a dictionary of `String`: `AnyHashable`
/// - Returns: Dictionary of `String`: `AnyHashable`
func toStringHashableDictionary() -> [String: AnyHashable] {
var dict: [String: AnyHashable] = [:]
for (key, value) in self {
dict[key] = value.toAnyHashable()
}
return dict
}
}

/// Coding keys for `EndpointProperty`
internal struct EndpointPropertyCodingKeys: CodingKey {
var stringValue: String

init?(stringValue: String) {
self.stringValue = stringValue
}

var intValue: Int?

init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
Loading