From cc95ec565ea8ffb2c8960f1dd6c7a6ee44ba5e1c Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 4 Jul 2023 10:11:51 -0300 Subject: [PATCH 1/7] Remove Get dependency --- Package.resolved | 14 --- Package.swift | 6 +- Sources/Functions/FunctionsClient.swift | 33 ++++--- .../FunctionsTests/FunctionsClientTests.swift | 92 +++++++++++++++++++ 4 files changed, 110 insertions(+), 35 deletions(-) delete mode 100644 Package.resolved create mode 100644 Tests/FunctionsTests/FunctionsClientTests.swift diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index e656aee..0000000 --- a/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "get", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Get", - "state" : { - "revision" : "12830cc64f31789ae6f4352d2d51d03a25fc3741", - "version" : "2.1.6" - } - } - ], - "version" : 2 -} diff --git a/Package.swift b/Package.swift index a44a659..8ef6246 100644 --- a/Package.swift +++ b/Package.swift @@ -14,13 +14,11 @@ let package = Package( products: [ .library(name: "Functions", targets: ["Functions"]) ], - dependencies: [ - .package(url: "https://github.com/kean/Get", from: "2.1.5") - ], + dependencies: [], targets: [ .target( name: "Functions", - dependencies: ["Get"] + dependencies: [] ), .testTarget( name: "FunctionsTests", diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index ca692b5..5db8109 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -1,23 +1,22 @@ -@preconcurrency import Foundation -@preconcurrency import Get +import Foundation public final class FunctionsClient { + public typealias FetchHandler = (URLRequest) async throws -> (Data, URLResponse) + let url: URL var headers: [String: String] - let client: APIClient + private let fetch: FetchHandler public init( url: URL, headers: [String: String] = [:], - apiClientDelegate: APIClientDelegate? = nil + fetch: @escaping FetchHandler = URLSession.shared.data(for:) ) { self.url = url self.headers = headers self.headers["X-Client-Info"] = "functions-swift/\(version)" - client = APIClient(baseURL: url) { - $0.delegate = apiClientDelegate - } + self.fetch = fetch } /// Updates the authorization header. @@ -74,21 +73,21 @@ public final class FunctionsClient { functionName: String, invokeOptions: FunctionInvokeOptions ) async throws -> (Data, HTTPURLResponse) { - let request = Request( - path: functionName, - method: invokeOptions.method.map({ HTTPMethod(rawValue: $0.rawValue) }) ?? .post, - body: invokeOptions.body, - headers: invokeOptions.headers.merging(headers) { first, _ in first } - ) + let url = self.url.appendingPathComponent(functionName) + + var urlRequest = URLRequest(url: url) + urlRequest.allHTTPHeaderFields = invokeOptions.headers.merging(headers) { first, _ in first } + urlRequest.httpMethod = invokeOptions.method?.rawValue ?? "POST + urlRequest.httpBody = invokeOptions.body - let response = try await client.data(for: request) + let (data, response) = try await fetch(urlRequest) - guard let httpResponse = response.response as? HTTPURLResponse else { + guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) } guard 200..<300 ~= httpResponse.statusCode else { - throw FunctionsError.httpError(code: httpResponse.statusCode, data: response.data) + throw FunctionsError.httpError(code: httpResponse.statusCode, data: data) } let isRelayError = httpResponse.value(forHTTPHeaderField: "x-relay-error") == "true" @@ -96,6 +95,6 @@ public final class FunctionsClient { throw FunctionsError.relayError } - return (response.data, httpResponse) + return (data, httpResponse) } } diff --git a/Tests/FunctionsTests/FunctionsClientTests.swift b/Tests/FunctionsTests/FunctionsClientTests.swift new file mode 100644 index 0000000..41ab2ed --- /dev/null +++ b/Tests/FunctionsTests/FunctionsClientTests.swift @@ -0,0 +1,92 @@ +import XCTest + +@testable import Functions + +final class FunctionsClientTests: XCTestCase { + let url = URL(string: "http://localhost:5432/functions/v1")! + let apiKey = "supabase.anon.key" + + func testInvoke() async throws { + var _request: URLRequest? + let sut = FunctionsClient(url: url, headers: ["apikey": apiKey]) { + _request = $0 + return (Data(), HTTPURLResponse.mock(url: self.url)) + } + + let body = ["name": "Supabase"] + + try await sut.invoke( + functionName: "hello_world", + invokeOptions: .init(headers: ["X-Custom-Key": "value"], body: body) + ) + + let request = try XCTUnwrap(_request) + + XCTAssertEqual(request.url, URL(string: "http://localhost:5432/functions/v1/hello_world")) + XCTAssertEqual(request.httpMethod, "POST") + XCTAssertEqual(request.value(forHTTPHeaderField: "apikey"), apiKey) + XCTAssertEqual(request.value(forHTTPHeaderField: "X-Custom-Key"), "value") + XCTAssertEqual( + request.value(forHTTPHeaderField: "X-Client-Info"), "functions-swift/\(Functions.version)") + } + + func testInvoke_shouldThrow_URLError_badServerResponse() async { + let sut = FunctionsClient(url: url) { _ in + (Data(), URLResponse()) + } + + do { + try await sut.invoke(functionName: "hello_world") + } catch let urlError as URLError { + XCTAssertEqual(urlError.code, .badServerResponse) + } catch { + XCTFail("Unexpected error thrown \(error)") + } + } + + func testInvoke_shouldThrow_FunctionsError_httpError() async { + let sut = FunctionsClient(url: url) { _ in + ( + "error".data(using: .utf8)!, + HTTPURLResponse.mock(url: self.url, statusCode: 300) + ) + } + + do { + try await sut.invoke(functionName: "hello_world") + XCTFail("Invoke should fail.") + } catch let FunctionsError.httpError(code, data) { + XCTAssertEqual(code, 300) + XCTAssertEqual(data, "error".data(using: .utf8)) + } catch { + XCTFail("Unexpected error thrown \(error)") + } + } + + func testInvoke_shouldThrow_FunctionsError_relayError() async { + let sut = FunctionsClient(url: url) { _ in + ( + Data(), + HTTPURLResponse.mock(url: self.url, headerFields: ["x-relay-error": "true"]) + ) + } + + do { + try await sut.invoke(functionName: "hello_world") + XCTFail("Invoke should fail.") + } catch FunctionsError.relayError { + } catch { + XCTFail("Unexpected error thrown \(error)") + } + } +} + +extension HTTPURLResponse { + static func mock( + url: URL, + statusCode: Int = 200, + headerFields: [String: String]? = nil + ) -> HTTPURLResponse { + HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: nil, headerFields: headerFields)! + } +} From 6fae8883fbdd61a2345f80563db4871389df6e2f Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Mon, 10 Jul 2023 08:55:30 -0300 Subject: [PATCH 2/7] Use URLSession --- Package.resolved | 14 +++++ Package.swift | 11 +++- Sources/Functions/FunctionsClient.swift | 13 ++-- .../FunctionsTests/FunctionsClientTests.swift | 59 ++++++++++--------- scripts/check-for-breaking-api-changes.sh | 42 +++++++++++++ 5 files changed, 99 insertions(+), 40 deletions(-) create mode 100644 Package.resolved create mode 100644 scripts/check-for-breaking-api-changes.sh diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..49ba33f --- /dev/null +++ b/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "mocker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/WeTransfer/Mocker", + "state" : { + "revision" : "4384e015cae4916a6828252467a4437173c7ae17", + "version" : "3.0.1" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift index 8ef6246..280af2b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 5.8 import PackageDescription @@ -14,7 +14,9 @@ let package = Package( products: [ .library(name: "Functions", targets: ["Functions"]) ], - dependencies: [], + dependencies: [ + .package(url: "https://github.com/WeTransfer/Mocker", from: "3.0.1") + ], targets: [ .target( name: "Functions", @@ -22,7 +24,10 @@ let package = Package( ), .testTarget( name: "FunctionsTests", - dependencies: ["Functions"] + dependencies: [ + "Functions", + "Mocker", + ] ), ] ) diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index 5db8109..abe522f 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -1,22 +1,19 @@ import Foundation -public final class FunctionsClient { - public typealias FetchHandler = (URLRequest) async throws -> (Data, URLResponse) - +public actor FunctionsClient { let url: URL var headers: [String: String] - - private let fetch: FetchHandler + let session: URLSession public init( url: URL, headers: [String: String] = [:], - fetch: @escaping FetchHandler = URLSession.shared.data(for:) + session: URLSession = .shared ) { self.url = url self.headers = headers self.headers["X-Client-Info"] = "functions-swift/\(version)" - self.fetch = fetch + self.session = session } /// Updates the authorization header. @@ -80,7 +77,7 @@ public final class FunctionsClient { urlRequest.httpMethod = invokeOptions.method?.rawValue ?? "POST urlRequest.httpBody = invokeOptions.body - let (data, response) = try await fetch(urlRequest) + let (data, response) = try await session.data(for: urlRequest) guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) diff --git a/Tests/FunctionsTests/FunctionsClientTests.swift b/Tests/FunctionsTests/FunctionsClientTests.swift index 41ab2ed..fc45b70 100644 --- a/Tests/FunctionsTests/FunctionsClientTests.swift +++ b/Tests/FunctionsTests/FunctionsClientTests.swift @@ -1,3 +1,4 @@ +import Mocker import XCTest @testable import Functions @@ -6,12 +7,15 @@ final class FunctionsClientTests: XCTestCase { let url = URL(string: "http://localhost:5432/functions/v1")! let apiKey = "supabase.anon.key" + lazy var sut = FunctionsClient(url: url, headers: ["apikey": apiKey]) + func testInvoke() async throws { + let url = URL(string: "http://localhost:5432/functions/v1/hello_world")! var _request: URLRequest? - let sut = FunctionsClient(url: url, headers: ["apikey": apiKey]) { - _request = $0 - return (Data(), HTTPURLResponse.mock(url: self.url)) - } + + var mock = Mock(url: url, dataType: .json, statusCode: 200, data: [.post: Data()]) + mock.onRequestHandler = .init { _request = $0 } + mock.register() let body = ["name": "Supabase"] @@ -22,18 +26,22 @@ final class FunctionsClientTests: XCTestCase { let request = try XCTUnwrap(_request) - XCTAssertEqual(request.url, URL(string: "http://localhost:5432/functions/v1/hello_world")) + XCTAssertEqual(request.url, url) XCTAssertEqual(request.httpMethod, "POST") XCTAssertEqual(request.value(forHTTPHeaderField: "apikey"), apiKey) XCTAssertEqual(request.value(forHTTPHeaderField: "X-Custom-Key"), "value") XCTAssertEqual( - request.value(forHTTPHeaderField: "X-Client-Info"), "functions-swift/\(Functions.version)") + request.value(forHTTPHeaderField: "X-Client-Info"), + "functions-swift/\(Functions.version)" + ) } func testInvoke_shouldThrow_URLError_badServerResponse() async { - let sut = FunctionsClient(url: url) { _ in - (Data(), URLResponse()) - } + let url = URL(string: "http://localhost:5432/functions/v1/hello_world")! + let mock = Mock( + url: url, dataType: .json, statusCode: 200, data: [.post: Data()], + requestError: URLError(.badServerResponse)) + mock.register() do { try await sut.invoke(functionName: "hello_world") @@ -45,12 +53,10 @@ final class FunctionsClientTests: XCTestCase { } func testInvoke_shouldThrow_FunctionsError_httpError() async { - let sut = FunctionsClient(url: url) { _ in - ( - "error".data(using: .utf8)!, - HTTPURLResponse.mock(url: self.url, statusCode: 300) - ) - } + let url = URL(string: "http://localhost:5432/functions/v1/hello_world")! + let mock = Mock( + url: url, dataType: .json, statusCode: 300, data: [.post: "error".data(using: .utf8)!]) + mock.register() do { try await sut.invoke(functionName: "hello_world") @@ -64,12 +70,11 @@ final class FunctionsClientTests: XCTestCase { } func testInvoke_shouldThrow_FunctionsError_relayError() async { - let sut = FunctionsClient(url: url) { _ in - ( - Data(), - HTTPURLResponse.mock(url: self.url, headerFields: ["x-relay-error": "true"]) - ) - } + let url = URL(string: "http://localhost:5432/functions/v1/hello_world")! + let mock = Mock( + url: url, dataType: .json, statusCode: 200, data: [.post: Data()], + additionalHeaders: ["x-relay-error": "true"]) + mock.register() do { try await sut.invoke(functionName: "hello_world") @@ -79,14 +84,10 @@ final class FunctionsClientTests: XCTestCase { XCTFail("Unexpected error thrown \(error)") } } -} -extension HTTPURLResponse { - static func mock( - url: URL, - statusCode: Int = 200, - headerFields: [String: String]? = nil - ) -> HTTPURLResponse { - HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: nil, headerFields: headerFields)! + func test_setAuth() async { + await sut.setAuth(token: "access.token") + let headers = await sut.headers + XCTAssertEqual(headers["Authorization"], "Bearer access.token") } } diff --git a/scripts/check-for-breaking-api-changes.sh b/scripts/check-for-breaking-api-changes.sh new file mode 100644 index 0000000..d2ce981 --- /dev/null +++ b/scripts/check-for-breaking-api-changes.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## + +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$(git -C "${CURRENT_SCRIPT_DIR}" rev-parse --show-toplevel)" + +log "Checking required environment variables..." +test -n "${BASELINE_REPO_URL:-}" || fatal "BASELINE_REPO_URL unset" +test -n "${BASELINE_TREEISH:-}" || fatal "BASELINE_TREEISH unset" + +log "Fetching baseline: ${BASELINE_REPO_URL}#${BASELINE_TREEISH}..." +git -C "${REPO_ROOT}" fetch "${BASELINE_REPO_URL}" "${BASELINE_TREEISH}" +BASELINE_COMMIT=$(git -C "${REPO_ROOT}" rev-parse FETCH_HEAD) + +log "Checking for API changes since ${BASELINE_REPO_URL}#${BASELINE_TREEISH} (${BASELINE_COMMIT})..." +swift package --package-path "${REPO_ROOT}" diagnose-api-breaking-changes \ + "${BASELINE_COMMIT}" \ + && RC=$? || RC=$? + +if [ "${RC}" -ne 0 ]; then + fatal "❌ Breaking API changes detected." + exit "${RC}" +fi +log "✅ No breaking API changes detected." From 3d0b7f083af39a071283e4e71901fdcdbc50f3a5 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 13 Jul 2023 09:33:37 -0300 Subject: [PATCH 3/7] Revert to FetchHandler --- Sources/Functions/FunctionsClient.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index abe522f..1fd1050 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -1,19 +1,23 @@ import Foundation public actor FunctionsClient { + public typealias FetchHandler = @Sendable (_ request: URLRequest) async throws -> ( + Data, URLResponse + ) + let url: URL var headers: [String: String] - let session: URLSession + let fetch: FetchHandler public init( url: URL, headers: [String: String] = [:], - session: URLSession = .shared + fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) } ) { self.url = url self.headers = headers self.headers["X-Client-Info"] = "functions-swift/\(version)" - self.session = session + self.fetch = fetch } /// Updates the authorization header. @@ -77,7 +81,7 @@ public actor FunctionsClient { urlRequest.httpMethod = invokeOptions.method?.rawValue ?? "POST urlRequest.httpBody = invokeOptions.body - let (data, response) = try await session.data(for: urlRequest) + let (data, response) = try await fetch(urlRequest) guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) From 8c7425d9ec5ad6b3fb7e8787889af55ee3c927fd Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Fri, 14 Jul 2023 05:35:20 -0300 Subject: [PATCH 4/7] Documentation --- Sources/Functions/FunctionsClient.swift | 55 +++++++++++++++---------- Sources/Functions/Types.swift | 21 +++++++++- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index 1fd1050..c5bf1e2 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -1,14 +1,25 @@ import Foundation +/// An actor representing a client for invoking functions. public actor FunctionsClient { + /// Typealias for the fetch handler used to make requests. public typealias FetchHandler = @Sendable (_ request: URLRequest) async throws -> ( Data, URLResponse ) + /// The base URL for the functions. let url: URL + /// Headers to be included in the requests. var headers: [String: String] + /// The fetch handler used to make requests. let fetch: FetchHandler + /// Initializes a new instance of `FunctionsClient`. + /// + /// - Parameters: + /// - url: The base URL for the functions. + /// - headers: Headers to be included in the requests. (Default: empty dictionary) + /// - fetch: The fetch handler used to make requests. (Default: URLSession.shared.data(for:)) public init( url: URL, headers: [String: String] = [:], @@ -21,53 +32,56 @@ public actor FunctionsClient { } /// Updates the authorization header. - /// - Parameter token: the new JWT token sent in the authorization header + /// + /// - Parameter token: The new JWT token sent in the authorization header. public func setAuth(token: String) { headers["Authorization"] = "Bearer \(token)" } - /// Invokes a function. + /// Invokes a function and decodes the response. + /// /// - Parameters: - /// - functionName: the name of the function to invoke. + /// - functionName: The name of the function to invoke. + /// - invokeOptions: Options for invoking the function. (Default: empty `FunctionInvokeOptions`) + /// - decode: A closure to decode the response data and HTTPURLResponse into a `Response` object. + /// - Returns: The decoded `Response` object. public func invoke( functionName: String, invokeOptions: FunctionInvokeOptions = .init(), decode: (Data, HTTPURLResponse) throws -> Response ) async throws -> Response { let (data, response) = try await rawInvoke( - functionName: functionName, - invokeOptions: invokeOptions - ) + functionName: functionName, invokeOptions: invokeOptions) return try decode(data, response) } - /// Invokes a function. + /// Invokes a function and decodes the response as a specific type. + /// /// - Parameters: - /// - functionName: the name of the function to invoke. + /// - functionName: The name of the function to invoke. + /// - invokeOptions: Options for invoking the function. (Default: empty `FunctionInvokeOptions`) + /// - decoder: The JSON decoder to use for decoding the response. (Default: `JSONDecoder()`) + /// - Returns: The decoded object of type `T`. public func invoke( functionName: String, invokeOptions: FunctionInvokeOptions = .init(), decoder: JSONDecoder = JSONDecoder() ) async throws -> T { - try await invoke( - functionName: functionName, - invokeOptions: invokeOptions, - decode: { data, _ in try decoder.decode(T.self, from: data) } - ) + try await invoke(functionName: functionName, invokeOptions: invokeOptions) { data, _ in + try decoder.decode(T.self, from: data) + } } - /// Invokes a function. + /// Invokes a function without expecting a response. + /// /// - Parameters: - /// - functionName: the name of the function to invoke. + /// - functionName: The name of the function to invoke. + /// - invokeOptions: Options for invoking the function. (Default: empty `FunctionInvokeOptions`) public func invoke( functionName: String, invokeOptions: FunctionInvokeOptions = .init() ) async throws { - try await invoke( - functionName: functionName, - invokeOptions: invokeOptions, - decode: { _, _ in () } - ) + try await invoke(functionName: functionName, invokeOptions: invokeOptions) { _, _ in () } } private func rawInvoke( @@ -75,7 +89,6 @@ public actor FunctionsClient { invokeOptions: FunctionInvokeOptions ) async throws -> (Data, HTTPURLResponse) { let url = self.url.appendingPathComponent(functionName) - var urlRequest = URLRequest(url: url) urlRequest.allHTTPHeaderFields = invokeOptions.headers.merging(headers) { first, _ in first } urlRequest.httpMethod = invokeOptions.method?.rawValue ?? "POST diff --git a/Sources/Functions/Types.swift b/Sources/Functions/Types.swift index 6770a87..2bf9810 100644 --- a/Sources/Functions/Types.swift +++ b/Sources/Functions/Types.swift @@ -1,22 +1,36 @@ import Foundation +/// An error type representing various errors that can occur while invoking functions. public enum FunctionsError: Error, LocalizedError { + /// Error indicating a relay error while invoking the Edge Function. case relayError + /// Error indicating a non-2xx status code returned by the Edge Function. case httpError(code: Int, data: Data) + /// A localized description of the error. public var errorDescription: String? { switch self { - case .relayError: return "Relay Error invoking the Edge Function" - case let .httpError(code, _): return "Edge Function returned a non-2xx status code: \(code)" + case .relayError: + return "Relay Error invoking the Edge Function" + case let .httpError(code, _): + return "Edge Function returned a non-2xx status code: \(code)" } } } +/// Options for invoking a function. public struct FunctionInvokeOptions { let method: Method? + /// Headers to be included in the function invocation. let headers: [String: String] + /// Body data to be sent with the function invocation. let body: Data? + /// Initializes the `FunctionInvokeOptions` structure. + /// + /// - Parameters: + /// - headers: Headers to be included in the function invocation. (Default: empty dictionary) + /// - body: The body data to be sent with the function invocation. (Default: nil) public init(method: Method? = nil, headers: [String: String] = [:], body: some Encodable) { var headers = headers @@ -37,6 +51,9 @@ public struct FunctionInvokeOptions { self.headers = headers } + /// Initializes the `FunctionInvokeOptions` structure. + /// + /// - Parameter headers: Headers to be included in the function invocation. (Default: empty dictionary) public init(method: Method? = nil, headers: [String: String] = [:]) { self.method = method self.headers = headers From 755678340eb35278f6130dc5f9b9980d5cb94782 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Fri, 14 Jul 2023 05:39:58 -0300 Subject: [PATCH 5/7] CI --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++++ Makefile | 11 +++++++++++ 2 files changed, 40 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5db18f4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - "*" + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + library: + runs-on: macos-latest + strategy: + matrix: + platform: + - iOS Simulator,name=iPhone 14 Pro Max + - macOS + - tvOS Simulator,name=Apple TV + - watchOS Simulator,name=Apple Watch Series 7 (45mm) + + steps: + - uses: actions/checkout@v3 + - name: Run tests + run: PLATFORM="${{ matrix.platform }}" make test-library diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fac1f75 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +PLATFORM ?= iOS Simulator,name=iPhone 14 Pro Max + +.PHONY: test-library +test-library: + xcodebuild test \ + -scheme functions-swift \ + -destination platform="$(PLATFORM)" + +.PHONY: format +format: + swift format -i -r ./Sources ./Tests ./Package.swift From 1a4d03ab7987a5a976b3c4cc925001e5a3e19a22 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Fri, 14 Jul 2023 08:36:33 -0300 Subject: [PATCH 6/7] Set swift-version-tools to 5.7 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 280af2b..4c0b733 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 5.7 import PackageDescription From 9b5bf6750850abec767dbca9ff93b742f0c2b289 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 17 Oct 2023 17:02:33 -0300 Subject: [PATCH 7/7] Fix after rebase --- Sources/Functions/FunctionsClient.swift | 2 +- Sources/Functions/Types.swift | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index c5bf1e2..962eb12 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -91,7 +91,7 @@ public actor FunctionsClient { let url = self.url.appendingPathComponent(functionName) var urlRequest = URLRequest(url: url) urlRequest.allHTTPHeaderFields = invokeOptions.headers.merging(headers) { first, _ in first } - urlRequest.httpMethod = invokeOptions.method?.rawValue ?? "POST + urlRequest.httpMethod = (invokeOptions.method ?? .post).rawValue urlRequest.httpBody = invokeOptions.body let (data, response) = try await fetch(urlRequest) diff --git a/Sources/Functions/Types.swift b/Sources/Functions/Types.swift index 2bf9810..6ac72b1 100644 --- a/Sources/Functions/Types.swift +++ b/Sources/Functions/Types.swift @@ -20,6 +20,7 @@ public enum FunctionsError: Error, LocalizedError { /// Options for invoking a function. public struct FunctionInvokeOptions { + /// Method to use in the function invocation. let method: Method? /// Headers to be included in the function invocation. let headers: [String: String] @@ -27,8 +28,9 @@ public struct FunctionInvokeOptions { let body: Data? /// Initializes the `FunctionInvokeOptions` structure. - /// + /// /// - Parameters: + /// - method: Method to use in the function invocation. /// - headers: Headers to be included in the function invocation. (Default: empty dictionary) /// - body: The body data to be sent with the function invocation. (Default: nil) public init(method: Method? = nil, headers: [String: String] = [:], body: some Encodable) { @@ -53,7 +55,9 @@ public struct FunctionInvokeOptions { /// Initializes the `FunctionInvokeOptions` structure. /// - /// - Parameter headers: Headers to be included in the function invocation. (Default: empty dictionary) + /// - Parameters: + /// - method: Method to use in the function invocation. + /// - headers: Headers to be included in the function invocation. (Default: empty dictionary) public init(method: Method? = nil, headers: [String: String] = [:]) { self.method = method self.headers = headers