diff --git a/.github/workflows/Ngrokit.yml b/.github/workflows/Ngrokit.yml index 5030c4c..81c3356 100644 --- a/.github/workflows/Ngrokit.yml +++ b/.github/workflows/Ngrokit.yml @@ -37,6 +37,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files with: + search-paths: ./Packages/Ngrokit/.build fail-on-empty-output: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 @@ -100,6 +101,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-spm with: + search-paths: ./Packages/Ngrokit/.build fail-on-empty-output: true - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 diff --git a/.github/workflows/SublimationBonjour.yml b/.github/workflows/SublimationBonjour.yml index 1382ded..01e2bf4 100644 --- a/.github/workflows/SublimationBonjour.yml +++ b/.github/workflows/SublimationBonjour.yml @@ -37,6 +37,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files with: + search-paths: ./Packages/SublimationBonjour/.build fail-on-empty-output: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 @@ -100,6 +101,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-spm with: + search-paths: ./Packages/SublimationBonjour/.build fail-on-empty-output: true - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 @@ -113,7 +115,7 @@ jobs: run: ./scripts/lint.sh if: startsWith(matrix.xcode,'/Applications/Xcode_16.0') - name: Run iOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-iOS with: @@ -127,7 +129,7 @@ jobs: files: ${{ join(fromJSON(steps.coverage-files-iOS.outputs.files), ',') }} flags: iOS,iOS${{ matrix.iOSVersion }},macOS,${{ env.XCODE_NAME }} - name: Run watchOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-watchOS with: diff --git a/.github/workflows/SublimationNgrok.yml b/.github/workflows/SublimationNgrok.yml index 8e2092c..76c80f0 100644 --- a/.github/workflows/SublimationNgrok.yml +++ b/.github/workflows/SublimationNgrok.yml @@ -37,6 +37,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files with: + search-paths: ./Packages/SublimationNgrok/.build fail-on-empty-output: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 @@ -100,6 +101,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-spm with: + search-paths: ./Packages/SublimationNgrok/.build fail-on-empty-output: true - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 @@ -113,7 +115,7 @@ jobs: run: ./scripts/lint.sh if: startsWith(matrix.xcode,'/Applications/Xcode_16.0') - name: Run iOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-iOS with: @@ -127,7 +129,7 @@ jobs: files: ${{ join(fromJSON(steps.coverage-files-iOS.outputs.files), ',') }} flags: iOS,iOS${{ matrix.iOSVersion }},macOS,${{ env.XCODE_NAME }} - name: Run watchOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-watchOS with: diff --git a/.github/workflows/SublimationService.yml b/.github/workflows/SublimationService.yml index 4dbfbdf..244d73f 100644 --- a/.github/workflows/SublimationService.yml +++ b/.github/workflows/SublimationService.yml @@ -37,6 +37,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files with: + search-paths: ./Packages/SublimationService/.build fail-on-empty-output: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 @@ -100,6 +101,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-spm with: + search-paths: ./Packages/SublimationService/.build fail-on-empty-output: true - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 @@ -113,7 +115,7 @@ jobs: run: ./scripts/lint.sh if: startsWith(matrix.xcode,'/Applications/Xcode_16.0') - name: Run iOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-iOS with: @@ -127,7 +129,7 @@ jobs: files: ${{ join(fromJSON(steps.coverage-files-iOS.outputs.files), ',') }} flags: iOS,iOS${{ matrix.iOSVersion }},macOS,${{ env.XCODE_NAME }} - name: Run watchOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-watchOS with: diff --git a/.github/workflows/SublimationVapor.yml b/.github/workflows/SublimationVapor.yml index f42f05c..a72f6ce 100644 --- a/.github/workflows/SublimationVapor.yml +++ b/.github/workflows/SublimationVapor.yml @@ -36,6 +36,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files with: + search-paths: ./Packages/SublimationVapor/.build fail-on-empty-output: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 @@ -99,6 +100,7 @@ jobs: - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-spm with: + search-paths: ./Packages/SublimationVapor/.build fail-on-empty-output: true - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 @@ -112,7 +114,7 @@ jobs: run: ./scripts/lint.sh if: startsWith(matrix.xcode,'/Applications/Xcode_16.0') - name: Run iOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "iphonesimulator" -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-iOS with: @@ -126,7 +128,7 @@ jobs: files: ${{ join(fromJSON(steps.coverage-files-iOS.outputs.files), ',') }} flags: iOS,iOS${{ matrix.iOSVersion }},macOS,${{ env.XCODE_NAME }} - name: Run watchOS target tests - run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }}-Package -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test + run: xcodebuild test -scheme ${{ env.PACKAGE_NAME }} -sdk "watchsimulator" -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test - uses: sersoft-gmbh/swift-coverage-action@v4 id: coverage-files-watchOS with: diff --git a/.gitignore b/.gitignore index 8691f7b..4d859cd 100644 --- a/.gitignore +++ b/.gitignore @@ -142,3 +142,5 @@ xcuserdata !Demo/SublimationDemoApp.xcodeproj .mint # End of https://www.toptal.com/developers/gitignore/api/swift,swiftpm,swiftpackagemanager,xcode,macos + +test_output.log \ No newline at end of file diff --git a/Demo/SublimationDemoServer/Package.resolved b/Demo/SublimationDemoServer/Package.resolved index 2fe6b27..768170a 100644 --- a/Demo/SublimationDemoServer/Package.resolved +++ b/Demo/SublimationDemoServer/Package.resolved @@ -36,33 +36,6 @@ "version" : "4.5.2" } }, - { - "identity" : "prch", - "kind" : "remoteSourceControl", - "location" : "https://github.com/brightdigit/Prch.git", - "state" : { - "revision" : "600b49cc7a77271ea3d2a9f9e449be081562a087", - "version" : "0.2.1" - } - }, - { - "identity" : "prchnio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/brightdigit/PrchNIO.git", - "state" : { - "revision" : "337d0b2f77b0c4ad5b5b3bad6768a9ecd70b003c", - "version" : "0.2.0-beta.1" - } - }, - { - "identity" : "prchvapor", - "kind" : "remoteSourceControl", - "location" : "https://github.com/brightdigit/PrchVapor.git", - "state" : { - "revision" : "8c19c645f9c2763014885e42a42096ae61da636b", - "version" : "0.2.0-beta.2" - } - }, { "identity" : "routing-kit", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 5c88241..b67a5d0 100644 --- a/Package.swift +++ b/Package.swift @@ -60,6 +60,10 @@ let package = Package( .product(name: "Logging", package: "swift-log") ], swiftSettings: swiftSettings + ), + .testTarget( + name: "SublimationTests", + dependencies: ["Sublimation"] ) ] ) diff --git a/Packages/Ngrokit/Package.swift b/Packages/Ngrokit/Package.swift index 6e12c4f..206b165 100644 --- a/Packages/Ngrokit/Package.swift +++ b/Packages/Ngrokit/Package.swift @@ -38,7 +38,8 @@ let package = Package( .macCatalyst(.v17) ], products: [ - .library(name: "Ngrokit", targets: ["Ngrokit"]) + .library(name: "Ngrokit", targets: ["Ngrokit"]), + .library(name: "NgrokitMocks", targets: ["NgrokitMocks"]) ], dependencies: [ .package( @@ -60,6 +61,15 @@ let package = Package( .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime") ], swiftSettings: swiftSettings + ), + .target( + name: "NgrokitMocks", + dependencies: ["Ngrokit"], + swiftSettings: swiftSettings + ), + .testTarget( + name: "NgrokitTests", + dependencies: ["Ngrokit", "NgrokitMocks"] ) ] ) diff --git a/Packages/Ngrokit/Scripts/lint.sh b/Packages/Ngrokit/Scripts/lint.sh index e7aa376..f847023 100755 --- a/Packages/Ngrokit/Scripts/lint.sh +++ b/Packages/Ngrokit/Scripts/lint.sh @@ -13,10 +13,10 @@ MINT_RUN="/opt/homebrew/bin/mint run $MINT_ARGS" if [ -z "$SRCROOT" ]; then SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PACKAGE_DIR="${SCRIPT_DIR}/.." - PERIPHERY_OPTIONS="--skip-build" -else - PACKAGE_DIR="${SRCROOT}" PERIPHERY_OPTIONS="" +else + PACKAGE_DIR="${SRCROOT}" + PERIPHERY_OPTIONS="--skip-build" fi @@ -32,6 +32,10 @@ fi echo "LINT Mode is $LINT_MODE" +if [ "$LINT_MODE" == "INSTALL" ]; then + exit +fi + if [ -z "$CI" ]; then $MINT_RUN swift-format format --recursive --parallel --in-place $PACKAGE_DIR/Sources else diff --git a/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Client.swift b/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Client.swift index a7b26a5..f9f5109 100644 --- a/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Client.swift +++ b/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Client.swift @@ -1,305 +1,263 @@ +// +// Client.swift +// Ngrokit +// +// Created by Leo Dion. +// Copyright © 2024 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import HTTPTypes // Generated by swift-openapi-generator, do not modify. @_spi(Generated) import OpenAPIRuntime + #if os(Linux) -@preconcurrency import struct Foundation.URL -@preconcurrency import struct Foundation.Data -@preconcurrency import struct Foundation.Date + @preconcurrency import struct Foundation.URL + @preconcurrency import struct Foundation.Data + @preconcurrency import struct Foundation.Date #else -import struct Foundation.URL -import struct Foundation.Data -import struct Foundation.Date + import struct Foundation.URL + import struct Foundation.Data + import struct Foundation.Date #endif -import HTTPTypes package struct Client: APIProtocol { - /// The underlying HTTP client. - private let client: UniversalClient - /// Creates a new client. - /// - Parameters: - /// - serverURL: The server URL that the client connects to. Any server - /// URLs defined in the OpenAPI document are available as static methods - /// on the ``Servers`` type. - /// - configuration: A set of configuration values for the client. - /// - transport: A transport that performs HTTP operations. - /// - middlewares: A list of middlewares to call before the transport. - package init( - serverURL: Foundation.URL, - configuration: Configuration = .init(), - transport: any ClientTransport, - middlewares: [any ClientMiddleware] = [] - ) { - self.client = .init( - serverURL: serverURL, - configuration: configuration, - transport: transport, - middlewares: middlewares - ) - } - private var converter: Converter { - client.converter - } - /// Access the root API resource of a running ngrok agent - /// - /// - Remark: HTTP `GET /api`. - /// - Remark: Generated from `#/paths//api/get`. - package func get_sol_api(_ input: Operations.get_sol_api.Input) async throws -> Operations.get_sol_api.Output { - try await client.send( - input: input, - forOperation: Operations.get_sol_api.id, - serializer: { _ in - let path = try converter.renderedPath( - template: "/api", - parameters: [] - ) - var request: HTTPTypes.HTTPRequest = .init( - soar_path: path, - method: .get - ) - suppressMutabilityWarning(&request) - return (request, nil) - }, - deserializer: { response, responseBody in - switch response.status.code { - case 200: - return .ok(.init()) - default: - return .undocumented( - statusCode: response.status.code, - .init( - headerFields: response.headerFields, - body: responseBody - ) - ) - } - } - ) - } - /// List Tunnels - /// - /// - Remark: HTTP `GET /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. - package func listTunnels(_ input: Operations.listTunnels.Input) async throws -> Operations.listTunnels.Output { - try await client.send( - input: input, - forOperation: Operations.listTunnels.id, - serializer: { input in - let path = try converter.renderedPath( - template: "/api/tunnels", - parameters: [] - ) - var request: HTTPTypes.HTTPRequest = .init( - soar_path: path, - method: .get - ) - suppressMutabilityWarning(&request) - converter.setAcceptHeader( - in: &request.headerFields, - contentTypes: input.headers.accept - ) - return (request, nil) - }, - deserializer: { response, responseBody in - switch response.status.code { - case 200: - let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.listTunnels.Output.Ok.Body - let chosenContentType = try converter.bestContentType( - received: contentType, - options: [ - "application/json" - ] - ) - switch chosenContentType { - case "application/json": - body = try await converter.getResponseBodyAsJSON( - Components.Schemas.TunnelList.self, - from: responseBody, - transforming: { value in - .json(value) - } - ) - default: - preconditionFailure("bestContentType chose an invalid content type.") - } - return .ok(.init(body: body)) - default: - return .undocumented( - statusCode: response.status.code, - .init( - headerFields: response.headerFields, - body: responseBody - ) - ) - } - } - ) - } - /// Start tunnel - /// - /// - Remark: HTTP `POST /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. - package func startTunnel(_ input: Operations.startTunnel.Input) async throws -> Operations.startTunnel.Output { - try await client.send( - input: input, - forOperation: Operations.startTunnel.id, - serializer: { input in - let path = try converter.renderedPath( - template: "/api/tunnels", - parameters: [] - ) - var request: HTTPTypes.HTTPRequest = .init( - soar_path: path, - method: .post - ) - suppressMutabilityWarning(&request) - converter.setAcceptHeader( - in: &request.headerFields, - contentTypes: input.headers.accept - ) - let body: OpenAPIRuntime.HTTPBody? - switch input.body { - case let .json(value): - body = try converter.setRequiredRequestBodyAsJSON( - value, - headerFields: &request.headerFields, - contentType: "application/json; charset=utf-8" - ) - } - return (request, body) - }, - deserializer: { response, responseBody in - switch response.status.code { - case 201: - let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.startTunnel.Output.Created.Body - let chosenContentType = try converter.bestContentType( - received: contentType, - options: [ - "application/json" - ] - ) - switch chosenContentType { - case "application/json": - body = try await converter.getResponseBodyAsJSON( - Components.Schemas.TunnelResponse.self, - from: responseBody, - transforming: { value in - .json(value) - } - ) - default: - preconditionFailure("bestContentType chose an invalid content type.") - } - return .created(.init(body: body)) - default: - return .undocumented( - statusCode: response.status.code, - .init( - headerFields: response.headerFields, - body: responseBody - ) - ) - } - } - ) - } - /// Tunnel detail - /// - /// - Remark: HTTP `GET /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. - package func getTunnel(_ input: Operations.getTunnel.Input) async throws -> Operations.getTunnel.Output { - try await client.send( - input: input, - forOperation: Operations.getTunnel.id, - serializer: { input in - let path = try converter.renderedPath( - template: "/api/tunnels/{}", - parameters: [ - input.path.name - ] - ) - var request: HTTPTypes.HTTPRequest = .init( - soar_path: path, - method: .get - ) - suppressMutabilityWarning(&request) - converter.setAcceptHeader( - in: &request.headerFields, - contentTypes: input.headers.accept - ) - return (request, nil) - }, - deserializer: { response, responseBody in - switch response.status.code { - case 200: - let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.getTunnel.Output.Ok.Body - let chosenContentType = try converter.bestContentType( - received: contentType, - options: [ - "application/json" - ] - ) - switch chosenContentType { - case "application/json": - body = try await converter.getResponseBodyAsJSON( - OpenAPIRuntime.OpenAPIValueContainer.self, - from: responseBody, - transforming: { value in - .json(value) - } - ) - default: - preconditionFailure("bestContentType chose an invalid content type.") - } - return .ok(.init(body: body)) - default: - return .undocumented( - statusCode: response.status.code, - .init( - headerFields: response.headerFields, - body: responseBody - ) - ) - } - } + /// The underlying HTTP client. + private let client: UniversalClient + /// Creates a new client. + /// - Parameters: + /// - serverURL: The server URL that the client connects to. Any server + /// URLs defined in the OpenAPI document are available as static methods + /// on the ``Servers`` type. + /// - configuration: A set of configuration values for the client. + /// - transport: A transport that performs HTTP operations. + /// - middlewares: A list of middlewares to call before the transport. + package init( + serverURL: Foundation.URL, + configuration: Configuration = .init(), + transport: any ClientTransport, + middlewares: [any ClientMiddleware] = [] + ) { + self.client = .init( + serverURL: serverURL, + configuration: configuration, + transport: transport, + middlewares: middlewares + ) + } + private var converter: Converter { client.converter } + /// Access the root API resource of a running ngrok agent + /// + /// - Remark: HTTP `GET /api`. + /// - Remark: Generated from `#/paths//api/get`. + package func get_sol_api(_ input: Operations.get_sol_api.Input) async throws + -> Operations.get_sol_api.Output + { + try await client.send( + input: input, + forOperation: Operations.get_sol_api.id, + serializer: { _ in + let path = try converter.renderedPath(template: "/api", parameters: []) + var request: HTTPTypes.HTTPRequest = .init(soar_path: path, method: .get) + suppressMutabilityWarning(&request) + return (request, nil) + }, + deserializer: { response, responseBody in + switch response.status.code { case 200: return .ok(.init()) default: + return .undocumented( + statusCode: response.status.code, + .init(headerFields: response.headerFields, body: responseBody) + ) + } + } + ) + } + /// List Tunnels + /// + /// - Remark: HTTP `GET /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. + package func listTunnels(_ input: Operations.listTunnels.Input) async throws + -> Operations.listTunnels.Output + { + try await client.send( + input: input, + forOperation: Operations.listTunnels.id, + serializer: { input in + let path = try converter.renderedPath(template: "/api/tunnels", parameters: []) + var request: HTTPTypes.HTTPRequest = .init(soar_path: path, method: .get) + suppressMutabilityWarning(&request) + converter.setAcceptHeader(in: &request.headerFields, contentTypes: input.headers.accept) + return (request, nil) + }, + deserializer: { response, responseBody in + switch response.status.code { case 200: + let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) + let body: Operations.listTunnels.Output.Ok.Body + let chosenContentType = try converter.bestContentType( + received: contentType, + options: ["application/json"] + ) + switch chosenContentType { case "application/json": + body = try await converter.getResponseBodyAsJSON( + Components.Schemas.TunnelList.self, + from: responseBody, + transforming: { value in .json(value) } + ) + default: preconditionFailure("bestContentType chose an invalid content type.") + } + return .ok(.init(body: body)) + default: + return .undocumented( + statusCode: response.status.code, + .init(headerFields: response.headerFields, body: responseBody) + ) + } + } + ) + } + /// Start tunnel + /// + /// - Remark: HTTP `POST /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. + package func startTunnel(_ input: Operations.startTunnel.Input) async throws + -> Operations.startTunnel.Output + { + try await client.send( + input: input, + forOperation: Operations.startTunnel.id, + serializer: { input in + let path = try converter.renderedPath(template: "/api/tunnels", parameters: []) + var request: HTTPTypes.HTTPRequest = .init(soar_path: path, method: .post) + suppressMutabilityWarning(&request) + converter.setAcceptHeader(in: &request.headerFields, contentTypes: input.headers.accept) + let body: OpenAPIRuntime.HTTPBody? + switch input.body { case let .json(value): + body = try converter.setRequiredRequestBodyAsJSON( + value, + headerFields: &request.headerFields, + contentType: "application/json; charset=utf-8" + ) + } + return (request, body) + }, + deserializer: { response, responseBody in + switch response.status.code { case 201: + let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) + let body: Operations.startTunnel.Output.Created.Body + let chosenContentType = try converter.bestContentType( + received: contentType, + options: ["application/json"] + ) + switch chosenContentType { case "application/json": + body = try await converter.getResponseBodyAsJSON( + Components.Schemas.TunnelResponse.self, + from: responseBody, + transforming: { value in .json(value) } + ) + default: preconditionFailure("bestContentType chose an invalid content type.") + } + return .created(.init(body: body)) + default: + return .undocumented( + statusCode: response.status.code, + .init(headerFields: response.headerFields, body: responseBody) + ) + } + } + ) + } + /// Tunnel detail + /// + /// - Remark: HTTP `GET /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. + package func getTunnel(_ input: Operations.getTunnel.Input) async throws + -> Operations.getTunnel.Output + { + try await client.send( + input: input, + forOperation: Operations.getTunnel.id, + serializer: { input in + let path = try converter.renderedPath( + template: "/api/tunnels/{}", + parameters: [input.path.name] ) - } - /// Stop tunnel - /// - /// - Remark: HTTP `DELETE /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. - package func stopTunnel(_ input: Operations.stopTunnel.Input) async throws -> Operations.stopTunnel.Output { - try await client.send( - input: input, - forOperation: Operations.stopTunnel.id, - serializer: { input in - let path = try converter.renderedPath( - template: "/api/tunnels/{}", - parameters: [ - input.path.name - ] - ) - var request: HTTPTypes.HTTPRequest = .init( - soar_path: path, - method: .delete - ) - suppressMutabilityWarning(&request) - return (request, nil) - }, - deserializer: { response, responseBody in - switch response.status.code { - case 204: - return .noContent(.init()) - default: - return .undocumented( - statusCode: response.status.code, - .init( - headerFields: response.headerFields, - body: responseBody - ) - ) - } - } + var request: HTTPTypes.HTTPRequest = .init(soar_path: path, method: .get) + suppressMutabilityWarning(&request) + converter.setAcceptHeader(in: &request.headerFields, contentTypes: input.headers.accept) + return (request, nil) + }, + deserializer: { response, responseBody in + switch response.status.code { case 200: + let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) + let body: Operations.getTunnel.Output.Ok.Body + let chosenContentType = try converter.bestContentType( + received: contentType, + options: ["application/json"] + ) + switch chosenContentType { case "application/json": + body = try await converter.getResponseBodyAsJSON( + OpenAPIRuntime.OpenAPIValueContainer.self, + from: responseBody, + transforming: { value in .json(value) } + ) + default: preconditionFailure("bestContentType chose an invalid content type.") + } + return .ok(.init(body: body)) + default: + return .undocumented( + statusCode: response.status.code, + .init(headerFields: response.headerFields, body: responseBody) + ) + } + } + ) + } + /// Stop tunnel + /// + /// - Remark: HTTP `DELETE /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. + package func stopTunnel(_ input: Operations.stopTunnel.Input) async throws + -> Operations.stopTunnel.Output + { + try await client.send( + input: input, + forOperation: Operations.stopTunnel.id, + serializer: { input in + let path = try converter.renderedPath( + template: "/api/tunnels/{}", + parameters: [input.path.name] ) - } + var request: HTTPTypes.HTTPRequest = .init(soar_path: path, method: .delete) + suppressMutabilityWarning(&request) + return (request, nil) + }, + deserializer: { response, responseBody in + switch response.status.code { case 204: return .noContent(.init()) default: + return .undocumented( + statusCode: response.status.code, + .init(headerFields: response.headerFields, body: responseBody) + ) + } + } + ) + } } diff --git a/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Types.swift b/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Types.swift index 2984cc7..c056256 100644 --- a/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Types.swift +++ b/Packages/Ngrokit/Sources/NgrokOpenAPIClient/Types.swift @@ -1,846 +1,786 @@ -// Generated by swift-openapi-generator, do not modify. +// +// Types.swift +// Ngrokit +// +// Created by Leo Dion. +// Copyright © 2024 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + @_spi(Generated) import OpenAPIRuntime + #if os(Linux) -@preconcurrency import struct Foundation.URL -@preconcurrency import struct Foundation.Data -@preconcurrency import struct Foundation.Date + @preconcurrency import struct Foundation.URL + @preconcurrency import struct Foundation.Data + @preconcurrency import struct Foundation.Date #else -import struct Foundation.URL -import struct Foundation.Data -import struct Foundation.Date + import struct Foundation.URL + import struct Foundation.Data + import struct Foundation.Date #endif /// A type that performs HTTP operations defined by the OpenAPI document. package protocol APIProtocol: Sendable { - /// Access the root API resource of a running ngrok agent - /// - /// - Remark: HTTP `GET /api`. - /// - Remark: Generated from `#/paths//api/get`. - func get_sol_api(_ input: Operations.get_sol_api.Input) async throws -> Operations.get_sol_api.Output - /// List Tunnels - /// - /// - Remark: HTTP `GET /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. - func listTunnels(_ input: Operations.listTunnels.Input) async throws -> Operations.listTunnels.Output - /// Start tunnel - /// - /// - Remark: HTTP `POST /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. - func startTunnel(_ input: Operations.startTunnel.Input) async throws -> Operations.startTunnel.Output - /// Tunnel detail - /// - /// - Remark: HTTP `GET /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. - func getTunnel(_ input: Operations.getTunnel.Input) async throws -> Operations.getTunnel.Output - /// Stop tunnel - /// - /// - Remark: HTTP `DELETE /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. - func stopTunnel(_ input: Operations.stopTunnel.Input) async throws -> Operations.stopTunnel.Output + /// Access the root API resource of a running ngrok agent + /// + /// - Remark: HTTP `GET /api`. + /// - Remark: Generated from `#/paths//api/get`. + func get_sol_api(_ input: Operations.get_sol_api.Input) async throws + -> Operations.get_sol_api.Output + /// List Tunnels + /// + /// - Remark: HTTP `GET /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. + func listTunnels(_ input: Operations.listTunnels.Input) async throws + -> Operations.listTunnels.Output + /// Start tunnel + /// + /// - Remark: HTTP `POST /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. + func startTunnel(_ input: Operations.startTunnel.Input) async throws + -> Operations.startTunnel.Output + /// Tunnel detail + /// + /// - Remark: HTTP `GET /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. + func getTunnel(_ input: Operations.getTunnel.Input) async throws -> Operations.getTunnel.Output + /// Stop tunnel + /// + /// - Remark: HTTP `DELETE /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. + func stopTunnel(_ input: Operations.stopTunnel.Input) async throws -> Operations.stopTunnel.Output } /// Convenience overloads for operation inputs. extension APIProtocol { - /// Access the root API resource of a running ngrok agent - /// - /// - Remark: HTTP `GET /api`. - /// - Remark: Generated from `#/paths//api/get`. - package func get_sol_api() async throws -> Operations.get_sol_api.Output { - try await get_sol_api(Operations.get_sol_api.Input()) - } - /// List Tunnels - /// - /// - Remark: HTTP `GET /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. - package func listTunnels(headers: Operations.listTunnels.Input.Headers = .init()) async throws -> Operations.listTunnels.Output { - try await listTunnels(Operations.listTunnels.Input(headers: headers)) - } - /// Start tunnel - /// - /// - Remark: HTTP `POST /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. - package func startTunnel( - headers: Operations.startTunnel.Input.Headers = .init(), - body: Operations.startTunnel.Input.Body - ) async throws -> Operations.startTunnel.Output { - try await startTunnel(Operations.startTunnel.Input( - headers: headers, - body: body - )) - } - /// Tunnel detail - /// - /// - Remark: HTTP `GET /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. - package func getTunnel( - path: Operations.getTunnel.Input.Path, - headers: Operations.getTunnel.Input.Headers = .init() - ) async throws -> Operations.getTunnel.Output { - try await getTunnel(Operations.getTunnel.Input( - path: path, - headers: headers - )) - } - /// Stop tunnel - /// - /// - Remark: HTTP `DELETE /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. - package func stopTunnel(path: Operations.stopTunnel.Input.Path) async throws -> Operations.stopTunnel.Output { - try await stopTunnel(Operations.stopTunnel.Input(path: path)) - } + /// Access the root API resource of a running ngrok agent + /// + /// - Remark: HTTP `GET /api`. + /// - Remark: Generated from `#/paths//api/get`. + package func get_sol_api() async throws -> Operations.get_sol_api.Output { + try await get_sol_api(Operations.get_sol_api.Input()) + } + /// List Tunnels + /// + /// - Remark: HTTP `GET /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. + package func listTunnels(headers: Operations.listTunnels.Input.Headers = .init()) async throws + -> Operations.listTunnels.Output + { try await listTunnels(Operations.listTunnels.Input(headers: headers)) } + /// Start tunnel + /// + /// - Remark: HTTP `POST /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. + package func startTunnel( + headers: Operations.startTunnel.Input.Headers = .init(), + body: Operations.startTunnel.Input.Body + ) async throws -> Operations.startTunnel.Output { + try await startTunnel(Operations.startTunnel.Input(headers: headers, body: body)) + } + /// Tunnel detail + /// + /// - Remark: HTTP `GET /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. + package func getTunnel( + path: Operations.getTunnel.Input.Path, + headers: Operations.getTunnel.Input.Headers = .init() + ) async throws -> Operations.getTunnel.Output { + try await getTunnel(Operations.getTunnel.Input(path: path, headers: headers)) + } + /// Stop tunnel + /// + /// - Remark: HTTP `DELETE /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. + package func stopTunnel(path: Operations.stopTunnel.Input.Path) async throws + -> Operations.stopTunnel.Output + { try await stopTunnel(Operations.stopTunnel.Input(path: path)) } } /// Server URLs defined in the OpenAPI document. package enum Servers { - /// Default Local Server - package static func server1() throws -> Foundation.URL { - try Foundation.URL( - validatingOpenAPIServerURL: "http://127.0.0.1:4040", - variables: [] - ) - } + /// Default Local Server + package static func server1() throws -> Foundation.URL { + try Foundation.URL(validatingOpenAPIServerURL: "http://127.0.0.1:4040", variables: []) + } } /// Types generated from the components section of the OpenAPI document. package enum Components { - /// Types generated from the `#/components/schemas` section of the OpenAPI document. - package enum Schemas { - /// - Remark: Generated from `#/components/schemas/TunnelList`. - package struct TunnelList: Codable, Hashable, Sendable { - /// - Remark: Generated from `#/components/schemas/TunnelList/tunnels`. - package var tunnels: [Components.Schemas.TunnelResponse] - /// Creates a new `TunnelList`. - /// - /// - Parameters: - /// - tunnels: - package init(tunnels: [Components.Schemas.TunnelResponse]) { - self.tunnels = tunnels - } - package enum CodingKeys: String, CodingKey { - case tunnels - } + /// Types generated from the `#/components/schemas` section of the OpenAPI document. + package enum Schemas { + /// - Remark: Generated from `#/components/schemas/TunnelList`. + package struct TunnelList: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/TunnelList/tunnels`. + package var tunnels: [Components.Schemas.TunnelResponse] + /// Creates a new `TunnelList`. + /// + /// - Parameters: + /// - tunnels: + package init(tunnels: [Components.Schemas.TunnelResponse]) { self.tunnels = tunnels } + package enum CodingKeys: String, CodingKey { case tunnels } + } + /// - Remark: Generated from `#/components/schemas/TunnelRequest`. + package struct TunnelRequest: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/TunnelRequest/addr`. + package var addr: Swift.String + /// - Remark: Generated from `#/components/schemas/TunnelRequest/proto`. + package var proto: Swift.String + /// - Remark: Generated from `#/components/schemas/TunnelRequest/name`. + package var name: Swift.String + /// Creates a new `TunnelRequest`. + /// + /// - Parameters: + /// - addr: + /// - proto: + /// - name: + package init(addr: Swift.String, proto: Swift.String, name: Swift.String) { + self.addr = addr + self.proto = proto + self.name = name + } + package enum CodingKeys: String, CodingKey { + case addr + case proto + case name + } + } + /// - Remark: Generated from `#/components/schemas/TunnelResponse`. + package struct TunnelResponse: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/TunnelResponse/name`. + package var name: Swift.String + /// - Remark: Generated from `#/components/schemas/TunnelResponse/uri`. + package var uri: Swift.String? + /// - Remark: Generated from `#/components/schemas/TunnelResponse/public_url`. + package var public_url: Swift.String + /// - Remark: Generated from `#/components/schemas/TunnelResponse/proto`. + package var proto: Swift.String? + /// - Remark: Generated from `#/components/schemas/TunnelResponse/config`. + package struct configPayload: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/TunnelResponse/config/addr`. + package var addr: Swift.String + /// - Remark: Generated from `#/components/schemas/TunnelResponse/config/inspect`. + package var inspect: Swift.Bool + /// Creates a new `configPayload`. + /// + /// - Parameters: + /// - addr: + /// - inspect: + package init(addr: Swift.String, inspect: Swift.Bool) { + self.addr = addr + self.inspect = inspect } - /// - Remark: Generated from `#/components/schemas/TunnelRequest`. - package struct TunnelRequest: Codable, Hashable, Sendable { - /// - Remark: Generated from `#/components/schemas/TunnelRequest/addr`. - package var addr: Swift.String - /// - Remark: Generated from `#/components/schemas/TunnelRequest/proto`. - package var proto: Swift.String - /// - Remark: Generated from `#/components/schemas/TunnelRequest/name`. - package var name: Swift.String - /// Creates a new `TunnelRequest`. - /// - /// - Parameters: - /// - addr: - /// - proto: - /// - name: - package init( - addr: Swift.String, - proto: Swift.String, - name: Swift.String - ) { - self.addr = addr - self.proto = proto - self.name = name - } - package enum CodingKeys: String, CodingKey { - case addr - case proto - case name - } + package enum CodingKeys: String, CodingKey { + case addr + case inspect } - /// - Remark: Generated from `#/components/schemas/TunnelResponse`. - package struct TunnelResponse: Codable, Hashable, Sendable { - /// - Remark: Generated from `#/components/schemas/TunnelResponse/name`. - package var name: Swift.String - /// - Remark: Generated from `#/components/schemas/TunnelResponse/uri`. - package var uri: Swift.String? - /// - Remark: Generated from `#/components/schemas/TunnelResponse/public_url`. - package var public_url: Swift.String - /// - Remark: Generated from `#/components/schemas/TunnelResponse/proto`. - package var proto: Swift.String? - /// - Remark: Generated from `#/components/schemas/TunnelResponse/config`. - package struct configPayload: Codable, Hashable, Sendable { - /// - Remark: Generated from `#/components/schemas/TunnelResponse/config/addr`. - package var addr: Swift.String - /// - Remark: Generated from `#/components/schemas/TunnelResponse/config/inspect`. - package var inspect: Swift.Bool - /// Creates a new `configPayload`. - /// - /// - Parameters: - /// - addr: - /// - inspect: - package init( - addr: Swift.String, - inspect: Swift.Bool - ) { - self.addr = addr - self.inspect = inspect - } - package enum CodingKeys: String, CodingKey { - case addr - case inspect - } - } - /// - Remark: Generated from `#/components/schemas/TunnelResponse/config`. - package var config: Components.Schemas.TunnelResponse.configPayload - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics`. - package struct metricsPayload: Codable, Hashable, Sendable { - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns`. - package struct connsPayload: Codable, Hashable, Sendable { - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/count`. - package var count: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/gauge`. - package var gauge: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/rate1`. - package var rate1: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/rate5`. - package var rate5: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/rate15`. - package var rate15: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p50`. - package var p50: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p90`. - package var p90: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p95`. - package var p95: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p99`. - package var p99: Swift.Int - /// Creates a new `connsPayload`. - /// - /// - Parameters: - /// - count: - /// - gauge: - /// - rate1: - /// - rate5: - /// - rate15: - /// - p50: - /// - p90: - /// - p95: - /// - p99: - package init( - count: Swift.Int, - gauge: Swift.Int, - rate1: Swift.Int, - rate5: Swift.Int, - rate15: Swift.Int, - p50: Swift.Int, - p90: Swift.Int, - p95: Swift.Int, - p99: Swift.Int - ) { - self.count = count - self.gauge = gauge - self.rate1 = rate1 - self.rate5 = rate5 - self.rate15 = rate15 - self.p50 = p50 - self.p90 = p90 - self.p95 = p95 - self.p99 = p99 - } - package enum CodingKeys: String, CodingKey { - case count - case gauge - case rate1 - case rate5 - case rate15 - case p50 - case p90 - case p95 - case p99 - } - } - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns`. - package var conns: Components.Schemas.TunnelResponse.metricsPayload.connsPayload? - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http`. - package struct httpPayload: Codable, Hashable, Sendable { - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/count`. - package var count: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/rate1`. - package var rate1: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/rate5`. - package var rate5: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/rate15`. - package var rate15: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p50`. - package var p50: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p90`. - package var p90: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p95`. - package var p95: Swift.Int - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p99`. - package var p99: Swift.Int - /// Creates a new `httpPayload`. - /// - /// - Parameters: - /// - count: - /// - rate1: - /// - rate5: - /// - rate15: - /// - p50: - /// - p90: - /// - p95: - /// - p99: - package init( - count: Swift.Int, - rate1: Swift.Int, - rate5: Swift.Int, - rate15: Swift.Int, - p50: Swift.Int, - p90: Swift.Int, - p95: Swift.Int, - p99: Swift.Int - ) { - self.count = count - self.rate1 = rate1 - self.rate5 = rate5 - self.rate15 = rate15 - self.p50 = p50 - self.p90 = p90 - self.p95 = p95 - self.p99 = p99 - } - package enum CodingKeys: String, CodingKey { - case count - case rate1 - case rate5 - case rate15 - case p50 - case p90 - case p95 - case p99 - } - } - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http`. - package var http: Components.Schemas.TunnelResponse.metricsPayload.httpPayload? - /// Creates a new `metricsPayload`. - /// - /// - Parameters: - /// - conns: - /// - http: - package init( - conns: Components.Schemas.TunnelResponse.metricsPayload.connsPayload? = nil, - http: Components.Schemas.TunnelResponse.metricsPayload.httpPayload? = nil - ) { - self.conns = conns - self.http = http - } - package enum CodingKeys: String, CodingKey { - case conns - case http - } - } - /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics`. - package var metrics: Components.Schemas.TunnelResponse.metricsPayload? - /// Creates a new `TunnelResponse`. - /// - /// - Parameters: - /// - name: - /// - uri: - /// - public_url: - /// - proto: - /// - config: - /// - metrics: - package init( - name: Swift.String, - uri: Swift.String? = nil, - public_url: Swift.String, - proto: Swift.String? = nil, - config: Components.Schemas.TunnelResponse.configPayload, - metrics: Components.Schemas.TunnelResponse.metricsPayload? = nil - ) { - self.name = name - self.uri = uri - self.public_url = public_url - self.proto = proto - self.config = config - self.metrics = metrics - } - package enum CodingKeys: String, CodingKey { - case name - case uri - case public_url - case proto - case config - case metrics - } + } + /// - Remark: Generated from `#/components/schemas/TunnelResponse/config`. + package var config: Components.Schemas.TunnelResponse.configPayload + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics`. + package struct metricsPayload: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns`. + package struct connsPayload: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/count`. + package var count: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/gauge`. + package var gauge: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/rate1`. + package var rate1: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/rate5`. + package var rate5: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/rate15`. + package var rate15: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p50`. + package var p50: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p90`. + package var p90: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p95`. + package var p95: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns/p99`. + package var p99: Swift.Int + /// Creates a new `connsPayload`. + /// + /// - Parameters: + /// - count: + /// - gauge: + /// - rate1: + /// - rate5: + /// - rate15: + /// - p50: + /// - p90: + /// - p95: + /// - p99: + package init( + count: Swift.Int, + gauge: Swift.Int, + rate1: Swift.Int, + rate5: Swift.Int, + rate15: Swift.Int, + p50: Swift.Int, + p90: Swift.Int, + p95: Swift.Int, + p99: Swift.Int + ) { + self.count = count + self.gauge = gauge + self.rate1 = rate1 + self.rate5 = rate5 + self.rate15 = rate15 + self.p50 = p50 + self.p90 = p90 + self.p95 = p95 + self.p99 = p99 + } + package enum CodingKeys: String, CodingKey { + case count + case gauge + case rate1 + case rate5 + case rate15 + case p50 + case p90 + case p95 + case p99 + } + } + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/conns`. + package var conns: Components.Schemas.TunnelResponse.metricsPayload.connsPayload? + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http`. + package struct httpPayload: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/count`. + package var count: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/rate1`. + package var rate1: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/rate5`. + package var rate5: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/rate15`. + package var rate15: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p50`. + package var p50: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p90`. + package var p90: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p95`. + package var p95: Swift.Int + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http/p99`. + package var p99: Swift.Int + /// Creates a new `httpPayload`. + /// + /// - Parameters: + /// - count: + /// - rate1: + /// - rate5: + /// - rate15: + /// - p50: + /// - p90: + /// - p95: + /// - p99: + package init( + count: Swift.Int, + rate1: Swift.Int, + rate5: Swift.Int, + rate15: Swift.Int, + p50: Swift.Int, + p90: Swift.Int, + p95: Swift.Int, + p99: Swift.Int + ) { + self.count = count + self.rate1 = rate1 + self.rate5 = rate5 + self.rate15 = rate15 + self.p50 = p50 + self.p90 = p90 + self.p95 = p95 + self.p99 = p99 + } + package enum CodingKeys: String, CodingKey { + case count + case rate1 + case rate5 + case rate15 + case p50 + case p90 + case p95 + case p99 + } } + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics/http`. + package var http: Components.Schemas.TunnelResponse.metricsPayload.httpPayload? + /// Creates a new `metricsPayload`. + /// + /// - Parameters: + /// - conns: + /// - http: + package init( + conns: Components.Schemas.TunnelResponse.metricsPayload.connsPayload? = nil, + http: Components.Schemas.TunnelResponse.metricsPayload.httpPayload? = nil + ) { + self.conns = conns + self.http = http + } + package enum CodingKeys: String, CodingKey { + case conns + case http + } + } + /// - Remark: Generated from `#/components/schemas/TunnelResponse/metrics`. + package var metrics: Components.Schemas.TunnelResponse.metricsPayload? + /// Creates a new `TunnelResponse`. + /// + /// - Parameters: + /// - name: + /// - uri: + /// - public_url: + /// - proto: + /// - config: + /// - metrics: + package init( + name: Swift.String, + uri: Swift.String? = nil, + public_url: Swift.String, + proto: Swift.String? = nil, + config: Components.Schemas.TunnelResponse.configPayload, + metrics: Components.Schemas.TunnelResponse.metricsPayload? = nil + ) { + self.name = name + self.uri = uri + self.public_url = public_url + self.proto = proto + self.config = config + self.metrics = metrics + } + package enum CodingKeys: String, CodingKey { + case name + case uri + case public_url + case proto + case config + case metrics + } } - /// Types generated from the `#/components/parameters` section of the OpenAPI document. - package enum Parameters {} - /// Types generated from the `#/components/requestBodies` section of the OpenAPI document. - package enum RequestBodies {} - /// Types generated from the `#/components/responses` section of the OpenAPI document. - package enum Responses {} - /// Types generated from the `#/components/headers` section of the OpenAPI document. - package enum Headers {} + } + /// Types generated from the `#/components/parameters` section of the OpenAPI document. + package enum Parameters {} + /// Types generated from the `#/components/requestBodies` section of the OpenAPI document. + package enum RequestBodies {} + /// Types generated from the `#/components/responses` section of the OpenAPI document. + package enum Responses {} + /// Types generated from the `#/components/headers` section of the OpenAPI document. + package enum Headers {} } /// API operations, with input and output types, generated from `#/paths` in the OpenAPI document. package enum Operations { - /// Access the root API resource of a running ngrok agent - /// - /// - Remark: HTTP `GET /api`. - /// - Remark: Generated from `#/paths//api/get`. - package enum get_sol_api { - package static let id: Swift.String = "get/api" - package struct Input: Sendable, Hashable { - /// Creates a new `Input`. - package init() {} - } - @frozen package enum Output: Sendable, Hashable { - package struct Ok: Sendable, Hashable { - /// Creates a new `Ok`. - package init() {} - } - /// Successful response - /// - /// - Remark: Generated from `#/paths//api/get/responses/200`. - /// - /// HTTP response code: `200 ok`. - case ok(Operations.get_sol_api.Output.Ok) - /// The associated value of the enum case if `self` is `.ok`. - /// - /// - Throws: An error if `self` is not `.ok`. - /// - SeeAlso: `.ok`. - package var ok: Operations.get_sol_api.Output.Ok { - get throws { - switch self { - case let .ok(response): - return response - default: - try throwUnexpectedResponseStatus( - expectedStatus: "ok", - response: self - ) - } - } - } - /// Undocumented response. - /// - /// A response with a code that is not documented in the OpenAPI document. - case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) + /// Access the root API resource of a running ngrok agent + /// + /// - Remark: HTTP `GET /api`. + /// - Remark: Generated from `#/paths//api/get`. + package enum get_sol_api { + package static let id: Swift.String = "get/api" + package struct Input: Sendable, Hashable { + /// Creates a new `Input`. + package init() {} + } + @frozen package enum Output: Sendable, Hashable { + package struct Ok: Sendable, Hashable { + /// Creates a new `Ok`. + package init() {} + } + /// Successful response + /// + /// - Remark: Generated from `#/paths//api/get/responses/200`. + /// + /// HTTP response code: `200 ok`. + case ok(Operations.get_sol_api.Output.Ok) + /// The associated value of the enum case if `self` is `.ok`. + /// + /// - Throws: An error if `self` is not `.ok`. + /// - SeeAlso: `.ok`. + package var ok: Operations.get_sol_api.Output.Ok { + get throws { + switch self { case let .ok(response): return response default: + try throwUnexpectedResponseStatus(expectedStatus: "ok", response: self) + } } + } + /// Undocumented response. + /// + /// A response with a code that is not documented in the OpenAPI document. + case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) } - /// List Tunnels - /// - /// - Remark: HTTP `GET /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. - package enum listTunnels { - package static let id: Swift.String = "listTunnels" - package struct Input: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/GET/header`. - package struct Headers: Sendable, Hashable { - package var accept: [OpenAPIRuntime.AcceptHeaderContentType] - /// Creates a new `Headers`. - /// - /// - Parameters: - /// - accept: - package init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { - self.accept = accept - } - } - package var headers: Operations.listTunnels.Input.Headers - /// Creates a new `Input`. - /// - /// - Parameters: - /// - headers: - package init(headers: Operations.listTunnels.Input.Headers = .init()) { - self.headers = headers - } + } + /// List Tunnels + /// + /// - Remark: HTTP `GET /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)`. + package enum listTunnels { + package static let id: Swift.String = "listTunnels" + package struct Input: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/GET/header`. + package struct Headers: Sendable, Hashable { + package var accept: + [OpenAPIRuntime.AcceptHeaderContentType] + /// Creates a new `Headers`. + /// + /// - Parameters: + /// - accept: + package init( + accept: [OpenAPIRuntime.AcceptHeaderContentType< + Operations.listTunnels.AcceptableContentType + >] = .defaultValues() + ) { self.accept = accept } + } + package var headers: Operations.listTunnels.Input.Headers + /// Creates a new `Input`. + /// + /// - Parameters: + /// - headers: + package init(headers: Operations.listTunnels.Input.Headers = .init()) { + self.headers = headers + } + } + @frozen package enum Output: Sendable, Hashable { + package struct Ok: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/GET/responses/200/content`. + @frozen package enum Body: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/GET/responses/200/content/application\/json`. + case json(Components.Schemas.TunnelList) + /// The associated value of the enum case if `self` is `.json`. + /// + /// - Throws: An error if `self` is not `.json`. + /// - SeeAlso: `.json`. + package var json: Components.Schemas.TunnelList { + get throws { + switch self { case let .json(body): return body + } + } + } } - @frozen package enum Output: Sendable, Hashable { - package struct Ok: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/GET/responses/200/content`. - @frozen package enum Body: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/GET/responses/200/content/application\/json`. - case json(Components.Schemas.TunnelList) - /// The associated value of the enum case if `self` is `.json`. - /// - /// - Throws: An error if `self` is not `.json`. - /// - SeeAlso: `.json`. - package var json: Components.Schemas.TunnelList { - get throws { - switch self { - case let .json(body): - return body - } - } - } - } - /// Received HTTP response body - package var body: Operations.listTunnels.Output.Ok.Body - /// Creates a new `Ok`. - /// - /// - Parameters: - /// - body: Received HTTP response body - package init(body: Operations.listTunnels.Output.Ok.Body) { - self.body = body - } - } - /// Successful response - /// - /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)/responses/200`. - /// - /// HTTP response code: `200 ok`. - case ok(Operations.listTunnels.Output.Ok) - /// The associated value of the enum case if `self` is `.ok`. - /// - /// - Throws: An error if `self` is not `.ok`. - /// - SeeAlso: `.ok`. - package var ok: Operations.listTunnels.Output.Ok { - get throws { - switch self { - case let .ok(response): - return response - default: - try throwUnexpectedResponseStatus( - expectedStatus: "ok", - response: self - ) - } - } - } - /// Undocumented response. - /// - /// A response with a code that is not documented in the OpenAPI document. - case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) - } - @frozen package enum AcceptableContentType: AcceptableProtocol { - case json - case other(Swift.String) - package init?(rawValue: Swift.String) { - switch rawValue.lowercased() { - case "application/json": - self = .json - default: - self = .other(rawValue) - } - } - package var rawValue: Swift.String { - switch self { - case let .other(string): - return string - case .json: - return "application/json" - } - } - package static var allCases: [Self] { - [ - .json - ] - } + /// Received HTTP response body + package var body: Operations.listTunnels.Output.Ok.Body + /// Creates a new `Ok`. + /// + /// - Parameters: + /// - body: Received HTTP response body + package init(body: Operations.listTunnels.Output.Ok.Body) { self.body = body } + } + /// Successful response + /// + /// - Remark: Generated from `#/paths//api/tunnels/get(listTunnels)/responses/200`. + /// + /// HTTP response code: `200 ok`. + case ok(Operations.listTunnels.Output.Ok) + /// The associated value of the enum case if `self` is `.ok`. + /// + /// - Throws: An error if `self` is not `.ok`. + /// - SeeAlso: `.ok`. + package var ok: Operations.listTunnels.Output.Ok { + get throws { + switch self { case let .ok(response): return response default: + try throwUnexpectedResponseStatus(expectedStatus: "ok", response: self) + } } + } + /// Undocumented response. + /// + /// A response with a code that is not documented in the OpenAPI document. + case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) } - /// Start tunnel - /// - /// - Remark: HTTP `POST /api/tunnels`. - /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. - package enum startTunnel { - package static let id: Swift.String = "startTunnel" - package struct Input: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/POST/header`. - package struct Headers: Sendable, Hashable { - package var accept: [OpenAPIRuntime.AcceptHeaderContentType] - /// Creates a new `Headers`. - /// - /// - Parameters: - /// - accept: - package init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { - self.accept = accept - } - } - package var headers: Operations.startTunnel.Input.Headers - /// - Remark: Generated from `#/paths/api/tunnels/POST/requestBody`. - @frozen package enum Body: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/POST/requestBody/content/application\/json`. - case json(Components.Schemas.TunnelRequest) - } - package var body: Operations.startTunnel.Input.Body - /// Creates a new `Input`. - /// - /// - Parameters: - /// - headers: - /// - body: - package init( - headers: Operations.startTunnel.Input.Headers = .init(), - body: Operations.startTunnel.Input.Body - ) { - self.headers = headers - self.body = body - } + @frozen package enum AcceptableContentType: AcceptableProtocol { + case json + case other(Swift.String) + package init?(rawValue: Swift.String) { + switch rawValue.lowercased() { case "application/json": self = .json default: + self = .other(rawValue) } - @frozen package enum Output: Sendable, Hashable { - package struct Created: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/POST/responses/201/content`. - @frozen package enum Body: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/POST/responses/201/content/application\/json`. - case json(Components.Schemas.TunnelResponse) - /// The associated value of the enum case if `self` is `.json`. - /// - /// - Throws: An error if `self` is not `.json`. - /// - SeeAlso: `.json`. - package var json: Components.Schemas.TunnelResponse { - get throws { - switch self { - case let .json(body): - return body - } - } - } - } - /// Received HTTP response body - package var body: Operations.startTunnel.Output.Created.Body - /// Creates a new `Created`. - /// - /// - Parameters: - /// - body: Received HTTP response body - package init(body: Operations.startTunnel.Output.Created.Body) { - self.body = body - } - } - /// Tunnel started successfully - /// - /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)/responses/201`. - /// - /// HTTP response code: `201 created`. - case created(Operations.startTunnel.Output.Created) - /// The associated value of the enum case if `self` is `.created`. - /// - /// - Throws: An error if `self` is not `.created`. - /// - SeeAlso: `.created`. - package var created: Operations.startTunnel.Output.Created { - get throws { - switch self { - case let .created(response): - return response - default: - try throwUnexpectedResponseStatus( - expectedStatus: "created", - response: self - ) - } - } - } - /// Undocumented response. - /// - /// A response with a code that is not documented in the OpenAPI document. - case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) + } + package var rawValue: Swift.String { + switch self { case let .other(string): return string case .json: return "application/json" } - @frozen package enum AcceptableContentType: AcceptableProtocol { - case json - case other(Swift.String) - package init?(rawValue: Swift.String) { - switch rawValue.lowercased() { - case "application/json": - self = .json - default: - self = .other(rawValue) - } - } - package var rawValue: Swift.String { - switch self { - case let .other(string): - return string - case .json: - return "application/json" - } - } - package static var allCases: [Self] { - [ - .json - ] - } + } + package static var allCases: [Self] { [.json] } + } + } + /// Start tunnel + /// + /// - Remark: HTTP `POST /api/tunnels`. + /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)`. + package enum startTunnel { + package static let id: Swift.String = "startTunnel" + package struct Input: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/POST/header`. + package struct Headers: Sendable, Hashable { + package var accept: + [OpenAPIRuntime.AcceptHeaderContentType] + /// Creates a new `Headers`. + /// + /// - Parameters: + /// - accept: + package init( + accept: [OpenAPIRuntime.AcceptHeaderContentType< + Operations.startTunnel.AcceptableContentType + >] = .defaultValues() + ) { self.accept = accept } + } + package var headers: Operations.startTunnel.Input.Headers + /// - Remark: Generated from `#/paths/api/tunnels/POST/requestBody`. + @frozen package enum Body: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/POST/requestBody/content/application\/json`. + case json(Components.Schemas.TunnelRequest) + } + package var body: Operations.startTunnel.Input.Body + /// Creates a new `Input`. + /// + /// - Parameters: + /// - headers: + /// - body: + package init( + headers: Operations.startTunnel.Input.Headers = .init(), + body: Operations.startTunnel.Input.Body + ) { + self.headers = headers + self.body = body + } + } + @frozen package enum Output: Sendable, Hashable { + package struct Created: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/POST/responses/201/content`. + @frozen package enum Body: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/POST/responses/201/content/application\/json`. + case json(Components.Schemas.TunnelResponse) + /// The associated value of the enum case if `self` is `.json`. + /// + /// - Throws: An error if `self` is not `.json`. + /// - SeeAlso: `.json`. + package var json: Components.Schemas.TunnelResponse { + get throws { + switch self { case let .json(body): return body + } + } + } } + /// Received HTTP response body + package var body: Operations.startTunnel.Output.Created.Body + /// Creates a new `Created`. + /// + /// - Parameters: + /// - body: Received HTTP response body + package init(body: Operations.startTunnel.Output.Created.Body) { self.body = body } + } + /// Tunnel started successfully + /// + /// - Remark: Generated from `#/paths//api/tunnels/post(startTunnel)/responses/201`. + /// + /// HTTP response code: `201 created`. + case created(Operations.startTunnel.Output.Created) + /// The associated value of the enum case if `self` is `.created`. + /// + /// - Throws: An error if `self` is not `.created`. + /// - SeeAlso: `.created`. + package var created: Operations.startTunnel.Output.Created { + get throws { + switch self { case let .created(response): return response default: + try throwUnexpectedResponseStatus(expectedStatus: "created", response: self) + } + } + } + /// Undocumented response. + /// + /// A response with a code that is not documented in the OpenAPI document. + case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) } - /// Tunnel detail - /// - /// - Remark: HTTP `GET /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. - package enum getTunnel { - package static let id: Swift.String = "getTunnel" - package struct Input: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/path`. - package struct Path: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/path/name`. - package var name: Swift.String - /// Creates a new `Path`. - /// - /// - Parameters: - /// - name: - package init(name: Swift.String) { - self.name = name - } - } - package var path: Operations.getTunnel.Input.Path - /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/header`. - package struct Headers: Sendable, Hashable { - package var accept: [OpenAPIRuntime.AcceptHeaderContentType] - /// Creates a new `Headers`. - /// - /// - Parameters: - /// - accept: - package init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { - self.accept = accept - } - } - package var headers: Operations.getTunnel.Input.Headers - /// Creates a new `Input`. - /// - /// - Parameters: - /// - path: - /// - headers: - package init( - path: Operations.getTunnel.Input.Path, - headers: Operations.getTunnel.Input.Headers = .init() - ) { - self.path = path - self.headers = headers - } + @frozen package enum AcceptableContentType: AcceptableProtocol { + case json + case other(Swift.String) + package init?(rawValue: Swift.String) { + switch rawValue.lowercased() { case "application/json": self = .json default: + self = .other(rawValue) } - @frozen package enum Output: Sendable, Hashable { - package struct Ok: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/responses/200/content`. - @frozen package enum Body: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/responses/200/content/application\/json`. - case json(OpenAPIRuntime.OpenAPIValueContainer) - /// The associated value of the enum case if `self` is `.json`. - /// - /// - Throws: An error if `self` is not `.json`. - /// - SeeAlso: `.json`. - package var json: OpenAPIRuntime.OpenAPIValueContainer { - get throws { - switch self { - case let .json(body): - return body - } - } - } - } - /// Received HTTP response body - package var body: Operations.getTunnel.Output.Ok.Body - /// Creates a new `Ok`. - /// - /// - Parameters: - /// - body: Received HTTP response body - package init(body: Operations.getTunnel.Output.Ok.Body) { - self.body = body - } - } - /// Successful response - /// - /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)/responses/200`. - /// - /// HTTP response code: `200 ok`. - case ok(Operations.getTunnel.Output.Ok) - /// The associated value of the enum case if `self` is `.ok`. - /// - /// - Throws: An error if `self` is not `.ok`. - /// - SeeAlso: `.ok`. - package var ok: Operations.getTunnel.Output.Ok { - get throws { - switch self { - case let .ok(response): - return response - default: - try throwUnexpectedResponseStatus( - expectedStatus: "ok", - response: self - ) - } - } - } - /// Undocumented response. - /// - /// A response with a code that is not documented in the OpenAPI document. - case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) + } + package var rawValue: Swift.String { + switch self { case let .other(string): return string case .json: return "application/json" } - @frozen package enum AcceptableContentType: AcceptableProtocol { - case json - case other(Swift.String) - package init?(rawValue: Swift.String) { - switch rawValue.lowercased() { - case "application/json": - self = .json - default: - self = .other(rawValue) - } - } - package var rawValue: Swift.String { - switch self { - case let .other(string): - return string - case .json: - return "application/json" - } - } - package static var allCases: [Self] { - [ - .json - ] - } + } + package static var allCases: [Self] { [.json] } + } + } + /// Tunnel detail + /// + /// - Remark: HTTP `GET /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)`. + package enum getTunnel { + package static let id: Swift.String = "getTunnel" + package struct Input: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/path`. + package struct Path: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/path/name`. + package var name: Swift.String + /// Creates a new `Path`. + /// + /// - Parameters: + /// - name: + package init(name: Swift.String) { self.name = name } + } + package var path: Operations.getTunnel.Input.Path + /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/header`. + package struct Headers: Sendable, Hashable { + package var accept: + [OpenAPIRuntime.AcceptHeaderContentType] + /// Creates a new `Headers`. + /// + /// - Parameters: + /// - accept: + package init( + accept: [OpenAPIRuntime.AcceptHeaderContentType< + Operations.getTunnel.AcceptableContentType + >] = .defaultValues() + ) { self.accept = accept } + } + package var headers: Operations.getTunnel.Input.Headers + /// Creates a new `Input`. + /// + /// - Parameters: + /// - path: + /// - headers: + package init( + path: Operations.getTunnel.Input.Path, + headers: Operations.getTunnel.Input.Headers = .init() + ) { + self.path = path + self.headers = headers + } + } + @frozen package enum Output: Sendable, Hashable { + package struct Ok: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/responses/200/content`. + @frozen package enum Body: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/{name}/GET/responses/200/content/application\/json`. + case json(OpenAPIRuntime.OpenAPIValueContainer) + /// The associated value of the enum case if `self` is `.json`. + /// + /// - Throws: An error if `self` is not `.json`. + /// - SeeAlso: `.json`. + package var json: OpenAPIRuntime.OpenAPIValueContainer { + get throws { + switch self { case let .json(body): return body + } + } + } } + /// Received HTTP response body + package var body: Operations.getTunnel.Output.Ok.Body + /// Creates a new `Ok`. + /// + /// - Parameters: + /// - body: Received HTTP response body + package init(body: Operations.getTunnel.Output.Ok.Body) { self.body = body } + } + /// Successful response + /// + /// - Remark: Generated from `#/paths//api/tunnels/{name}/get(getTunnel)/responses/200`. + /// + /// HTTP response code: `200 ok`. + case ok(Operations.getTunnel.Output.Ok) + /// The associated value of the enum case if `self` is `.ok`. + /// + /// - Throws: An error if `self` is not `.ok`. + /// - SeeAlso: `.ok`. + package var ok: Operations.getTunnel.Output.Ok { + get throws { + switch self { case let .ok(response): return response default: + try throwUnexpectedResponseStatus(expectedStatus: "ok", response: self) + } + } + } + /// Undocumented response. + /// + /// A response with a code that is not documented in the OpenAPI document. + case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) } - /// Stop tunnel - /// - /// - Remark: HTTP `DELETE /api/tunnels/{name}`. - /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. - package enum stopTunnel { - package static let id: Swift.String = "stopTunnel" - package struct Input: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/{name}/DELETE/path`. - package struct Path: Sendable, Hashable { - /// - Remark: Generated from `#/paths/api/tunnels/{name}/DELETE/path/name`. - package var name: Swift.String - /// Creates a new `Path`. - /// - /// - Parameters: - /// - name: - package init(name: Swift.String) { - self.name = name - } - } - package var path: Operations.stopTunnel.Input.Path - /// Creates a new `Input`. - /// - /// - Parameters: - /// - path: - package init(path: Operations.stopTunnel.Input.Path) { - self.path = path - } + @frozen package enum AcceptableContentType: AcceptableProtocol { + case json + case other(Swift.String) + package init?(rawValue: Swift.String) { + switch rawValue.lowercased() { case "application/json": self = .json default: + self = .other(rawValue) } - @frozen package enum Output: Sendable, Hashable { - package struct NoContent: Sendable, Hashable { - /// Creates a new `NoContent`. - package init() {} - } - /// Tunnel stopped successfully - /// - /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)/responses/204`. - /// - /// HTTP response code: `204 noContent`. - case noContent(Operations.stopTunnel.Output.NoContent) - /// The associated value of the enum case if `self` is `.noContent`. - /// - /// - Throws: An error if `self` is not `.noContent`. - /// - SeeAlso: `.noContent`. - package var noContent: Operations.stopTunnel.Output.NoContent { - get throws { - switch self { - case let .noContent(response): - return response - default: - try throwUnexpectedResponseStatus( - expectedStatus: "noContent", - response: self - ) - } - } - } - /// Undocumented response. - /// - /// A response with a code that is not documented in the OpenAPI document. - case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) + } + package var rawValue: Swift.String { + switch self { case let .other(string): return string case .json: return "application/json" + } + } + package static var allCases: [Self] { [.json] } + } + } + /// Stop tunnel + /// + /// - Remark: HTTP `DELETE /api/tunnels/{name}`. + /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)`. + package enum stopTunnel { + package static let id: Swift.String = "stopTunnel" + package struct Input: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/{name}/DELETE/path`. + package struct Path: Sendable, Hashable { + /// - Remark: Generated from `#/paths/api/tunnels/{name}/DELETE/path/name`. + package var name: Swift.String + /// Creates a new `Path`. + /// + /// - Parameters: + /// - name: + package init(name: Swift.String) { self.name = name } + } + package var path: Operations.stopTunnel.Input.Path + /// Creates a new `Input`. + /// + /// - Parameters: + /// - path: + package init(path: Operations.stopTunnel.Input.Path) { self.path = path } + } + @frozen package enum Output: Sendable, Hashable { + package struct NoContent: Sendable, Hashable { + /// Creates a new `NoContent`. + package init() {} + } + /// Tunnel stopped successfully + /// + /// - Remark: Generated from `#/paths//api/tunnels/{name}/delete(stopTunnel)/responses/204`. + /// + /// HTTP response code: `204 noContent`. + case noContent(Operations.stopTunnel.Output.NoContent) + /// The associated value of the enum case if `self` is `.noContent`. + /// + /// - Throws: An error if `self` is not `.noContent`. + /// - SeeAlso: `.noContent`. + package var noContent: Operations.stopTunnel.Output.NoContent { + get throws { + switch self { case let .noContent(response): return response default: + try throwUnexpectedResponseStatus(expectedStatus: "noContent", response: self) + } } + } + /// Undocumented response. + /// + /// A response with a code that is not documented in the OpenAPI document. + case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload) } + } } diff --git a/Packages/Ngrokit/Sources/Ngrokit/FileHandle.swift b/Packages/Ngrokit/Sources/Ngrokit/FileHandle.swift index e904947..44bbf55 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/FileHandle.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/FileHandle.swift @@ -1,6 +1,6 @@ // // FileHandle.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -30,7 +30,7 @@ public import Foundation // swiftlint:disable:next force_try -private let ngrokCLIErrorRegex = try! NSRegularExpression(pattern: "ERR_NGROK_([0-9]+)") +fileprivate let ngrokCLIErrorRegex = try! NSRegularExpression(pattern: "ERR_NGROK_([0-9]+)") /// A protocol for handling data. public protocol DataHandle { @@ -49,24 +49,20 @@ extension DataHandle { /// - Returns: The parsed ngrok error code. /// - Throws: An error if there was a problem parsing the error code. internal func parseNgrokErrorCode() throws -> NgrokError { - guard let data = try readToEnd() else { - throw RuntimeError.unknownError - } + guard let data = try readToEnd() else { throw RuntimeError.unknownError } let text = String(decoding: data, as: UTF8.self) - guard let match = ngrokCLIErrorRegex.firstMatch( - in: text, - range: .init(location: 0, length: text.count) - ), match.numberOfRanges > 0 else { - throw RuntimeError.unknownEarlyTermination(text) - } + guard + let match = ngrokCLIErrorRegex.firstMatch( + in: text, + range: .init(location: 0, length: text.count) + ), match.numberOfRanges > 0 + else { throw RuntimeError.unknownEarlyTermination(text) } guard let range = Range(match.range(at: 1), in: text) else { throw RuntimeError.unknownEarlyTermination(text) } - guard let code = Int(text[range]) else { - throw RuntimeError.unknownEarlyTermination(text) - } + guard let code = Int(text[range]) else { throw RuntimeError.unknownEarlyTermination(text) } guard let error = NgrokError(rawValue: code) else { throw RuntimeError.unknownNgrokErrorCode(code) } diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokCLIAPI.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokCLIAPI.swift index dde9d32..6a16af0 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokCLIAPI.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokCLIAPI.swift @@ -1,6 +1,6 @@ // // NgrokCLIAPI.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokClient.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokClient.swift index 2491d27..cbfca92 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokClient.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokClient.swift @@ -1,6 +1,6 @@ // // NgrokClient.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -74,13 +74,8 @@ public struct NgrokClient: Sendable { self.init(underlyingClient: underlyingClient) } - internal init(underlyingClient: any APIProtocol) { - self.underlyingClient = underlyingClient - } - - public func status() async throws { - _ = try await self.underlyingClient.get_sol_api().ok - } + internal init(underlyingClient: any APIProtocol) { self.underlyingClient = underlyingClient } + public func status() async throws { _ = try await self.underlyingClient.get_sol_api().ok } /// Starts a new tunnel. /// @@ -92,11 +87,8 @@ public struct NgrokClient: Sendable { public func startTunnel(_ request: TunnelRequest) async throws -> NgrokTunnel { let tunnelRequest: Components.Schemas.TunnelRequest tunnelRequest = .init(request: request) - let response = try await underlyingClient.startTunnel( - .init( - body: .json(tunnelRequest) - ) - ).created.body.json + let response = try await underlyingClient.startTunnel(.init(body: .json(tunnelRequest))).created + .body.json let tunnel: NgrokTunnel = try .init(response: response) return tunnel } @@ -116,9 +108,6 @@ public struct NgrokClient: Sendable { /// /// - Throws: An error if the tunnel listing fails. public func listTunnels() async throws -> [NgrokTunnel] { - try await underlyingClient - .listTunnels() - .ok.body.json.tunnels - .map(NgrokTunnel.init(response:)) + try await underlyingClient.listTunnels().ok.body.json.tunnels.map(NgrokTunnel.init(response:)) } } diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokError.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokError.swift index 54ae124..4ec9144 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokError.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokError.swift @@ -1,6 +1,6 @@ // // NgrokError.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,7 +27,7 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import Foundation +public import Foundation // swiftlint:disable line_length @@ -69,40 +69,32 @@ public enum NgrokError: Int, LocalizedError { case tunnelConnectionFailed = 8_012 public var errorDescription: String? { - switch self { - case .invalidMetadataLength: - "Invalid metadata length" - case .accountLimitExceeded: + switch self { case .invalidMetadataLength: "Invalid metadata length" case .accountLimitExceeded: "You've hit your account limit for simultaneous ngrok agent sessions. Try stopping an existing agent or upgrading your account." - case .unsupportedAgentVersion: - "Your ngrok agent version is no longer supported. Only the most recent version of the ngrok agent is supported without an account. Update to a newer version with ngrok update or by downloading from https://ngrok.com/download. Sign up for an account to avoid forced version upgrades: https://ngrok.com/signup." - case .captchaFailed: - "You failed to solve the captcha, please try again." - case .accountViolation: - "You are disallowed from creating an ngrok account due to violation of the terms of service." - case .gatewayError: - "Ngrok gateway error. The server returned an invalid or incomplete HTTP response. Try starting ngrok with the full upstream service URL (e.g. ngrok http https://localhost:8081)" - case .tunnelNotFound: - "Tunnel not found. This could be because your agent is not online or your tunnel has been flagged by our automated moderation system." - case .accountBanned: - "The account associated with this hostname has been banned. We've determined this account to be in violation of ngrok's terms of service. If you are the account owner and believe this is a mistake, please contact support@ngrok.com." - case .passwordTooShort: - "Your password must be at least 10 characters." - case .accountCreationNotAllowed: - "You may not create a new account because you are already a member of a free account. Upgrade or delete that account first before creating a new account." - case .invalidCredentials: - "The email or password you entered is not valid." - case .userAlreadyExists: - "A user with the email address already exists." - case .disallowedEmailProvider: - "Sign-ups are disallowed for the email provider. Please sign up with a different email provider." - case .htmlContentSignupRequired: - "Before you can serve HTML content, you must sign up for an ngrok account and install your authtoken." - case .websiteVisitWarning: - "You are about to visit HOSTPORT, served by SERVINGIP. This website is served for free through ngrok.com. You should only visit this website if you trust whoever sent the link to you." - case .tunnelConnectionFailed: - "Traffic was successfully tunneled to the ngrok agent, but the agent failed to establish a connection to the upstream web service" + case .unsupportedAgentVersion: + "Your ngrok agent version is no longer supported. Only the most recent version of the ngrok agent is supported without an account. Update to a newer version with ngrok update or by downloading from https://ngrok.com/download. Sign up for an account to avoid forced version upgrades: https://ngrok.com/signup." + case .captchaFailed: "You failed to solve the captcha, please try again." + case .accountViolation: + "You are disallowed from creating an ngrok account due to violation of the terms of service." + case .gatewayError: + "Ngrok gateway error. The server returned an invalid or incomplete HTTP response. Try starting ngrok with the full upstream service URL (e.g. ngrok http https://localhost:8081)" + case .tunnelNotFound: + "Tunnel not found. This could be because your agent is not online or your tunnel has been flagged by our automated moderation system." + case .accountBanned: + "The account associated with this hostname has been banned. We've determined this account to be in violation of ngrok's terms of service. If you are the account owner and believe this is a mistake, please contact support@ngrok.com." + case .passwordTooShort: "Your password must be at least 10 characters." + case .accountCreationNotAllowed: + "You may not create a new account because you are already a member of a free account. Upgrade or delete that account first before creating a new account." + case .invalidCredentials: "The email or password you entered is not valid." + case .userAlreadyExists: "A user with the email address already exists." + case .disallowedEmailProvider: + "Sign-ups are disallowed for the email provider. Please sign up with a different email provider." + case .htmlContentSignupRequired: + "Before you can serve HTML content, you must sign up for an ngrok account and install your authtoken." + case .websiteVisitWarning: + "You are about to visit HOSTPORT, served by SERVINGIP. This website is served for free through ngrok.com. You should only visit this website if you trust whoever sent the link to you." + case .tunnelConnectionFailed: + "Traffic was successfully tunneled to the ngrok agent, but the agent failed to establish a connection to the upstream web service" } - } - // swiftlint:enable line_length + } // swiftlint:enable line_length } diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokMacProcess.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokMacProcess.swift index 428a28e..cb75f7e 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokMacProcess.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokMacProcess.swift @@ -1,6 +1,6 @@ // // NgrokMacProcess.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -43,7 +43,6 @@ import Foundation /// - SeeAlso: `NgrokProcess` public actor NgrokMacProcess: NgrokProcess { - private var terminationHandler: (@Sendable (any Error) -> Void)? internal let process: ProcessType private let pipe: ProcessType.PipeType @@ -56,18 +55,8 @@ public actor NgrokMacProcess: NgrokProcess { /// - processType: The type of process to use. /// /// - Returns: A new instance of `NgrokMacProcess`. - public init( - ngrokPath: String, - httpPort: Int, - processType _: ProcessType.Type - ) { - self.init( - process: .init( - executableFilePath: ngrokPath, - scheme: "http", - port: httpPort - ) - ) + public init(ngrokPath: String, httpPort: Int, processType _: ProcessType.Type) { + self.init(process: .init(executableFilePath: ngrokPath, scheme: "http", port: httpPort)) } private init( @@ -79,7 +68,8 @@ public actor NgrokMacProcess: NgrokProcess { self.process = process if let pipe { self.pipe = pipe - } else { + } + else { let newPipe: ProcessType.PipeType = process.createPipe() self.process.standardError = newPipe self.pipe = newPipe @@ -90,15 +80,11 @@ public actor NgrokMacProcess: NgrokProcess { /// /// - Parameters: /// - forProcess: The process that has terminated. - @Sendable - private nonisolated func terminationHandler(forProcess _: any Processable) { + @Sendable private nonisolated func terminationHandler(forProcess _: any Processable) { Task { let error: any Error - do { - error = try self.pipe.fileHandleForReading.parseNgrokErrorCode() - } catch let runtimeError as RuntimeError { - error = runtimeError - } + do { error = try self.pipe.fileHandleForReading.parseNgrokErrorCode() } + catch let runtimeError as RuntimeError { error = runtimeError } await self.terminationHandler?(error) } } @@ -114,8 +100,5 @@ public actor NgrokMacProcess: NgrokProcess { terminationHandler = onError try process.run() } - - nonisolated public func terminate() { - self.process.terminate() - } + nonisolated public func terminate() { self.process.terminate() } } diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokProcess.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokProcess.swift index 3624bc2..7e209fe 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokProcess.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokProcess.swift @@ -1,6 +1,6 @@ // // NgrokProcess.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -44,6 +44,5 @@ public protocol NgrokProcess: Sendable { /// /// - Parameter onError: A closure to handle any errors that occur during the process. func run(onError: @Sendable @escaping (any Error) -> Void) async throws - - func terminate () + func terminate() } diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokProcessCLIAPI.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokProcessCLIAPI.swift index 6d73a14..2e5b65e 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokProcessCLIAPI.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokProcessCLIAPI.swift @@ -1,6 +1,6 @@ // // NgrokProcessCLIAPI.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -46,9 +46,7 @@ public struct NgrokProcessCLIAPI { /// Initializes a new instance of `NgrokProcessCLIAPI`. /// /// - Parameter ngrokPath: The path to the Ngrok executable. - public init(ngrokPath: String) { - self.ngrokPath = ngrokPath - } + public init(ngrokPath: String) { self.ngrokPath = ngrokPath } } extension NgrokProcessCLIAPI: NgrokCLIAPI { @@ -58,10 +56,6 @@ extension NgrokProcessCLIAPI: NgrokCLIAPI { /// /// - Returns: An instance of `NgrokProcess` for the specified HTTP port. public func process(forHTTPPort httpPort: Int) -> any NgrokProcess { - NgrokMacProcess( - ngrokPath: ngrokPath, - httpPort: httpPort, - processType: ProcessType.self - ) + NgrokMacProcess(ngrokPath: ngrokPath, httpPort: httpPort, processType: ProcessType.self) } } diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnel.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnel.swift index dd36924..f883e8c 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnel.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnel.swift @@ -1,6 +1,6 @@ // // NgrokTunnel.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -97,10 +97,7 @@ extension NgrokTunnel { self.init( name: response.name, publicURL: publicURL, - config: .init( - addr: addr, - inspect: response.config.inspect - ) + config: .init(addr: addr, inspect: response.config.inspect) ) } } diff --git a/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnelConfiguration.swift b/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnelConfiguration.swift index f17590b..79a04c6 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnelConfiguration.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/NgrokTunnelConfiguration.swift @@ -1,6 +1,6 @@ // // NgrokTunnelConfiguration.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/Ngrokit/Sources/Ngrokit/Pipable.swift b/Packages/Ngrokit/Sources/Ngrokit/Pipable.swift index c745b58..1ab6780 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/Pipable.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/Pipable.swift @@ -1,6 +1,6 @@ // // Pipable.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/Ngrokit/Sources/Ngrokit/Processable.swift b/Packages/Ngrokit/Sources/Ngrokit/Processable.swift index cb4985e..eb7fda2 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/Processable.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/Processable.swift @@ -1,6 +1,6 @@ // // Processable.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -100,6 +100,5 @@ public protocol Processable: Sendable, AnyObject { /// /// - Requires: This method must be implemented. func run() throws - - func terminate () + func terminate() } diff --git a/Packages/Ngrokit/Sources/Ngrokit/ProcessableProcess.swift b/Packages/Ngrokit/Sources/Ngrokit/ProcessableProcess.swift index 27651fc..c59ab46 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/ProcessableProcess.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/ProcessableProcess.swift @@ -1,6 +1,6 @@ // // ProcessableProcess.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -39,31 +39,22 @@ public import Foundation /// property before executing the process. /// /// - SeeAlso: `Processable` -public final class ProcessableProcess: Processable { + public final class ProcessableProcess: Processable { - /// The type of pipe used for standard error. public typealias PipeType = Pipe private let process: Process - public var terminationReason: TerminationReason { - process.terminationReason - } + public var terminationReason: TerminationReason { process.terminationReason } /// The pipe used for standard error. public var standardError: Pipe? { - get { - process.standardError as? Pipe - } - set { - process.standardError = newValue - } + get { process.standardError as? Pipe } + set { process.standardError = newValue } } - private init(process: Process) { - self.process = process - } + private init(process: Process) { self.process = process } /// Initializes a new `ProcessableProcess` instance. /// @@ -84,9 +75,7 @@ public final class ProcessableProcess: Processable { /// Sets the termination handler closure for the process. /// /// - Parameter closure: The closure to be called when the process terminates. - public func setTerminationHandler( - _ closure: @escaping @Sendable (ProcessableProcess) -> Void - ) { + public func setTerminationHandler(_ closure: @escaping @Sendable (ProcessableProcess) -> Void) { process.terminationHandler = { process in assert(process == self.process) closure(self) @@ -96,17 +85,10 @@ public final class ProcessableProcess: Processable { /// Creates a new pipe. /// /// - Returns: A new `Pipe` instance. - public func createPipe() -> Pipe { - Pipe() - } + public func createPipe() -> Pipe { Pipe() } - public func run() throws { - try process.run() - } - - public func terminate() { - process.terminate() - } + public func run() throws { try process.run() } + public func terminate() { process.terminate() } } extension Pipe: Pipable {} diff --git a/Packages/Ngrokit/Sources/Ngrokit/RuntimeError.swift b/Packages/Ngrokit/Sources/Ngrokit/RuntimeError.swift index 0cd9ba8..e2b88a1 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/RuntimeError.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/RuntimeError.swift @@ -1,6 +1,6 @@ // // RuntimeError.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/Ngrokit/Sources/Ngrokit/TerminationReason.swift b/Packages/Ngrokit/Sources/Ngrokit/TerminationReason.swift index eaf850a..89f924c 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/TerminationReason.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/TerminationReason.swift @@ -1,6 +1,6 @@ // // TerminationReason.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,14 +27,11 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public import Foundation - -// swiftlint:disable file_types_order #if os(macOS) + public import Foundation /// Represents the reason for the termination of a process. public typealias TerminationReason = Process.TerminationReason #else - /// Represents the reason for the termination of a process. /// /// - exit: The process exited normally. @@ -43,5 +40,4 @@ public import Foundation case exit = 1 case uncaughtSignal = 2 } -#endif -// swiftlint:enable file_types_order +#endif // swiftlint:enable file_types_order diff --git a/Packages/Ngrokit/Sources/Ngrokit/TunnelRequest.swift b/Packages/Ngrokit/Sources/Ngrokit/TunnelRequest.swift index 1255f11..69fc4ee 100644 --- a/Packages/Ngrokit/Sources/Ngrokit/TunnelRequest.swift +++ b/Packages/Ngrokit/Sources/Ngrokit/TunnelRequest.swift @@ -1,6 +1,6 @@ // // TunnelRequest.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/Ngrokit/Sources/NgrokitMocks/MockAPI.swift b/Packages/Ngrokit/Sources/NgrokitMocks/MockAPI.swift index 4bfc397..d5da344 100644 --- a/Packages/Ngrokit/Sources/NgrokitMocks/MockAPI.swift +++ b/Packages/Ngrokit/Sources/NgrokitMocks/MockAPI.swift @@ -1,6 +1,6 @@ // // MockAPI.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -28,7 +28,7 @@ // import Foundation -import NgrokOpenAPIClient +package import NgrokOpenAPIClient #if canImport(FoundationNetworking) import FoundationNetworking @@ -55,37 +55,32 @@ package final actor MockAPI: APIProtocol { } // swiftlint:disable unavailable_function force_unwrapping - package func getTunnel( - _: NgrokOpenAPIClient.Operations.getTunnel.Input - ) async throws -> NgrokOpenAPIClient.Operations.getTunnel.Output { - fatalError("not implemented") - } + package func getTunnel(_: NgrokOpenAPIClient.Operations.getTunnel.Input) async throws + -> NgrokOpenAPIClient.Operations.getTunnel.Output + { fatalError("not implemented") } - package func stopTunnel( - _ input: Operations.stopTunnel.Input - ) async throws -> Operations.stopTunnel.Output { + package func stopTunnel(_ input: Operations.stopTunnel.Input) async throws + -> Operations.stopTunnel.Output + { stopTunnelPassed.append(input) return try actualStopTunnelResult!.get() } - package func startTunnel( - _ input: Operations.startTunnel.Input - ) async throws -> Operations.startTunnel.Output { + package func startTunnel(_ input: Operations.startTunnel.Input) async throws + -> Operations.startTunnel.Output + { startTunnelPassed.append(input) return try actualStartTunnelResult!.get() } - package func listTunnels( - _ input: NgrokOpenAPIClient.Operations.listTunnels.Input - ) async throws -> NgrokOpenAPIClient.Operations.listTunnels.Output { + package func listTunnels(_ input: NgrokOpenAPIClient.Operations.listTunnels.Input) async throws + -> NgrokOpenAPIClient.Operations.listTunnels.Output + { listTunnelPassed.append(input) return try actualListTunnelResult!.get() } - package func get_sol_api( - _: NgrokOpenAPIClient.Operations.get_sol_api.Input - ) async throws -> NgrokOpenAPIClient.Operations.get_sol_api.Output { - fatalError("not implemented") - } - // swiftlint:enable unavailable_function force_unwrapping + package func get_sol_api(_: NgrokOpenAPIClient.Operations.get_sol_api.Input) async throws + -> NgrokOpenAPIClient.Operations.get_sol_api.Output + { fatalError("not implemented") } // swiftlint:enable unavailable_function force_unwrapping } diff --git a/Packages/Ngrokit/Sources/NgrokitMocks/MockDataHandle.swift b/Packages/Ngrokit/Sources/NgrokitMocks/MockDataHandle.swift index 85a46ee..9087367 100644 --- a/Packages/Ngrokit/Sources/NgrokitMocks/MockDataHandle.swift +++ b/Packages/Ngrokit/Sources/NgrokitMocks/MockDataHandle.swift @@ -1,6 +1,6 @@ // // MockDataHandle.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,33 +27,30 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import Foundation -import Ngrokit +public import Foundation +public import Ngrokit -package struct MockDataHandle: DataHandle { - package static let code: Data = .init(""" - ERROR: authentication failed: Your account is limited to 1 simultaneous ngrok agent session. - ERROR: You can run multiple tunnels on a single agent session using a configuration file. - ERROR: To learn more, see https://ngrok.com/docs/secure-tunnels/ngrok-agent/reference/config/ - ERROR: - ERROR: Active ngrok agent sessions in region 'us': - ERROR: - ts_2bjiyVxWh6dMoaZUfjXNsHWFNta (2607:fb90:8da8:5b15:900b:13fd:c5e7:f9c6) - ERROR: - ERROR: ERR_NGROK_108 - ERROR: - """.utf8) +public struct MockDataHandle: DataHandle, Sendable { + package static let code: Data = .init( + """ + ERROR: authentication failed: Your account is limited to 1 simultaneous ngrok agent session. + ERROR: You can run multiple tunnels on a single agent session using a configuration file. + ERROR: To learn more, see https://ngrok.com/docs/secure-tunnels/ngrok-agent/reference/config/ + ERROR: + ERROR: Active ngrok agent sessions in region 'us': + ERROR: - ts_2bjiyVxWh6dMoaZUfjXNsHWFNta (2607:fb90:8da8:5b15:900b:13fd:c5e7:f9c6) + ERROR: + ERROR: ERR_NGROK_108 + ERROR: + """ + .utf8 + ) private let actualResult: Result - package init(_ actualResult: Result) { - self.actualResult = actualResult - } + package init(_ actualResult: Result) { self.actualResult = actualResult } - package static func withNgrokCode() -> MockDataHandle { - .init(.success(code)) - } + package static func withNgrokCode() -> MockDataHandle { .init(.success(code)) } - package func readToEnd() throws -> Data? { - try actualResult.get() - } + public func readToEnd() throws -> Data? { try actualResult.get() } } diff --git a/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokCLIAPI.swift b/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokCLIAPI.swift index 9080351..aa6cb81 100644 --- a/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokCLIAPI.swift +++ b/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokCLIAPI.swift @@ -1,6 +1,6 @@ // // MockNgrokCLIAPI.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,22 +27,16 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import Foundation -import Ngrokit +public import Foundation +public import Ngrokit -package final class MockNgrokCLIAPI: NgrokCLIAPI { +public final actor MockNgrokCLIAPI: NgrokCLIAPI { package let process: any NgrokProcess package private(set) var httpPorts = [Int]() - package convenience init(id: UUID) { - self.init(process: MockNgrokProcess(id: id)) - } + public init(id: UUID) { self.init(process: MockNgrokProcess(id: id)) } - internal init(process: any NgrokProcess) { - self.process = process - } + internal init(process: any NgrokProcess) { self.process = process } - package func process(forHTTPPort _: Int) -> any Ngrokit.NgrokProcess { - process - } + public nonisolated func process(forHTTPPort _: Int) -> any Ngrokit.NgrokProcess { process } } diff --git a/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokProcess.swift b/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokProcess.swift index 5016487..a1ae3c0 100644 --- a/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokProcess.swift +++ b/Packages/Ngrokit/Sources/NgrokitMocks/MockNgrokProcess.swift @@ -1,6 +1,6 @@ // // MockNgrokProcess.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,15 +27,14 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import Foundation -import Ngrokit +public import Foundation +public import Ngrokit -package final class MockNgrokProcess: NgrokProcess { - package let id: UUID +public final class MockNgrokProcess: NgrokProcess { + public func terminate() {} + public let id: UUID - package init(id: UUID) { - self.id = id - } + public init(id: UUID) { self.id = id } - package func run(onError _: @escaping @Sendable (any Error) -> Void) async throws {} + public func run(onError _: @escaping @Sendable (any Error) -> Void) async throws {} } diff --git a/Packages/Ngrokit/Sources/NgrokitMocks/MockPipe.swift b/Packages/Ngrokit/Sources/NgrokitMocks/MockPipe.swift index da7186d..986bf85 100644 --- a/Packages/Ngrokit/Sources/NgrokitMocks/MockPipe.swift +++ b/Packages/Ngrokit/Sources/NgrokitMocks/MockPipe.swift @@ -1,6 +1,6 @@ // // MockPipe.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -28,12 +28,12 @@ // import Foundation -import Ngrokit +public import Ngrokit -package final class MockPipe: Pipable { - package typealias DataHandleType = MockDataHandle +public final class MockPipe: Pipable { + public typealias DataHandleType = MockDataHandle - package let fileHandleForReading: MockDataHandle + public let fileHandleForReading: MockDataHandle internal init(fileHandleForReading: MockDataHandle) { self.fileHandleForReading = fileHandleForReading diff --git a/Packages/Ngrokit/Sources/NgrokitMocks/MockProcess.swift b/Packages/Ngrokit/Sources/NgrokitMocks/MockProcess.swift index 8b38601..280b022 100644 --- a/Packages/Ngrokit/Sources/NgrokitMocks/MockProcess.swift +++ b/Packages/Ngrokit/Sources/NgrokitMocks/MockProcess.swift @@ -1,6 +1,6 @@ // // MockProcess.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,22 +27,23 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import Foundation -import Ngrokit +public import Foundation +public import Ngrokit -package final class MockProcess: Processable { - package typealias PipeType = MockPipe +public final class MockProcess: Processable { + public func terminate() {} + public typealias PipeType = MockPipe package let executableFilePath: String package let scheme: String package let port: Int package let pipeDataResult: Result package let runError: (any Error)? - package let terminationReason: Ngrokit.TerminationReason - package var standardError: MockPipe? + public let terminationReason: Ngrokit.TerminationReason + public nonisolated(unsafe) var standardError: MockPipe? - package private(set) var isTerminationHandlerSet = false - package private(set) var isRunCalled = false + package private(set) nonisolated(unsafe) var isTerminationHandlerSet = false + package private(set) nonisolated(unsafe) var isRunCalled = false internal init( executableFilePath: String, @@ -62,11 +63,7 @@ package final class MockProcess: Processable { self.runError = runError } - package convenience init( - executableFilePath: String, - scheme: String, - port: Int - ) { + public convenience init(executableFilePath: String, scheme: String, port: Int) { self.init( executableFilePath: executableFilePath, scheme: scheme, @@ -75,18 +72,16 @@ package final class MockProcess: Processable { ) } - package nonisolated func createPipe() -> MockPipe { + public nonisolated func createPipe() -> MockPipe { .init(fileHandleForReading: .init(pipeDataResult)) } - package func setTerminationHandler(_: @escaping @Sendable (MockProcess) -> Void) { + public func setTerminationHandler(_: @escaping @Sendable (MockProcess) -> Void) { isTerminationHandlerSet = true } - package func run() throws { + public func run() throws { isRunCalled = true - if let error = runError { - throw error - } + if let error = runError { throw error } } } diff --git a/Packages/Ngrokit/Sources/NgrokitMocks/URL.swift b/Packages/Ngrokit/Sources/NgrokitMocks/URL.swift index 1417f8a..5bc013a 100644 --- a/Packages/Ngrokit/Sources/NgrokitMocks/URL.swift +++ b/Packages/Ngrokit/Sources/NgrokitMocks/URL.swift @@ -1,6 +1,6 @@ // // URL.swift -// Sublimation +// Ngrokit // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,14 +27,12 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import Foundation +package import Foundation #if canImport(FoundationNetworking) - import FoundationNetworking + package import FoundationNetworking #endif extension URL { - package static func temporaryDirectory() -> URL { - URL(fileURLWithPath: NSTemporaryDirectory()) - } + package static func temporaryDirectory() -> URL { URL(fileURLWithPath: NSTemporaryDirectory()) } } diff --git a/Tests/NgrokitTests/DataHandleTests.swift b/Packages/Ngrokit/Tests/NgrokitTests/DataHandleTests.swift similarity index 100% rename from Tests/NgrokitTests/DataHandleTests.swift rename to Packages/Ngrokit/Tests/NgrokitTests/DataHandleTests.swift diff --git a/Tests/NgrokitTests/NgrokClientTests.swift b/Packages/Ngrokit/Tests/NgrokitTests/NgrokClientTests.swift similarity index 100% rename from Tests/NgrokitTests/NgrokClientTests.swift rename to Packages/Ngrokit/Tests/NgrokitTests/NgrokClientTests.swift diff --git a/Tests/NgrokitTests/NgrokErrorTests.swift b/Packages/Ngrokit/Tests/NgrokitTests/NgrokErrorTests.swift similarity index 100% rename from Tests/NgrokitTests/NgrokErrorTests.swift rename to Packages/Ngrokit/Tests/NgrokitTests/NgrokErrorTests.swift diff --git a/Tests/NgrokitTests/NgrokMacProcessTests.swift b/Packages/Ngrokit/Tests/NgrokitTests/NgrokMacProcessTests.swift similarity index 100% rename from Tests/NgrokitTests/NgrokMacProcessTests.swift rename to Packages/Ngrokit/Tests/NgrokitTests/NgrokMacProcessTests.swift diff --git a/Tests/NgrokitTests/NgrokProcessCLIAPITests.swift b/Packages/Ngrokit/Tests/NgrokitTests/NgrokProcessCLIAPITests.swift similarity index 100% rename from Tests/NgrokitTests/NgrokProcessCLIAPITests.swift rename to Packages/Ngrokit/Tests/NgrokitTests/NgrokProcessCLIAPITests.swift diff --git a/Packages/SublimationBonjour/Package.swift b/Packages/SublimationBonjour/Package.swift index 5fabad9..89719ba 100644 --- a/Packages/SublimationBonjour/Package.swift +++ b/Packages/SublimationBonjour/Package.swift @@ -41,7 +41,7 @@ let package = Package( .library(name: "SublimationBonjour", targets: ["SublimationBonjour"]) ], dependencies: [ - .package(path: "../.."), + .package(name: "Sublimation" , path: "../.."), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.26.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0") ], @@ -54,6 +54,10 @@ let package = Package( .product(name: "SwiftProtobuf", package: "swift-protobuf") ], swiftSettings: swiftSettings + ), + .testTarget( + name: "SublimationBonjourTests", + dependencies: ["SublimationBonjour"] ) ] ) diff --git a/Packages/SublimationBonjour/Scripts/lint.sh b/Packages/SublimationBonjour/Scripts/lint.sh index e8b0009..65560e4 100755 --- a/Packages/SublimationBonjour/Scripts/lint.sh +++ b/Packages/SublimationBonjour/Scripts/lint.sh @@ -13,10 +13,10 @@ MINT_RUN="/opt/homebrew/bin/mint run $MINT_ARGS" if [ -z "$SRCROOT" ]; then SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PACKAGE_DIR="${SCRIPT_DIR}/.." - PERIPHERY_OPTIONS="--skip-build" -else - PACKAGE_DIR="${SRCROOT}" PERIPHERY_OPTIONS="" +else + PACKAGE_DIR="${SRCROOT}" + PERIPHERY_OPTIONS="--skip-build" fi @@ -32,6 +32,10 @@ fi echo "LINT Mode is $LINT_MODE" +if [ "$LINT_MODE" == "INSTALL" ]; then + exit +fi + if [ -z "$CI" ]; then $MINT_RUN swift-format format --recursive --parallel --in-place $PACKAGE_DIR/Sources else diff --git a/Packages/SublimationBonjour/Sources/SublimationBonjour/ServerConfiguration.pb.swift b/Packages/SublimationBonjour/Sources/SublimationBonjour/BindingConfiguration+Protobuf.swift similarity index 91% rename from Packages/SublimationBonjour/Sources/SublimationBonjour/ServerConfiguration.pb.swift rename to Packages/SublimationBonjour/Sources/SublimationBonjour/BindingConfiguration+Protobuf.swift index 1f5600d..e400c41 100644 --- a/Packages/SublimationBonjour/Sources/SublimationBonjour/ServerConfiguration.pb.swift +++ b/Packages/SublimationBonjour/Sources/SublimationBonjour/BindingConfiguration+Protobuf.swift @@ -1,5 +1,5 @@ // -// ServerConfiguration.pb.swift +// BindingConfiguration+Protobuf.swift // SublimationBonjour // // Created by Leo Dion. @@ -28,7 +28,7 @@ // private import Foundation -package import SwiftProtobuf +public import SwiftProtobuf // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -67,9 +67,9 @@ public struct BindingConfiguration { package var hosts: [String] = [] - package var unknownFields = SwiftProtobuf.UnknownStorage() + public var unknownFields = SwiftProtobuf.UnknownStorage() - package init() {} + public init() {} private var _isSecure: Bool? private var _port: UInt32? @@ -84,12 +84,12 @@ public struct BindingConfiguration { extension BindingConfiguration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - package static let protoMessageName: String = "ServerConfiguration" - package static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + public static let protoMessageName: String = "ServerConfiguration" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "is_secure"), 2: .same(proto: "port"), 9: .same(proto: "hosts"), ] - package mutating func decodeMessage(decoder: inout some SwiftProtobuf.Decoder) throws { + public mutating func decodeMessage(decoder: inout some SwiftProtobuf.Decoder) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every case branch when no optimizations are @@ -102,7 +102,7 @@ extension BindingConfiguration: SwiftProtobuf.Message, SwiftProtobuf._MessageImp } } - package func traverse(visitor: inout some SwiftProtobuf.Visitor) throws { + public func traverse(visitor: inout some SwiftProtobuf.Visitor) throws { // The use of inline closures is to circumvent an issue where the compiler // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and diff --git a/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BindingConfiguration.swift b/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BindingConfiguration+URL.swift similarity index 87% rename from Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BindingConfiguration.swift rename to Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BindingConfiguration+URL.swift index 99a7173..faacace 100644 --- a/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BindingConfiguration.swift +++ b/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BindingConfiguration+URL.swift @@ -1,5 +1,5 @@ // -// BindingConfiguration.swift +// BindingConfiguration+URL.swift // SublimationBonjour // // Created by Leo Dion. @@ -34,9 +34,9 @@ internal import Network extension BindingConfiguration { - internal func urls(defaultIsSecure: Bool, defaultPort: Int) -> [URL] { - let isSecure = self.hasIsSecure ? self.isSecure : defaultIsSecure - let port = self.hasPort ? Int(self.port) : defaultPort + internal func urls(defaults: URLDefaultConfiguration) -> [URL] { + let isSecure = self.hasIsSecure ? self.isSecure : defaults.isSecure + let port = self.hasPort ? Int(self.port) : defaults.port return self.hosts.compactMap { host in if host.isLocalhost() { return nil } if host.isValidIPv6Address() { return nil } diff --git a/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BonjourClient.swift b/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BonjourClient.swift index c7fa4e2..3413f82 100644 --- a/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BonjourClient.swift +++ b/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BonjourClient.swift @@ -38,10 +38,20 @@ public import Logging #endif + public struct URLDefaultConfiguration { + public init(isSecure: Bool = false, port: Int = 8080) { + self.isSecure = isSecure + self.port = port + } + public let isSecure: Bool + public let port: Int + } + public actor BonjourClient { private let browser: NWBrowser private let streams = StreamManager() private let logger: Logger? + private let defaultURLConfiguration: URLDefaultConfiguration public var urls: AsyncStream { get async { @@ -63,15 +73,13 @@ } } - public init(logger: Logger? = nil) { + public init(logger: Logger? = nil, defaultURLConfiguration: URLDefaultConfiguration = .init()) { assert(logger != nil) let descriptor: NWBrowser.Descriptor - #if os(watchOS) - descriptor = .bonjourWithTXTRecord(type: "_sublimation._tcp", domain: nil) - #else - descriptor = .bonjour(type: "_sublimation._tcp", domain: nil) - #endif + descriptor = .bonjourWithTXTRecord(type: "_sublimation._tcp", domain: nil) + let browser = NWBrowser(for: descriptor, using: .tcp) + self.defaultURLConfiguration = defaultURLConfiguration self.browser = browser self.logger = logger browser.browseResultsChangedHandler = { results, _ in self.parseResults(results) } @@ -126,7 +134,6 @@ } let values = pairs.map(\.1) guard let data: Data = .init(base64Encoded: values.joined()) else { - // self.logger?.error("Unable to decode Base64 TXT Record for \(result.endpoint.debugDescription)") throw TXTRecordError.base64Decoding } return try .init(serializedData: data) @@ -146,7 +153,7 @@ continue } #warning("Defaults should be passed to connection") - let urls = configuration.urls(defaultIsSecure: false, defaultPort: 8_080) + let urls = configuration.urls(defaults: self.defaultURLConfiguration) self.append(urls: urls) } } diff --git a/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BonjourConnection.swift b/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BonjourConnection.swift deleted file mode 100644 index cd319b6..0000000 --- a/Packages/SublimationBonjour/Sources/SublimationBonjour/Client/BonjourConnection.swift +++ /dev/null @@ -1,116 +0,0 @@ -// -// BonjourConnection.swift -// SublimationBonjour -// -// Created by Leo Dion. -// Copyright © 2024 BrightDigit. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -//// -//// BonjourConnection.swift -//// SublimationBonjour -//// -//// Created by Leo Dion. -//// Copyright © 2024 BrightDigit. -//// -//// Permission is hereby granted, free of charge, to any person -//// obtaining a copy of this software and associated documentation -//// files (the “Software”), to deal in the Software without -//// restriction, including without limitation the rights to use, -//// copy, modify, merge, publish, distribute, sublicense, and/or -//// sell copies of the Software, and to permit persons to whom the -//// Software is furnished to do so, subject to the following -//// conditions: -//// -//// The above copyright notice and this permission notice shall be -//// included in all copies or substantial portions of the Software. -//// -//// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -//// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -//// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -//// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -//// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -//// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -//// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -//// OTHER DEALINGS IN THE SOFTWARE. -//// -// -//#if canImport(Network) -// internal import Foundation -// -// internal import Network -// -// internal actor BonjourConnection { -// private let connection: NWConnection -// private let client: BonjourClient -// internal let id: UUID -// -// -// private static func onConnection( -// _ connection: NWConnection, -// withID id: UUID, -// state: NWConnection.State, -// from client: BonjourClient -// ) { -// client.connection(withID: id, updatedTo: state) -// switch state { case .ready: -// connection.receiveMessage { content, contentContext, isComplete, error in -// let configuration: BindingConfiguration? -// do { configuration = try .init(content, contentContext, isComplete, error) } -// catch { -// client.connection(withID: id, failedWithError: error) -// return -// } -// -// guard let configuration else { return } -// #warning("Defaults should be passed to connection") -// let urls = configuration.urls(defaultIsSecure: false, defaultPort: 8_080) -// client.connection(withID: id, received: urls) -// } -// case let .waiting(error): client.connection(withID: id, failedWithError: error) -// case let .failed(error): client.connection(withID: id, failedWithError: error) -// case .cancelled: client.cancelledConnection(withID: id) -// default: break -// } -// } -// -// internal nonisolated func cancel() { Task { await self.completeCancel() } } -// -// private func completeCancel() { self.connection.cancel() } -// } -// -// extension BonjourConnection { -// internal init?(result: NWBrowser.Result, client: BonjourClient) { -// guard -// case let .service(name: name, type: type, domain: domain, interface: _) = result.endpoint -// else { return nil } -// let connection = NWConnection( -// to: .service(name: name, type: type, domain: domain, interface: nil), -// using: .tcp -// ) -// self.init(id: .init(), connection: connection, client: client) -// connection.start(queue: .global()) -// } -// } -//#endif diff --git a/Packages/SublimationBonjour/Tests/SublimationBonjourTests/SublimationBonjourTests.swift b/Packages/SublimationBonjour/Tests/SublimationBonjourTests/SublimationBonjourTests.swift new file mode 100644 index 0000000..78e1899 --- /dev/null +++ b/Packages/SublimationBonjour/Tests/SublimationBonjourTests/SublimationBonjourTests.swift @@ -0,0 +1,35 @@ +// +// SublimationBonjourTests.swift +// SublimationBonjour +// +// Created by Leo Dion on 7/30/24. +// + +import XCTest + +final class SublimationBonjourTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Packages/SublimationNgrok/Package.resolved b/Packages/SublimationNgrok/Package.resolved index 44c50d5..62bfbc0 100644 --- a/Packages/SublimationNgrok/Package.resolved +++ b/Packages/SublimationNgrok/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "4d82adafa6068ab225d36c2834ce182e6ca1528a9081fb4949f2d495d81d33c0", + "originHash" : "7958b55329765e55415847655566ddfb2dd94a7500820ab1db8731ad8c374073", "pins" : [ { "identity" : "swift-http-types", @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-openapi-runtime", "state" : { - "revision" : "71fcfa7691794054aa44538420acc281cc8e94e9", - "version" : "1.4.1" + "revision" : "26e8ae3515d1ff3607e924ac96fc0094775f55e8", + "version" : "1.5.0" } } ], diff --git a/Packages/SublimationNgrok/Package.swift b/Packages/SublimationNgrok/Package.swift index c78f598..e710b07 100644 --- a/Packages/SublimationNgrok/Package.swift +++ b/Packages/SublimationNgrok/Package.swift @@ -41,7 +41,7 @@ let package = Package( .library(name: "SublimationNgrok", targets: ["SublimationNgrok"]) ], dependencies: [ - .package(path: "../.."), + .package(name: "Sublimation", path: "../.."), .package(path: "../Ngrokit") ], targets: [ @@ -64,7 +64,24 @@ let package = Package( name: "SublimationKVdb", dependencies: ["SublimationTunnel"], swiftSettings: swiftSettings - ) + ), + .target( + name: "SublimationMocks", + dependencies: ["Sublimation"], + swiftSettings: swiftSettings + ), + .testTarget( + name: "SublimationKVdbTests", + dependencies: ["SublimationKVdb", "SublimationMocks"] + ), + .testTarget( + name: "SublimationTunnelTests", + dependencies: ["SublimationTunnel", "SublimationMocks"] + ), + .testTarget( + name: "SublimationNgrokTests", + dependencies: ["SublimationNgrok", "SublimationMocks", .product(name: "NgrokitMocks", package: "Ngrokit")] + ), ] ) // swiftlint:enable explicit_acl explicit_top_level_acl diff --git a/Packages/SublimationNgrok/Scripts/lint.sh b/Packages/SublimationNgrok/Scripts/lint.sh index 0ccd97b..0bace07 100755 --- a/Packages/SublimationNgrok/Scripts/lint.sh +++ b/Packages/SublimationNgrok/Scripts/lint.sh @@ -13,10 +13,10 @@ MINT_RUN="/opt/homebrew/bin/mint run $MINT_ARGS" if [ -z "$SRCROOT" ]; then SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PACKAGE_DIR="${SCRIPT_DIR}/.." - PERIPHERY_OPTIONS="--skip-build" -else - PACKAGE_DIR="${SRCROOT}" PERIPHERY_OPTIONS="" +else + PACKAGE_DIR="${SRCROOT}" + PERIPHERY_OPTIONS="--skip-build" fi @@ -32,6 +32,10 @@ fi echo "LINT Mode is $LINT_MODE" +if [ "$LINT_MODE" == "INSTALL" ]; then + exit +fi + if [ -z "$CI" ]; then $MINT_RUN swift-format format --recursive --parallel --in-place $PACKAGE_DIR/Sources else diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdb.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdb.swift index e7c37dc..52172ed 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdb.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdb.swift @@ -1,6 +1,6 @@ // // KVdb.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -74,32 +74,6 @@ public enum KVdb { forKey key: some Any, atBucket bucketName: String ) -> URLType { - URLType( - kvDBBase: baseString, - keyBucketPath: path(forKey: key, atBucket: bucketName) - ) + URLType(kvDBBase: baseString, keyBucketPath: path(forKey: key, atBucket: bucketName)) } - -// /// Retrieves the URL for a given key in a bucket. -// /// -// /// - Parameters: -// /// - key: The key for the value. -// /// - bucketName: The name of the bucket. -// /// - session: The URLSession to use for the request. Defaults to `.ephemeral`. -// /// -// /// - Returns: The URL for the key, or `nil` if it doesn't exist. -// /// -// /// - Throws: An error if the request fails. -// @available(*, deprecated) -// public static func url( -// withKey key: Key, -// atBucket bucketName: String, -// using session: URLSession = .ephemeral() -// ) async throws -> URL? { -// let repository = KVdbTunnelRepository( -// client: URLSessionClient(session: session), -// bucketName: bucketName -// ) -// return try await repository.tunnel(forKey: key) -// } } diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbServerError.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbServerError.swift index 634bd50..15d7a0c 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbServerError.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbServerError.swift @@ -1,6 +1,6 @@ // // KVdbServerError.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -37,8 +37,6 @@ public import Foundation /// - invalidURL: The URL is invalid. /// - cantSaveTunnel: Unable to save the tunnel with the given ID and data. public enum KVdbServerError: Error { -// case clientNotSetup -// case noTunnelFound case invalidURL case cantSaveTunnel(Int?, Data?) case cantSaveTunnelError(any Error) diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient 2.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient+TunnelClient.swift similarity index 93% rename from Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient 2.swift rename to Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient+TunnelClient.swift index 5b725d6..ce64a07 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient 2.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient+TunnelClient.swift @@ -1,6 +1,6 @@ // -// KVdbTunnelClient 2.swift -// Sublimation +// KVdbTunnelClient+TunnelClient.swift +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -28,6 +28,6 @@ // import Foundation -import SublimationTunnel +public import SublimationTunnel extension KVdbTunnelClient: TunnelClient {} diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient.swift index 700b993..cda71b1 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbTunnelClient.swift @@ -1,6 +1,6 @@ // // KVdbTunnelClient.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -65,19 +65,12 @@ public struct KVdbTunnelClient: Sendable { /// - Throws: `NgrokServerError.invalidURL` if the retrieved URL is invalid. /// /// - Returns: The URL associated with the key in the specified bucket. - public func getValue( - ofKey key: Key, - fromBucket bucketName: String - ) async throws -> URL { + public func getValue(ofKey key: Key, fromBucket bucketName: String) async throws -> URL { let uri = KVdb.construct(URL.self, forKey: key, atBucket: bucketName) let url: URL? - url = try await get(uri) - .map { String(decoding: $0, as: UTF8.self) } - .flatMap(URL.init(string:)) + url = try await get(uri).map { String(decoding: $0, as: UTF8.self) }.flatMap(URL.init(string:)) - guard let url else { - throw KVdbServerError.invalidURL - } + guard let url else { throw KVdbServerError.invalidURL } return url } @@ -88,16 +81,9 @@ public struct KVdbTunnelClient: Sendable { /// - Parameter bucketName: The name of the bucket where the value will be stored. /// /// - Throws: `NgrokServerError.cantSaveTunnel` if the tunnel cannot be saved. - public func saveValue( - _ value: URL, - withKey key: Key, - inBucket bucketName: String - ) async throws { + public func saveValue(_ value: URL, withKey key: Key, inBucket bucketName: String) async throws { let uri = KVdb.construct(URL.self, forKey: key, atBucket: bucketName) - do { - try await self.post(uri, value.absoluteString.data(using: .utf8)) - } catch { - throw KVdbServerError.cantSaveTunnelError(error) - } + do { try await self.post(uri, value.absoluteString.data(using: .utf8)) } + catch { throw KVdbServerError.cantSaveTunnelError(error) } } } diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbURLConstructable.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbURLConstructable.swift index c27276d..2f54c1c 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbURLConstructable.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/KVdbURLConstructable.swift @@ -1,6 +1,6 @@ // // KVdbURLConstructable.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/Optional.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/Optional.swift index e1d1be8..03ff12e 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/Optional.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/Optional.swift @@ -1,6 +1,6 @@ // // Optional.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -36,8 +36,6 @@ extension Optional { /// - Returns: A tuple containing the wrapped value of the optional and `other`, /// or `nil` if either the optional or `other` is `nil`. internal func flatTuple(_ other: OtherType?) -> (Wrapped, OtherType)? { - flatMap { wrapped in - other.map { (wrapped, $0) } - } + flatMap { wrapped in other.map { (wrapped, $0) } } } } diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/Result.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/Result.swift index d79dc96..9691bb6 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/Result.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/Result.swift @@ -1,6 +1,6 @@ // // Result.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -33,9 +33,11 @@ extension Result { internal init(success: Success?, failure: Failure?) where Failure == any Error { if let failure { self = .failure(failure) - } else if let success { + } + else if let success { self = .success(success) - } else { + } + else { self = .failure(EmptyError()) } } diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/URL+KVdbURLConstructable.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/URL+KVdbURLConstructable.swift index e5e0c24..e0293c8 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/URL+KVdbURLConstructable.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/URL+KVdbURLConstructable.swift @@ -1,6 +1,6 @@ // // URL+KVdbURLConstructable.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSession.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSession.swift index ba3d9c2..b7c4261 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSession.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSession.swift @@ -1,6 +1,6 @@ // // URLSession.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -38,50 +38,5 @@ extension URLSession { /// Creates a new ephemeral `URLSession` instance. /// /// - Returns: A new ephemeral `URLSession` instance. - public static func ephemeral() -> URLSession { - URLSession(configuration: .ephemeral) - } - - /// Fetches data asynchronously for the given request. - /// - /// - Parameter request: The request to fetch data for. - /// - /// - Returns: A tuple containing the fetched data and the URL response. - /// - /// - Throws: An error if the data fetching operation fails. - internal func dataAsync(for request: URLRequest) async throws -> (Data, URLResponse) { - #if !canImport(FoundationNetworking) - if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { - return try await self.data(for: request) - } - #endif - - return try await withCheckedThrowingContinuation { continuation in - let task = self.dataTask(with: request) { data, response, error in - continuation.resume( - with: .init( - success: data.flatTuple(response), - failure: error - ) - ) - } - task.resume() - } - } - - /// Fetches data asynchronously from the given URL. - /// - /// - Parameter url: The URL to fetch data from. - /// - /// - Returns: A tuple containing the fetched data and the URL response. - /// - /// - Throws: An error if the data fetching operation fails. - internal func dataAsync(from url: URL) async throws -> (Data, URLResponse) { - #if !canImport(FoundationNetworking) - if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { - return try await data(for: .init(url: url)) - } - #endif - return try await dataAsync(for: .init(url: url)) - } + public static func ephemeral() -> URLSession { URLSession(configuration: .ephemeral) } } diff --git a/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSessionClient.swift b/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSessionClient.swift index 0042c57..5dd40fd 100644 --- a/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSessionClient.swift +++ b/Packages/SublimationNgrok/Sources/SublimationKVdb/URLSessionClient.swift @@ -1,6 +1,6 @@ // // URLSessionClient.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -28,10 +28,10 @@ // public import Foundation -import SublimationTunnel +public import SublimationTunnel #if canImport(FoundationNetworking) - public import FoundationNetworking + @preconcurrency public import FoundationNetworking #endif /// A client for interacting with a KVdb tunnel using URLSession. @@ -51,9 +51,7 @@ public struct URLSessionClient: TunnelClient { /// /// - Parameter session: The URLSession to use for network requests. /// Defaults to an ephemeral session. - public init(session: URLSession = .ephemeral()) { - self.session = session - } + public init(session: URLSession = .ephemeral()) { self.session = session } /// Retrieves the value associated with a key from a specific bucket. /// @@ -64,18 +62,13 @@ public struct URLSessionClient: TunnelClient { /// - Returns: The URL value associated with the key. /// /// - Throws: A `NgrokServerError` if the retrieval operation fails. - public func getValue( - ofKey key: Key, - fromBucket bucketName: String - ) async throws -> URL { + public func getValue(ofKey key: Key, fromBucket bucketName: String) async throws -> URL { let url = KVdb.construct(URL.self, forKey: key, atBucket: bucketName) - let data = try await session.dataAsync(from: url).0 + let data = try await session.data(from: url).0 let urlString = String(decoding: data, as: UTF8.self) - guard let url = URL(string: urlString) else { - throw KVdbServerError.invalidURL - } + guard let url = URL(string: urlString) else { throw KVdbServerError.invalidURL } return url } @@ -88,15 +81,11 @@ public struct URLSessionClient: TunnelClient { /// - bucketName: The name of the bucket to save the value in. /// /// - Throws: A `NgrokServerError` if the save operation fails. - public func saveValue( - _ value: URL, - withKey key: Key, - inBucket bucketName: String - ) async throws { + public func saveValue(_ value: URL, withKey key: Key, inBucket bucketName: String) async throws { let url = KVdb.construct(URL.self, forKey: key, atBucket: bucketName) var request = URLRequest(url: url) request.httpBody = value.absoluteString.data(using: .utf8) - let (data, response) = try await session.dataAsync(for: request) + let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw KVdbServerError.cantSaveTunnel(nil, nil) } diff --git a/Packages/SublimationNgrok/Sources/SublimationMocks/MockError.swift b/Packages/SublimationNgrok/Sources/SublimationMocks/MockError.swift new file mode 100644 index 0000000..7b0b660 --- /dev/null +++ b/Packages/SublimationNgrok/Sources/SublimationMocks/MockError.swift @@ -0,0 +1,44 @@ +// +// MockError.swift +// SublimationNgrok +// +// Created by Leo Dion. +// Copyright © 2024 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +package enum MockError: Error { case value(T) } + +extension Result { + package var error: (any Error)? { + guard case let .failure(failure) = self else { return nil } + return failure + } + + package func mockErrorValue() -> T? { + guard let mockError = error as? MockError else { return nil } + + switch mockError { case let .value(value): return value + } + } +} diff --git a/Tests/SublimationVaporTests/MockServerApplication.swift b/Packages/SublimationNgrok/Sources/SublimationMocks/MockServerApplication.swift similarity index 69% rename from Tests/SublimationVaporTests/MockServerApplication.swift rename to Packages/SublimationNgrok/Sources/SublimationMocks/MockServerApplication.swift index cc92cdc..3348c2e 100644 --- a/Tests/SublimationVaporTests/MockServerApplication.swift +++ b/Packages/SublimationNgrok/Sources/SublimationMocks/MockServerApplication.swift @@ -1,6 +1,6 @@ // // MockServerApplication.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,31 +27,32 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import Logging +package import Foundation +package import Logging import SublimationCore import XCTest -internal class MockServerApplication: Application { - internal let httpServerConfigurationPort: Int - internal let httpServerTLS: Bool - internal let logger: Logger +package class MockServerApplication: Application { + package let httpServerConfigurationPort: Int + package let httpServerTLS: Bool + package let logger: Logger - internal private(set) var postRequests = [(URL, Data?)]() - internal private(set) var getRequests = [URL]() - internal private(set) var queuedGetResponses = [Result]() - internal private(set) var queuedPostResponses = [Result]() - internal init(httpServerConfigurationPort: Int, httpServerTLS: Bool, logger: Logger) { + package private(set) var postRequests = [(URL, Data?)]() + package private(set) var getRequests = [URL]() + package private(set) var queuedGetResponses = [Result]() + package private(set) var queuedPostResponses = [Result]() + package init(httpServerConfigurationPort: Int, httpServerTLS: Bool, logger: Logger) { self.httpServerConfigurationPort = httpServerConfigurationPort self.httpServerTLS = httpServerTLS self.logger = logger } - internal func post(to url: URL, body: Data?) async throws { + package func post(to url: URL, body: Data?) async throws { postRequests.append((url, body)) try queuedPostResponses.remove(at: 0).get() } - internal func get(from url: URL) async throws -> Data? { + package func get(from url: URL) async throws -> Data? { getRequests.append(url) return try queuedGetResponses.remove(at: 0).get() } diff --git a/Packages/SublimationNgrok/Sources/SublimationMocks/MockURL.swift b/Packages/SublimationNgrok/Sources/SublimationMocks/MockURL.swift new file mode 100644 index 0000000..0495882 --- /dev/null +++ b/Packages/SublimationNgrok/Sources/SublimationMocks/MockURL.swift @@ -0,0 +1,37 @@ +// +// MockURL.swift +// SublimationNgrok +// +// Created by Leo Dion. +// Copyright © 2024 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +package struct MockURL { + package let kvDBBase: String + package let keyBucketPath: String + package init(kvDBBase: String, keyBucketPath: String) { + self.kvDBBase = kvDBBase + self.keyBucketPath = keyBucketPath + } +} diff --git a/Packages/SublimationNgrok/Sources/SublimationMocks/URL.swift b/Packages/SublimationNgrok/Sources/SublimationMocks/URL.swift new file mode 100644 index 0000000..2bacfdf --- /dev/null +++ b/Packages/SublimationNgrok/Sources/SublimationMocks/URL.swift @@ -0,0 +1,38 @@ +// +// URL.swift +// SublimationNgrok +// +// Created by Leo Dion. +// Copyright © 2024 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +package import Foundation + +#if canImport(FoundationNetworking) + package import FoundationNetworking +#endif + +extension URL { + package static func random() -> URL { URL(fileURLWithPath: NSTemporaryDirectory()) } +} diff --git a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIConfiguration.swift b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIConfiguration.swift index 212f0a4..b637449 100644 --- a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIConfiguration.swift +++ b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIConfiguration.swift @@ -1,6 +1,6 @@ // // NgrokCLIAPIConfiguration.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -29,7 +29,7 @@ public import Logging public import SublimationCore -import SublimationTunnel +public import SublimationTunnel /// Configuration for the Ngrok CLI API server. /// @@ -55,10 +55,7 @@ extension NgrokCLIAPIConfiguration { /// /// - Parameter serverApplication: The server application to use for configuration. internal init(serverApplication: any Application) { - self.init( - port: serverApplication.httpServerConfigurationPort, - logger: serverApplication.logger - ) + self.init(port: serverApplication.httpServerConfigurationPort, logger: serverApplication.logger) } /// Initializes a new instance of `NgrokCLIAPIConfiguration` @@ -66,7 +63,5 @@ extension NgrokCLIAPIConfiguration { /// /// - Parameter application: The Vapor application to use for configuration. - public init(application: any Application) { - self.init(serverApplication: application) - } + public init(application: any Application) { self.init(serverApplication: application) } } diff --git a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer+TunnelServer.swift b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer+TunnelServer.swift index cb41af1..d0b3e60 100644 --- a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer+TunnelServer.swift +++ b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer+TunnelServer.swift @@ -1,6 +1,6 @@ // // NgrokCLIAPIServer+TunnelServer.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -28,19 +28,17 @@ // import Foundation +import Ngrokit public import OpenAPIRuntime import SublimationTunnel -import Ngrokit extension NgrokCLIAPIServer { /// Runs the server. - public func begin( - isConnectionRefused: @escaping (ClientError) -> Bool) async { + public func begin(isConnectionRefused: @escaping (ClientError) -> Bool) async { let start = Date() let newTunnel: any Tunnel - do { - newTunnel = try await self.newTunnel(isConnectionRefused: isConnectionRefused) - } catch { + do { newTunnel = try await self.newTunnel(isConnectionRefused: isConnectionRefused) } + catch { delegate.server(self, errorDidOccur: error) return } @@ -51,33 +49,25 @@ extension NgrokCLIAPIServer { } /// Starts the server. - public func start( - isConnectionRefused: @escaping @Sendable (ClientError) -> Bool) { - Task { - await begin(isConnectionRefused: isConnectionRefused) - } + public func start(isConnectionRefused: @escaping @Sendable (ClientError) -> Bool) { + Task { await begin(isConnectionRefused: isConnectionRefused) } } actor HasStarted { internal private(set) var isStarted = false - - func started() { - isStarted = true - } - } - - public func shutdown() { - self.process.terminate() + func started() { isStarted = true } } + public func shutdown() { self.process.terminate() } public func run(isConnectionRefused: @escaping @Sendable (ClientError) -> Bool) async throws { - try await withThrowingTaskGroup(of: Void.self, body: { group in - let started = HasStarted() - group.addTask{ - try await withCheckedThrowingContinuation { continuation in - - let start = Date() - Task { - let newTunnel = try await self.newTunnel( - isConnectionRefused: isConnectionRefused) { error in + try await withThrowingTaskGroup( + of: Void.self, + body: { group in + let started = HasStarted() + group.addTask { + try await withCheckedThrowingContinuation { continuation in + let start = Date() + Task { + let newTunnel = try await self.newTunnel(isConnectionRefused: isConnectionRefused) { + error in if let error = error as? RuntimeError { if case let .unknownEarlyTermination(string) = error { continuation.resume() @@ -86,24 +76,22 @@ extension NgrokCLIAPIServer { } continuation.resume(throwing: error) } - let seconds = Int(-start.timeIntervalSinceNow) - logger.notice("New Tunnel Created in \(seconds) secs: \(newTunnel.publicURL)") - delegate.server(self, updatedTunnel: newTunnel) - await started.started() + let seconds = Int(-start.timeIntervalSinceNow) + logger.notice("New Tunnel Created in \(seconds) secs: \(newTunnel.publicURL)") + delegate.server(self, updatedTunnel: newTunnel) + await started.started() + } } } - } - group.addTask{ - var isActive = true - - while isActive { - try await Task.sleep(for: .seconds(5.0), tolerance: .seconds(2.5)) - if await started.isStarted { - try await self.status() + group.addTask { + var isActive = true + while isActive { + try await Task.sleep(for: .seconds(5.0), tolerance: .seconds(2.5)) + if await started.isStarted { try await self.status() } } } + try await group.waitForAll() } - try await group.waitForAll() - }) + ) } } diff --git a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer.swift b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer.swift index feb61d5..19f9adf 100644 --- a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer.swift +++ b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServer.swift @@ -1,6 +1,6 @@ // // NgrokCLIAPIServer.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -87,14 +87,10 @@ public struct NgrokCLIAPIServer: TunnelServer, Sendable { /// Handles a CLI error. /// /// - Parameter error: The error that occurred. - @Sendable - @available(*, deprecated) - private func cliError(_ error: any Error) { + @Sendable @available(*, deprecated) private func cliError(_ error: any Error) { delegate.server(self, errorDidOccur: error) } - - @available(*, deprecated) - private func searchForExistingTunnel( + @available(*, deprecated) private func searchForExistingTunnel( within timeout: TimeInterval, isConnectionRefused: @escaping (ClientError) -> Bool ) async throws -> TunnelResult? { @@ -109,16 +105,14 @@ public struct NgrokCLIAPIServer: TunnelServer, Sendable { return tunnel } - return try await client.searchForCreatedTunnel( - within: timeout, - logger: logger, - isConnectionRefused: isConnectionRefused - ) - .map { - .init(isOld: false, tunnel: $0) - } + return + try await client.searchForCreatedTunnel( + within: timeout, + logger: logger, + isConnectionRefused: isConnectionRefused + ) + .map { .init(isOld: false, tunnel: $0) } } - private func searchForExistingTunnel( within timeout: TimeInterval, isConnectionRefused: @escaping (ClientError) -> Bool, @@ -135,37 +129,31 @@ public struct NgrokCLIAPIServer: TunnelServer, Sendable { return tunnel } - return try await client.searchForCreatedTunnel( - within: timeout, - logger: logger, - isConnectionRefused: isConnectionRefused - ) - .map { - .init(isOld: false, tunnel: $0) - } + return + try await client.searchForCreatedTunnel( + within: timeout, + logger: logger, + isConnectionRefused: isConnectionRefused + ) + .map { .init(isOld: false, tunnel: $0) } } private func getTunnel( from result: NetworkResult, onTerminationError: @Sendable @escaping (any Error) -> Void ) async throws -> TunnelResult?? { - switch result { - case .connectionRefused: - logger.notice( - "Ngrok not running. Waiting for Process and New Tunnel... (about 30 secs)" - ) + switch result { case .connectionRefused: + logger.notice("Ngrok not running. Waiting for Process and New Tunnel... (about 30 secs)") try await process.run(onError: onTerminationError) - case let .success(tunnel): - logger.debug("Process Already Running.") - return TunnelResult??.some(tunnel.map { .init(isOld: true, tunnel: $0) }) + case let .success(tunnel): + logger.debug("Process Already Running.") + return TunnelResult??.some(tunnel.map { .init(isOld: true, tunnel: $0) }) - case let .failure(error): - throw error + case let .failure(error): throw error } return nil } - internal func newTunnel( isConnectionRefused: @Sendable @escaping (ClientError) -> Bool, @@ -179,26 +167,19 @@ public struct NgrokCLIAPIServer: TunnelServer, Sendable { if tunnel.isOld { try await client.stopTunnel(withName: tunnel.tunnel.name) logger.info("Existing Tunnel Stopped. \(tunnel.tunnel.publicURL)") - } else { + } + else { return tunnel.tunnel } } - return try await client.startTunnel( - .init( - port: port, - name: "vapor-development" - ) - ) - } - - internal func status () async throws { - try await self.client.status() + return try await client.startTunnel(.init(port: port, name: "vapor-development")) } + internal func status() async throws { try await self.client.status() } - internal func newTunnel( - isConnectionRefused: @escaping (ClientError) -> Bool - ) async throws -> any Tunnel { + internal func newTunnel(isConnectionRefused: @escaping (ClientError) -> Bool) async throws + -> any Tunnel + { if let tunnel = try await searchForExistingTunnel( within: 60.0, isConnectionRefused: isConnectionRefused @@ -206,16 +187,12 @@ public struct NgrokCLIAPIServer: TunnelServer, Sendable { if tunnel.isOld { try await client.stopTunnel(withName: tunnel.tunnel.name) logger.info("Existing Tunnel Stopped. \(tunnel.tunnel.publicURL)") - } else { + } + else { return tunnel.tunnel } } - return try await client.startTunnel( - .init( - port: port, - name: "vapor-development" - ) - ) + return try await client.startTunnel(.init(port: port, name: "vapor-development")) } } diff --git a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServerFactory.swift b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServerFactory.swift index 03b1d4a..5d0e350 100644 --- a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServerFactory.swift +++ b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokCLIAPIServerFactory.swift @@ -1,6 +1,6 @@ // // NgrokCLIAPIServerFactory.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -47,18 +47,12 @@ public struct NgrokCLIAPIServerFactory: TunnelServerFa private let ngrokClient: @Sendable () -> NgrokClient - public init( - cliAPI: any NgrokCLIAPI, - ngrokClient: @escaping @Sendable () -> NgrokClient - ) { + public init(cliAPI: any NgrokCLIAPI, ngrokClient: @escaping @Sendable () -> NgrokClient) { self.cliAPI = cliAPI self.ngrokClient = ngrokClient } - public init( - ngrokPath: String, - ngrokClient: @escaping @Sendable () -> NgrokClient - ) { + public init(ngrokPath: String, ngrokClient: @escaping @Sendable () -> NgrokClient) { self.init( cliAPI: NgrokProcessCLIAPI(ngrokPath: ngrokPath), ngrokClient: ngrokClient @@ -73,10 +67,9 @@ public struct NgrokCLIAPIServerFactory: TunnelServerFa /// /// - Returns: A new `NgrokCLIAPIServer` instance. - public func server( - from configuration: Configuration, - handler: any TunnelServerDelegate - ) -> NgrokCLIAPIServer { + public func server(from configuration: Configuration, handler: any TunnelServerDelegate) + -> NgrokCLIAPIServer + { let process = cliAPI.process(forHTTPPort: configuration.port) return .init( delegate: handler, diff --git a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokClient.swift b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokClient.swift index 2949de2..a12a0a5 100644 --- a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokClient.swift +++ b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokClient.swift @@ -1,6 +1,6 @@ // // NgrokClient.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -34,18 +34,16 @@ import OpenAPIRuntime import SublimationTunnel extension NgrokClient { - internal func attemptTunnel( - isConnectionRefused: @escaping (ClientError) -> Bool - ) async -> TunnelAttemptResult { - let networkResult = await AnyTunnelNetworkResult({ - try await self.listTunnels().first - }, isConnectionRefused: isConnectionRefused) - switch networkResult { - case let .connectionRefused(error): - return .error(error) + internal func attemptTunnel(isConnectionRefused: @escaping (ClientError) -> Bool) async + -> TunnelAttemptResult + { + let networkResult = await AnyTunnelNetworkResult( + { try await self.listTunnels().first }, + isConnectionRefused: isConnectionRefused + ) + switch networkResult { case let .connectionRefused(error): return .error(error) - default: - return .network(networkResult) + default: return .network(networkResult) } } @@ -63,12 +61,9 @@ extension NgrokClient { try await Task.sleep(for: .seconds(5), tolerance: .seconds(5)) let result = await self.attemptTunnel(isConnectionRefused: isConnectionRefused) attempts += 1 - switch result { - case let .network(newNetworkResult): - networkResult = newNetworkResult + switch result { case let .network(newNetworkResult): networkResult = newNetworkResult - case let .error(error): - lastError = error + case let .error(error): lastError = error } } diff --git a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokTunnel.swift b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokTunnel.swift index 56718de..3c8dfcc 100644 --- a/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokTunnel.swift +++ b/Packages/SublimationNgrok/Sources/SublimationNgrok/NgrokTunnel.swift @@ -1,6 +1,6 @@ // // NgrokTunnel.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -28,6 +28,6 @@ // import Ngrokit -import SublimationTunnel +public import SublimationTunnel extension NgrokTunnel: Tunnel {} diff --git a/Packages/SublimationNgrok/Sources/SublimationNgrok/TunnelAttemptResult.swift b/Packages/SublimationNgrok/Sources/SublimationNgrok/TunnelAttemptResult.swift index 928b089..3f858fe 100644 --- a/Packages/SublimationNgrok/Sources/SublimationNgrok/TunnelAttemptResult.swift +++ b/Packages/SublimationNgrok/Sources/SublimationNgrok/TunnelAttemptResult.swift @@ -1,6 +1,6 @@ // // TunnelAttemptResult.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Tests/SublimationVaporTests/MockServerDelegate.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/MockServerDelegate.swift similarity index 77% rename from Tests/SublimationVaporTests/MockServerDelegate.swift rename to Packages/SublimationNgrok/Sources/SublimationTunnel/MockServerDelegate.swift index 3e392df..887035a 100644 --- a/Tests/SublimationVaporTests/MockServerDelegate.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/MockServerDelegate.swift @@ -1,6 +1,6 @@ // // MockServerDelegate.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,19 +27,14 @@ // OTHER DEALINGS IN THE SOFTWARE. // -@testable import SublimationTunnel -import XCTest +package import Foundation -internal final class MockServerDelegate: TunnelServerDelegate { - internal let id: UUID +package final class MockServerDelegate: TunnelServerDelegate { + package let id: UUID - internal init(id: UUID) { - self.id = id - } + package init(id: UUID) { self.id = id } - internal func server( - _: any TunnelServer, updatedTunnel _: any Tunnel - ) {} + package func server(_: any TunnelServer, updatedTunnel _: any Tunnel) {} - internal func server(_: any TunnelServer, errorDidOccur _: any Error) {} + package func server(_: any TunnelServer, errorDidOccur _: any Error) {} } diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/MockTunnelClient.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/MockTunnelClient.swift new file mode 100644 index 0000000..fa634b8 --- /dev/null +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/MockTunnelClient.swift @@ -0,0 +1,69 @@ +// +// MockTunnelClient.swift +// SublimationNgrok +// +// Created by Leo Dion. +// Copyright © 2024 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if canImport(FoundationNetworking) + @preconcurrency package import Foundation + extension URL: @unchecked Sendable {} +#else + package import Foundation +#endif + +package actor MockTunnelClient: TunnelClient { + package struct GetParameters { + package let key: Key + package let bucketName: String + } + + package struct SaveParameters { + package let value: URL + package let key: Key + package let bucketName: String + } + + internal let getValueResult: Result? + internal let saveValueError: (any Error)? + + package private(set) var getValuesPassed = [GetParameters]() + package private(set) var saveValuesPassed = [SaveParameters]() + package init(getValueResult: Result? = nil, saveValueError: (any Error)? = nil) { + self.getValueResult = getValueResult + self.saveValueError = saveValueError + } + + package func getValue(ofKey key: Key, fromBucket bucketName: String) async throws -> URL { + getValuesPassed.append(.init(key: key, bucketName: bucketName)) + // swiftlint:disable:next force_unwrapping + return try getValueResult!.get() + } + + package func saveValue(_ value: URL, withKey key: Key, inBucket bucketName: String) async throws { + saveValuesPassed.append(.init(value: value, key: key, bucketName: bucketName)) + if let saveValueError { throw saveValueError } + } +} diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/NetworkResult.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/NetworkResult.swift index 60badb4..e91ecb4 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/NetworkResult.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/NetworkResult.swift @@ -1,6 +1,6 @@ // // NetworkResult.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -29,8 +29,9 @@ import Foundation -package typealias AnyTunnelNetworkResult = - NetworkResult<(any Tunnel)?, ConnectionErrorType> +package typealias AnyTunnelNetworkResult = NetworkResult< + (any Tunnel)?, ConnectionErrorType +> /// Represents the result of a network operation. /// @@ -64,23 +65,16 @@ extension NetworkResult { _ closure: @escaping () async throws -> T, isConnectionRefused: @escaping (ConnectionErrorType) -> Bool ) async { - do { - self = try await .success(closure()) - } catch { - self = .init(error: error, isConnectionRefused: isConnectionRefused) - } + do { self = try await .success(closure()) } + catch { self = .init(error: error, isConnectionRefused: isConnectionRefused) } } public func get() throws -> T? { - switch self { - case .connectionRefused: - return nil + switch self { case .connectionRefused: return nil - case let .failure(error): - throw error + case let .failure(error): throw error - case let .success(item): - return item + case let .success(item): return item } } } diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/Tunnel.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/Tunnel.swift index c0e1613..97ffb87 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/Tunnel.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/Tunnel.swift @@ -1,6 +1,6 @@ // // Tunnel.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelBucketRepositoryFactory.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelBucketRepositoryFactory.swift index 4e03e4e..484b804 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelBucketRepositoryFactory.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelBucketRepositoryFactory.swift @@ -1,6 +1,6 @@ // // TunnelBucketRepositoryFactory.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -35,8 +35,7 @@ import Foundation /// This factory is used to set up and configure a /// `KVdbTunnelRepository` with a specific bucket name. -public struct TunnelBucketRepositoryFactory: - WritableTunnelRepositoryFactory { +public struct TunnelBucketRepositoryFactory: WritableTunnelRepositoryFactory { /// The type of tunnel repository created by this factory. public typealias TunnelRepositoryType = TunnelClientRepository @@ -46,18 +45,15 @@ public struct TunnelBucketRepositoryFactory: /// Initializes a new instance of the factory with the specified bucket name. /// /// - Parameter bucketName: The name of the bucket to use. - public init(bucketName: String) { - self.bucketName = bucketName - } + public init(bucketName: String) { self.bucketName = bucketName } /// Sets up a client and returns a new `KVdbTunnelRepository` instance. /// /// - Parameter client: The tunnel client to use. /// - Returns: A new `KVdbTunnelRepository` instance. - public func setupClient( - _ client: TunnelClientType - ) -> TunnelClientRepository - where TunnelClientType: TunnelClient, TunnelClientType.Key == Key { + public func setupClient(_ client: TunnelClientType) -> TunnelClientRepository< + Key + > where TunnelClientType: TunnelClient, TunnelClientType.Key == Key { .init(client: client, bucketName: bucketName) } } diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClient.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClient.swift index 8d917e0..696f542 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClient.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClient.swift @@ -1,6 +1,6 @@ // // TunnelClient.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClientRepository.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClientRepository.swift index 2a4924f..611c46c 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClientRepository.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelClientRepository.swift @@ -1,6 +1,6 @@ // // TunnelClientRepository.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepository.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepository.swift index 6f57936..8257ae1 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepository.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepository.swift @@ -1,6 +1,6 @@ // // TunnelRepository.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepositoryFactory.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepositoryFactory.swift index 648a580..49b8ba3 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepositoryFactory.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelRepositoryFactory.swift @@ -1,6 +1,6 @@ // // TunnelRepositoryFactory.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -63,7 +63,6 @@ public protocol TunnelRepositoryFactory: Sendable { /// /// - Note: The `TunnelClientType` must have a `Key` type /// that matches the `Key` type of the `TunnelRepositoryType`. - func setupClient( - _ client: TunnelClientType - ) -> TunnelRepositoryType where TunnelClientType.Key == TunnelRepositoryType.Key + func setupClient(_ client: TunnelClientType) + -> TunnelRepositoryType where TunnelClientType.Key == TunnelRepositoryType.Key } diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServer.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServer.swift index c297d58..fd9897e 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServer.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServer.swift @@ -1,6 +1,6 @@ // // TunnelServer.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -34,17 +34,16 @@ /// - Note: The Ngrok server allows you to expose a local server to the internet. /// /// - Important: Make sure to call the `start()` method to start the Ngrok server. -public protocol TunnelServer : Sendable { +public protocol TunnelServer: Sendable { /// Type of connection error which denotes whether the service isn't available. associatedtype ConnectionErrorType: Error /// Starts the Ngrok server. /// /// Call this method to start the Ngrok server and /// expose your local server to the internet. - @available(*, deprecated) - func start(isConnectionRefused: @escaping @Sendable (ConnectionErrorType) -> Bool) - + @available(*, deprecated) func start( + isConnectionRefused: @escaping @Sendable (ConnectionErrorType) -> Bool + ) func run(isConnectionRefused: @escaping @Sendable (ConnectionErrorType) -> Bool) async throws - func shutdown() } diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerConfiguration.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerConfiguration.swift index a3de424..a2be936 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerConfiguration.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerConfiguration.swift @@ -1,6 +1,6 @@ // // TunnelServerConfiguration.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerDelegate.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerDelegate.swift index a1f5d75..74dbcba 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerDelegate.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerDelegate.swift @@ -1,6 +1,6 @@ // // TunnelServerDelegate.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -30,8 +30,7 @@ import Foundation /// A delegate protocol for `NgrokServer` that handles server events and errors. -@available(*, deprecated) -public protocol TunnelServerDelegate: AnyObject, Sendable { +@available(*, deprecated) public protocol TunnelServerDelegate: AnyObject, Sendable { /// Notifies the delegate that a tunnel has been updated. /// /// - Parameters: diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerFactory.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerFactory.swift index 84ff8f4..4d110f6 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerFactory.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelServerFactory.swift @@ -1,6 +1,6 @@ // // TunnelServerFactory.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -39,8 +39,6 @@ public protocol TunnelServerFactory: Sendable { /// - handler: The delegate object that handles server events. /// /// - Returns: A server instance based on the provided configuration. - func server( - from configuration: Configuration, - handler: any TunnelServerDelegate - ) -> Configuration.Server + func server(from configuration: Configuration, handler: any TunnelServerDelegate) + -> Configuration.Server } diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelSublimatory.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelSublimatory.swift index 4714441..8064536 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelSublimatory.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/TunnelSublimatory.swift @@ -1,6 +1,6 @@ // // TunnelSublimatory.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -32,17 +32,17 @@ import Logging public import SublimationCore /// Closure which returns a ``TunnelClient`` from the ``Application``. -public typealias RepositoryClientFactory = - (@Sendable @escaping () -> any Application) -> any TunnelClient +public typealias RepositoryClientFactory = (@Sendable @escaping () -> any Application) -> + any TunnelClient public actor TunnelSublimatory< WritableTunnelRepositoryFactoryType: WritableTunnelRepositoryFactory, TunnelServerFactoryType: TunnelServerFactory >: Sublimatory, TunnelServerDelegate { - public typealias Key = WritableTunnelRepositoryFactoryType.TunnelRepositoryType.Key - public typealias ConnectionErrorType = TunnelServerFactoryType.Configuration.Server.ConnectionErrorType + public typealias ConnectionErrorType = TunnelServerFactoryType.Configuration.Server + .ConnectionErrorType private let factory: TunnelServerFactoryType private let repoFactory: WritableTunnelRepositoryFactoryType private let key: Key @@ -68,9 +68,7 @@ public actor TunnelSublimatory< isConnectionRefused: @escaping @Sendable (ConnectionErrorType) -> Bool ) async { let logger = application().logger - let tunnelRepo = repoFactory.setupClient( - repoClientFactory(application) - ) + let tunnelRepo = repoFactory.setupClient(repoClientFactory(application)) await self.init( factory: factory, repoFactory: repoFactory, @@ -95,7 +93,9 @@ public actor TunnelSublimatory< logger: Logger, repoClientFactory: @escaping RepositoryClientFactory, isConnectionRefused: @escaping @Sendable (ConnectionErrorType) -> Bool, - server: @escaping @Sendable (TunnelSublimatory) -> TunnelServerFactoryType.Configuration.Server + server: @escaping @Sendable ( + TunnelSublimatory + ) -> TunnelServerFactoryType.Configuration.Server ) async { self.factory = factory self.repoFactory = repoFactory @@ -116,17 +116,14 @@ public actor TunnelSublimatory< /// /// - SeeAlso: `Tunnel` private func saveTunnel(_ tunnel: any Tunnel) async { - do { - try await tunnelRepo.saveURL(tunnel.publicURL, withKey: key) - } catch { - logger.error( - "Unable to save url to repository: \(error.localizedDescription)" - ) + do { try await tunnelRepo.saveURL(tunnel.publicURL, withKey: key) } + catch { + logger.error("Unable to save url to repository: \(error.localizedDescription)") return } -// logger?.notice( -// "Saved url \(tunnel.publicURL) to repository with key \(key)" -// ) + // logger?.notice( + // "Saved url \(tunnel.publicURL) to repository with key \(key)" + // ) } /// Handles an error that occurred during tunnel operation. @@ -150,9 +147,7 @@ public actor TunnelSublimatory< /// - SeeAlso: `NgrokServer` /// - SeeAlso: `Tunnel` public nonisolated func server(_: any TunnelServer, updatedTunnel tunnel: any Tunnel) { - Task { - await self.saveTunnel(tunnel) - } + Task { await self.saveTunnel(tunnel) } } /// Called when an error occurs in the Ngrok server. @@ -165,77 +160,13 @@ public actor TunnelSublimatory< /// /// - SeeAlso: `NgrokServer` public nonisolated func server(_: any TunnelServer, errorDidOccur error: any Error) { - Task { - await self.onError(error) - } - } - - /// Begins the Sublimation application from the given application. - /// - /// - Parameters: - /// - application: The Vapor application. - /// - /// - Note: This method is private and asynchronous. - /// - /// - SeeAlso: `Application` -// private func beginFromApplication(_ application: @Sendable @escaping () -> any Application) async { -// let server = factory.server( -// from: TunnelServerFactoryType.Configuration(application: application()), -// handler: self -// ) -// logger = application().logger -// tunnelRepo = repoFactory.setupClient( -// repoClientFactory(application) -// ) -// self.server = server -// server.start(isConnectionRefused: isConnectionRefused) -// } - - /// Called when the application is about to boot. - /// - /// - Parameters: - /// - application: The Vapor application. - /// - /// - Throws: An error if the application fails to begin. - /// - /// - Note: This method is nonisolated. - /// - /// - SeeAlso: `Application` -// public func willBoot(from application: @escaping @Sendable () -> any Application) async { -// await self.beginFromApplication(application) -// } -// -// func setupForApplication(_ application: @escaping @Sendable () -> any Application) { -// let server = factory.server( -// from: TunnelServerFactoryType.Configuration(application: application()), -// handler: self -// ) -// logger = application().logger -// tunnelRepo = repoFactory.setupClient( -// repoClientFactory(application) -// ) -// self.server = server -// } -// -// public nonisolated func initialize(for application: @escaping @Sendable () -> any Application) { -// Task { -// await self.setupForApplication(application) -// } -// } -// - func shutdownServer () { - server.shutdown() - } - public nonisolated func shutdown() { - Task { - await self.shutdownServer() - } + Task { await self.onError(error) } } + func shutdownServer() { server.shutdown() } + public nonisolated func shutdown() { Task { await self.shutdownServer() } } public func run() async throws { - let isConnectionRefused = self.isConnectionRefused - try await server.run(isConnectionRefused: isConnectionRefused) } } diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepository.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepository.swift index 2f4d6e5..f908703 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepository.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepository.swift @@ -1,6 +1,6 @@ // // WritableTunnelRepository.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepositoryFactory.swift b/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepositoryFactory.swift index 5dd1ab6..10f20cf 100644 --- a/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepositoryFactory.swift +++ b/Packages/SublimationNgrok/Sources/SublimationTunnel/WritableTunnelRepositoryFactory.swift @@ -1,6 +1,6 @@ // // WritableTunnelRepositoryFactory.swift -// Sublimation +// SublimationNgrok // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,11 +27,6 @@ // OTHER DEALINGS IN THE SOFTWARE. // -// #if os(macOS) -// @_exported import class Ngrokit.ProcessableProcess -// #endif -// @_exported import struct Ngrokit.NgrokClient - import Foundation #if canImport(FoundationNetworking) @@ -49,4 +44,4 @@ import Foundation /// - SeeAlso: `TunnelRepositoryFactory` /// - SeeAlso: `WritableTunnelRepository` public protocol WritableTunnelRepositoryFactory: TunnelRepositoryFactory - where TunnelRepositoryType: WritableTunnelRepository {} +where TunnelRepositoryType: WritableTunnelRepository {} diff --git a/Tests/SublimationKVdbTests/KVdbTests.swift b/Packages/SublimationNgrok/Tests/SublimationKVdbTests/KVdbTests.swift similarity index 100% rename from Tests/SublimationKVdbTests/KVdbTests.swift rename to Packages/SublimationNgrok/Tests/SublimationKVdbTests/KVdbTests.swift diff --git a/Tests/SublimationKVdbTests/OptionalTests.swift b/Packages/SublimationNgrok/Tests/SublimationKVdbTests/OptionalTests.swift similarity index 100% rename from Tests/SublimationKVdbTests/OptionalTests.swift rename to Packages/SublimationNgrok/Tests/SublimationKVdbTests/OptionalTests.swift diff --git a/Tests/SublimationKVdbTests/ResultTests.swift b/Packages/SublimationNgrok/Tests/SublimationKVdbTests/ResultTests.swift similarity index 100% rename from Tests/SublimationKVdbTests/ResultTests.swift rename to Packages/SublimationNgrok/Tests/SublimationKVdbTests/ResultTests.swift diff --git a/Tests/SublimationKVdbTests/URLTests.swift b/Packages/SublimationNgrok/Tests/SublimationKVdbTests/URLTests.swift similarity index 100% rename from Tests/SublimationKVdbTests/URLTests.swift rename to Packages/SublimationNgrok/Tests/SublimationKVdbTests/URLTests.swift diff --git a/Tests/SublimationVaporTests/NgrokCLIAPIConfigurationTests.swift b/Packages/SublimationNgrok/Tests/SublimationNgrokTests/NgrokCLIAPIConfigurationTests.swift similarity index 98% rename from Tests/SublimationVaporTests/NgrokCLIAPIConfigurationTests.swift rename to Packages/SublimationNgrok/Tests/SublimationNgrokTests/NgrokCLIAPIConfigurationTests.swift index 93fce11..769cf7a 100644 --- a/Tests/SublimationVaporTests/NgrokCLIAPIConfigurationTests.swift +++ b/Packages/SublimationNgrok/Tests/SublimationNgrokTests/NgrokCLIAPIConfigurationTests.swift @@ -27,6 +27,7 @@ // OTHER DEALINGS IN THE SOFTWARE. // +import SublimationMocks @testable import SublimationNgrok import XCTest diff --git a/Tests/SublimationVaporTests/NgrokCLIAPIServerFactoryTests.swift b/Packages/SublimationNgrok/Tests/SublimationNgrokTests/NgrokCLIAPIServerFactoryTests.swift similarity index 95% rename from Tests/SublimationVaporTests/NgrokCLIAPIServerFactoryTests.swift rename to Packages/SublimationNgrok/Tests/SublimationNgrokTests/NgrokCLIAPIServerFactoryTests.swift index 3407c5c..526fa67 100644 --- a/Tests/SublimationVaporTests/NgrokCLIAPIServerFactoryTests.swift +++ b/Packages/SublimationNgrok/Tests/SublimationNgrokTests/NgrokCLIAPIServerFactoryTests.swift @@ -29,10 +29,12 @@ import HTTPTypes import Ngrokit -import NgrokitMocks import OpenAPIRuntime -@testable import SublimationNgrok +import SublimationMocks +import SublimationTunnel import XCTest +@testable import SublimationNgrok +import NgrokitMocks internal final class MockTransport: ClientTransport { // swiftlint:disable:next unavailable_function @@ -57,7 +59,7 @@ internal class NgrokCLIAPIServerFactoryTests: XCTestCase { ) let delegateID = UUID() let processID = UUID() - let configuration = NgrokCLIAPIConfiguration(serverApplication: application) + let configuration = NgrokCLIAPIConfiguration(application: application) let factory = NgrokCLIAPIServerFactory( cliAPI: MockNgrokCLIAPI(id: processID), ngrokClient: { NgrokClient(transport: MockTransport()) diff --git a/Tests/SublimationTunnelTests/KVdbTunnelRepositoryFactoryTests.swift b/Packages/SublimationNgrok/Tests/SublimationTunnelTests/KVdbTunnelRepositoryFactoryTests.swift similarity index 100% rename from Tests/SublimationTunnelTests/KVdbTunnelRepositoryFactoryTests.swift rename to Packages/SublimationNgrok/Tests/SublimationTunnelTests/KVdbTunnelRepositoryFactoryTests.swift diff --git a/Packages/SublimationService/Package.swift b/Packages/SublimationService/Package.swift index 50a47b3..7351f22 100644 --- a/Packages/SublimationService/Package.swift +++ b/Packages/SublimationService/Package.swift @@ -41,7 +41,7 @@ let package = Package( .library(name: "SublimationService", targets: ["SublimationService"]) ], dependencies: [ - .package(path: "../.."), + .package(name: "Sublimation", path: "../.."), .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.6.0") ], targets: [ @@ -52,6 +52,10 @@ let package = Package( .product(name: "ServiceLifecycle", package: "swift-service-lifecycle") ], swiftSettings: swiftSettings + ), + .testTarget( + name: "SublimationServiceTests", + dependencies: ["SublimationService"] ) ] ) diff --git a/Packages/SublimationService/Scripts/lint.sh b/Packages/SublimationService/Scripts/lint.sh index 87f21fa..4653602 100755 --- a/Packages/SublimationService/Scripts/lint.sh +++ b/Packages/SublimationService/Scripts/lint.sh @@ -13,10 +13,10 @@ MINT_RUN="/opt/homebrew/bin/mint run $MINT_ARGS" if [ -z "$SRCROOT" ]; then SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PACKAGE_DIR="${SCRIPT_DIR}/.." - PERIPHERY_OPTIONS="--skip-build" -else - PACKAGE_DIR="${SRCROOT}" PERIPHERY_OPTIONS="" +else + PACKAGE_DIR="${SRCROOT}" + PERIPHERY_OPTIONS="--skip-build" fi @@ -32,6 +32,10 @@ fi echo "LINT Mode is $LINT_MODE" +if [ "$LINT_MODE" == "INSTALL" ]; then + exit +fi + if [ -z "$CI" ]; then $MINT_RUN swift-format format --recursive --parallel --in-place $PACKAGE_DIR/Sources else diff --git a/Packages/SublimationService/Sources/SublimationService/Service.swift b/Packages/SublimationService/Sources/SublimationService/Service.swift index b4c15ac..b0acec9 100644 --- a/Packages/SublimationService/Sources/SublimationService/Service.swift +++ b/Packages/SublimationService/Sources/SublimationService/Service.swift @@ -1,6 +1,6 @@ // // Service.swift -// Sublimation +// SublimationVapor // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,14 +27,8 @@ // OTHER DEALINGS IN THE SOFTWARE. // -// -// Service.swift -// SublimationService -// -// Created by Leo Dion on 7/22/24. -// - public import ServiceLifecycle + @_exported public import class Sublimation.Sublimation extension Sublimation: @retroactive Service { diff --git a/Packages/SublimationService/Tests/SublimationServiceTests/ServiceTests.swift b/Packages/SublimationService/Tests/SublimationServiceTests/ServiceTests.swift new file mode 100644 index 0000000..fbe88b1 --- /dev/null +++ b/Packages/SublimationService/Tests/SublimationServiceTests/ServiceTests.swift @@ -0,0 +1,35 @@ +// +// ServiceTests.swift +// SublimationService +// +// Created by Leo Dion on 7/30/24. +// + +import XCTest + +final class ServiceTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Packages/SublimationVapor/Package.swift b/Packages/SublimationVapor/Package.swift index a2f2aaf..e03e338 100644 --- a/Packages/SublimationVapor/Package.swift +++ b/Packages/SublimationVapor/Package.swift @@ -41,7 +41,7 @@ let package = Package( .library(name: "SublimationVapor", targets: ["SublimationVapor"]) ], dependencies: [ - .package(path: "../.."), + .package(name: "Sublimation", path: "../.."), .package( url: "https://github.com/vapor/vapor.git", from: "4.92.0" @@ -77,6 +77,10 @@ let package = Package( ) ], swiftSettings: swiftSettings + ), + .testTarget( + name: "SublimationVaporTests", + dependencies: ["SublimationVapor"] ) ] ) diff --git a/Packages/SublimationVapor/Scripts/lint.sh b/Packages/SublimationVapor/Scripts/lint.sh index 87f21fa..4653602 100755 --- a/Packages/SublimationVapor/Scripts/lint.sh +++ b/Packages/SublimationVapor/Scripts/lint.sh @@ -13,10 +13,10 @@ MINT_RUN="/opt/homebrew/bin/mint run $MINT_ARGS" if [ -z "$SRCROOT" ]; then SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PACKAGE_DIR="${SCRIPT_DIR}/.." - PERIPHERY_OPTIONS="--skip-build" -else - PACKAGE_DIR="${SRCROOT}" PERIPHERY_OPTIONS="" +else + PACKAGE_DIR="${SRCROOT}" + PERIPHERY_OPTIONS="--skip-build" fi @@ -32,6 +32,10 @@ fi echo "LINT Mode is $LINT_MODE" +if [ "$LINT_MODE" == "INSTALL" ]; then + exit +fi + if [ -z "$CI" ]; then $MINT_RUN swift-format format --recursive --parallel --in-place $PACKAGE_DIR/Sources else diff --git a/Packages/SublimationVapor/Sources/SublimationVapor/ClientError.swift b/Packages/SublimationVapor/Sources/SublimationVapor/ClientError.swift index 54f0db6..dfb6b04 100644 --- a/Packages/SublimationVapor/Sources/SublimationVapor/ClientError.swift +++ b/Packages/SublimationVapor/Sources/SublimationVapor/ClientError.swift @@ -1,6 +1,6 @@ // // ClientError.swift -// Sublimation +// SublimationVapor // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationVapor/Sources/SublimationVapor/KVdbTunnelClient.swift b/Packages/SublimationVapor/Sources/SublimationVapor/KVdbTunnelClient.swift index 1f9fc89..6188fb6 100644 --- a/Packages/SublimationVapor/Sources/SublimationVapor/KVdbTunnelClient.swift +++ b/Packages/SublimationVapor/Sources/SublimationVapor/KVdbTunnelClient.swift @@ -1,6 +1,6 @@ // // KVdbTunnelClient.swift -// Sublimation +// SublimationVapor // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation+Ngrok.swift b/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation+Ngrok.swift index ccc0349..92bcf6e 100644 --- a/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation+Ngrok.swift +++ b/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation+Ngrok.swift @@ -1,6 +1,6 @@ // // Sublimation+Ngrok.swift -// Sublimation +// SublimationVapor // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation.swift b/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation.swift index 272300c..97f6074 100644 --- a/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation.swift +++ b/Packages/SublimationVapor/Sources/SublimationVapor/Sublimation.swift @@ -1,6 +1,6 @@ // // Sublimation.swift -// Sublimation +// SublimationVapor // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -33,17 +33,10 @@ import OpenAPIRuntime public import Sublimation public import Vapor -extension Sublimation: LifecycleHandler { +extension Sublimation: @retroactive LifecycleHandler { public func willBoot(_ application: Vapor.Application) throws { - Task { - try await self.sublimatory.run() - } + Task { try await self.sublimatory.run() } } - - public func shutdown(_ application: Vapor.Application) { - Task { - self.sublimatory.shutdown() - } - } + public func shutdown(_ application: Vapor.Application) { Task { self.sublimatory.shutdown() } } } diff --git a/Packages/SublimationVapor/Sources/SublimationVapor/TunnelSublimatory.swift b/Packages/SublimationVapor/Sources/SublimationVapor/TunnelSublimatory.swift index f9afc78..16a3c91 100644 --- a/Packages/SublimationVapor/Sources/SublimationVapor/TunnelSublimatory.swift +++ b/Packages/SublimationVapor/Sources/SublimationVapor/TunnelSublimatory.swift @@ -1,6 +1,6 @@ // // TunnelSublimatory.swift -// Sublimation +// SublimationVapor // // Created by Leo Dion. // Copyright © 2024 BrightDigit. diff --git a/Packages/SublimationVapor/Sources/SublimationVapor/Vapor.Application.swift b/Packages/SublimationVapor/Sources/SublimationVapor/Vapor.Application.swift index 43c6604..eb3bb0b 100644 --- a/Packages/SublimationVapor/Sources/SublimationVapor/Vapor.Application.swift +++ b/Packages/SublimationVapor/Sources/SublimationVapor/Vapor.Application.swift @@ -1,6 +1,6 @@ // // Vapor.Application.swift -// Sublimation +// SublimationVapor // // Created by Leo Dion. // Copyright © 2024 BrightDigit. @@ -27,17 +27,14 @@ // OTHER DEALINGS IN THE SOFTWARE. // -import protocol SublimationCore.Application public import Vapor -extension Vapor.Application: Application { - public var httpServerConfigurationPort: Int { - self.http.server.configuration.port - } +public import protocol SublimationCore.Application - public var httpServerTLS: Bool { - self.http.server.configuration.tlsConfiguration != nil - } +extension Vapor.Application: @retroactive Application { + public var httpServerConfigurationPort: Int { self.http.server.configuration.port } + + public var httpServerTLS: Bool { self.http.server.configuration.tlsConfiguration != nil } public func post(to url: URL, body: Data?) async throws { _ = try await client.post(.init(string: url.absoluteString)) { request in diff --git a/Packages/SublimationVapor/Tests/SublimationVaporTests/SublimationVaporTests.swift b/Packages/SublimationVapor/Tests/SublimationVaporTests/SublimationVaporTests.swift new file mode 100644 index 0000000..02bd873 --- /dev/null +++ b/Packages/SublimationVapor/Tests/SublimationVaporTests/SublimationVaporTests.swift @@ -0,0 +1,35 @@ +// +// SublimationVaporTests.swift +// SublimationVapor +// +// Created by Leo Dion on 8/9/24. +// + +import XCTest + +final class SublimationVaporTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Scripts/lint.sh b/Scripts/lint.sh index b6d5ce7..887aea6 100755 --- a/Scripts/lint.sh +++ b/Scripts/lint.sh @@ -13,10 +13,10 @@ MINT_RUN="/opt/homebrew/bin/mint run $MINT_ARGS" if [ -z "$SRCROOT" ]; then SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PACKAGE_DIR="${SCRIPT_DIR}/.." - PERIPHERY_OPTIONS="--skip-build" -else - PACKAGE_DIR="${SRCROOT}" PERIPHERY_OPTIONS="" +else + PACKAGE_DIR="${SRCROOT}" + PERIPHERY_OPTIONS="--skip-build" fi @@ -32,6 +32,10 @@ fi echo "LINT Mode is $LINT_MODE" +if [ "$LINT_MODE" == "INSTALL" ]; then + exit +fi + if [ -z "$CI" ]; then $MINT_RUN swift-format format --recursive --parallel --in-place $PACKAGE_DIR/Sources else diff --git a/Scripts/lints.sh b/Scripts/lints.sh new file mode 100755 index 0000000..b1fba26 --- /dev/null +++ b/Scripts/lints.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +if [ -z "$SRCROOT" ]; then + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +else + script_dir="${SRCROOT}/Scripts" +fi + +directories=( + "Packages/Ngrokit" + "Packages/SublimationBonjour" + "Packages/SublimationNgrok" + "Packages/SublimationService" + "Packages/SublimationVapor" + "." +) + +cd "$script_dir/.." || exit 1 + +for i in "${!directories[@]}"; do + dir="${directories[$i]}" + if [ -f "$dir/Scripts/lint.sh" ]; then + echo "Running lint.sh in $dir" + (cd "$dir" && LINT_MODE="$LINT_MODE" ./Scripts/lint.sh) + + # Check if the script failed + if [ $? -ne 0 ]; then + echo "Lint script failed in $dir" + exit 1 + fi + + # Copy .mint folder to the next directory if it's not the last one + if [ $i -lt $((${#directories[@]} - 1)) ]; then + next_dir="${directories[$i+1]}" + if [ -d "$dir/.mint" ]; then + echo "Copying .mint folder from $dir to $next_dir" + cp -R "$dir/.mint" "$next_dir/" + else + echo "No .mint folder found in $dir" + fi + fi + else + echo "lint.sh not found in $dir" + fi +done + +echo "All lint processes completed successfully." diff --git a/Sources/Sublimation/Sublimation.swift b/Sources/Sublimation/Sublimation.swift index f4bdc26..7fbdf94 100644 --- a/Sources/Sublimation/Sublimation.swift +++ b/Sources/Sublimation/Sublimation.swift @@ -31,6 +31,7 @@ import Foundation import Logging public import SublimationCore +/// Adds the ability to auto-discover development urls to your full stack applicaiton. public final class Sublimation: Sendable { public let sublimatory: any Sublimatory diff --git a/Sources/SublimationCore/Application.swift b/Sources/SublimationCore/Application.swift index 47238eb..4e20a65 100644 --- a/Sources/SublimationCore/Application.swift +++ b/Sources/SublimationCore/Application.swift @@ -45,10 +45,12 @@ public protocol Application { /// - Parameters: /// - url: The url to post to. /// - body: The optional data. + /// - Throws: If there's an issue with the request. func post(to url: URL, body: Data?) async throws /// Makes a client call to a url. /// - Parameter url: The url to call. - /// - Returns: <#description#> + /// - Returns: The data returned from that request. + /// - Throws: If there's an issue with the request. func get(from url: URL) async throws -> Data? } diff --git a/Sources/SublimationCore/Sublimatory.swift b/Sources/SublimationCore/Sublimatory.swift index 81c9ded..65b1f4b 100644 --- a/Sources/SublimationCore/Sublimatory.swift +++ b/Sources/SublimationCore/Sublimatory.swift @@ -34,5 +34,6 @@ public protocol Sublimatory: Sendable { } extension Sublimatory { + /// Shutdown any active services. public func shutdown() {} } diff --git a/Sources/SublimationMocks/MockError.swift b/Sources/SublimationMocks/MockError.swift index e13b594..84478bc 100644 --- a/Sources/SublimationMocks/MockError.swift +++ b/Sources/SublimationMocks/MockError.swift @@ -31,14 +31,14 @@ package enum MockError: Error { case value(T) } extension Result { package var error: (any Error)? { - guard case let .failure(failure) = self else { return nil } + guard case .failure(let failure) = self else { return nil } return failure } package func mockErrorValue() -> T? { guard let mockError = error as? MockError else { return nil } - switch mockError { case let .value(value): return value + switch mockError { case .value(let value): return value } } } diff --git a/Sources/SublimationMocks/MockTunnelClient.swift b/Sources/SublimationMocks/MockTunnelClient.swift index d8933fc..d4f1fa8 100644 --- a/Sources/SublimationMocks/MockTunnelClient.swift +++ b/Sources/SublimationMocks/MockTunnelClient.swift @@ -54,7 +54,7 @@ package actor MockTunnelClient: TunnelClient { package func getValue(ofKey key: Key, fromBucket bucketName: String) async throws -> URL { getValuesPassed.append(.init(key: key, bucketName: bucketName)) - // swiftlint:disable:next force_unwrapping + // swift-format-ignore: NeverForceUnwrap return try getValueResult!.get() } diff --git a/Tests/SublimationBonjourTests/MockTXTRecord.swift b/Tests/SublimationBonjourTests/MockTXTRecord.swift deleted file mode 100644 index d13a284..0000000 --- a/Tests/SublimationBonjourTests/MockTXTRecord.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// MockTXTRecord.swift -// Sublimation -// -// Created by Leo Dion. -// Copyright © 2024 BrightDigit. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -import Foundation -@testable import SublimationBonjour -import XCTest - -internal struct MockTXTRecord: TXTRecord { - func getKeys() -> any Sequence { - dictionary.keys - } - - internal let dictionary: [String: String] - - internal var count: Int { - dictionary.count - } - - internal init(_ dictionary: [String: String]) { - self.dictionary = dictionary - } - - internal func getStringEntry(for key: String) -> String? { - dictionary[key] - } -} - -private let ipAddresses: [String] = [ - "192.168.0.1", "10.0.0.1", "172.16.254.1", "8.8.8.8", - "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "fe80::1ff:fe23:4567:890a", - "2001:0db8:0000:0042:0000:8a2e:0370:7334", "::ffff:192.168.1.1", - "192.168.0.2", "10.0.0.2", "172.16.254.2", "8.8.4.4", - "2001:0db8:85a3:0000:0000:8a2e:0370:7335", "fe80::1ff:fe23:4567:890b", - "2001:0db8:0000:0042:0000:8a2e:0370:7335", "::ffff:192.168.1.2", - "192.168.0.3", "10.0.0.3", "172.16.254.3", "1.1.1.1", - "2001:0db8:85a3:0000:0000:8a2e:0370:7336", "fe80::1ff:fe23:4567:890c", - "2001:0db8:0000:0042:0000:8a2e:0370:7336", "::ffff:192.168.1.3", - "192.168.0.4", "10.0.0.4", "172.16.254.4", "9.9.9.9", - "2001:0db8:85a3:0000:0000:8a2e:0370:7337", "fe80::1ff:fe23:4567:890d", - "2001:0db8:0000:0042:0000:8a2e:0370:7337", "::ffff:192.168.1.4", - "192.168.0.5", "10.0.0.5", "172.16.254.5", "1.0.0.1", - "2001:0db8:85a3:0000:0000:8a2e:0370:7338", "fe80::1ff:fe23:4567:890e", - "2001:0db8:0000:0042:0000:8a2e:0370:7338", "::ffff:192.168.1.5", - "192.168.0.6", "10.0.0.6", "172.16.254.6", "4.2.2.2", - "2001:0db8:85a3:0000:0000:8a2e:0370:7339", "fe80::1ff:fe23:4567:890f", - "2001:0db8:0000:0042:0000:8a2e:0370:7339", "::ffff:192.168.1.6", - "192.168.0.7", "10.0.0.7", "172.16.254.7", "4.2.2.1", - "2001:0db8:85a3:0000:0000:8a2e:0370:7340", "fe80::1ff:fe23:4567:8910", - "2001:0db8:0000:0042:0000:8a2e:0370:7340", "::ffff:192.168.1.7", - "192.168.0.8", "10.0.0.8", "172.16.254.8", "208.67.222.222", - "2001:0db8:85a3:0000:0000:8a2e:0370:7341", "fe80::1ff:fe23:4567:8911", - "2001:0db8:0000:0042:0000:8a2e:0370:7341", "::ffff:192.168.1.8", - "192.168.0.9", "10.0.0.9", "172.16.254.9", "208.67.220.220", - "2001:0db8:85a3:0000:0000:8a2e:0370:7342", "fe80::1ff:fe23:4567:8912", - "2001:0db8:0000:0042:0000:8a2e:0370:7342", "::ffff:192.168.1.9", - "192.168.0.10", "10.0.0.10", "172.16.254.10", "198.51.100.1", - "2001:0db8:85a3:0000:0000:8a2e:0370:7343", "fe80::1ff:fe23:4567:8913", - "2001:0db8:0000:0042:0000:8a2e:0370:7343", "::ffff:192.168.1.10", - "192.168.0.11", "10.0.0.11", "172.16.254.11", "203.0.113.1", - "2001:0db8:85a3:0000:0000:8a2e:0370:7344", "fe80::1ff:fe23:4567:8914", - "2001:0db8:0000:0042:0000:8a2e:0370:7344", "::ffff:192.168.1.11", - "192.168.0.12", "10.0.0.12", "172.16.254.12", "192.0.2.1", - "2001:0db8:85a3:0000:0000:8a2e:0370:7345", "fe80::1ff:fe23:4567:8915", - "2001:0db8:0000:0042:0000:8a2e:0370:7345", "::ffff:192.168.1.12", - "192.168.0.13", "10.0.0.13", "172.16.254.13", "198.51.100.2", - "2001:0db8:85a3:0000:0000:8a2e:0370:7346", "fe80::1ff:fe23:4567:8916", - "2001:0db8:0000:0042:0000:8a2e:0370:7346", "::ffff:192.168.1.13", - "192.168.0.14", "10.0.0.14", "172.16.254.14", "203.0.113.2", - "2001:0db8:85a3:0000:0000:8a2e:0370:7347", "fe80::1ff:fe23:4567:8917", - "2001:0db8:0000:0042:0000:8a2e:0370:7347", "::ffff:192.168.1.14", - "192.168.0.15", "10.0.0.15", "172.16.254.15", "192.0.2.2", - "2001:0db8:85a3:0000:0000:8a2e:0370:7348", "fe80::1ff:fe23:4567:8918", - "2001:0db8:0000:0042:0000:8a2e:0370:7348", "::ffff:192.168.1.15" -] - -extension Array where Element == String { - internal static func randomIpAddresses(maxLength: Int) -> Self { - .init( - ipAddresses.shuffled().prefix(maxLength) - ) - } -} diff --git a/Tests/SublimationBonjourTests/TXTRecordTests.swift b/Tests/SublimationBonjourTests/TXTRecordTests.swift deleted file mode 100644 index 1f94432..0000000 --- a/Tests/SublimationBonjourTests/TXTRecordTests.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// TXTRecordTests.swift -// Sublimation -// -// Created by Leo Dion. -// Copyright © 2024 BrightDigit. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -import Foundation -@testable import SublimationBonjour -import XCTest - -internal class TXTRecordTests: XCTestCase { - internal func testBadRecord() { - let record = MockTXTRecord(["Serial Number": UUID().uuidString]) - let urls = record.urls(defaultPort: 0, defaultTLS: false, logger: nil) - - XCTAssert(urls.isEmpty) - } - - internal func testInit() { - let expectation = expectation(description: "Filter") - - let expectedIsTLS: Bool = .random() - let expectedPort: Int = .random(in: 1_000 ... 9_000) - let expectedAddresses: [String] = .randomIpAddresses(maxLength: 5) - - expectation.expectedFulfillmentCount = expectedAddresses.count - - let record = MockTXTRecord( - isTLS: expectedIsTLS, - port: expectedPort, - maximumCount: nil, - addresses: expectedAddresses, - filter: { _ in - expectation.fulfill() - return true - } - ) - - XCTAssertEqual(record.dictionary[SublimationKey.port.stringValue], expectedPort.description) - XCTAssertEqual(record.dictionary[SublimationKey.tls.stringValue], expectedIsTLS.description) - - for (index, expectedAddress) in expectedAddresses.enumerated() { - XCTAssertEqual(record.dictionary[SublimationKey.address(index).stringValue], expectedAddress) - } - - XCTAssertEqual(record.count, expectedAddresses.count + 2) - - wait(for: [expectation], timeout: 1.0) - } - - internal func testGetEntry() { - let expectedIsTLS: Bool = .random() - let expectedPort: Int = .random(in: 1_000 ... 9_000) - let expectedAddresses: [String] = .randomIpAddresses(maxLength: 5) - - let record = MockTXTRecord( - isTLS: expectedIsTLS, - port: expectedPort, - maximumCount: nil, - addresses: expectedAddresses, - filter: { _ in - true - } - ) - - XCTAssertEqual(record.getEntry(for: .port).value, expectedPort) - XCTAssertEqual(record.getEntry(for: .tls).value, expectedIsTLS) - - for (index, expectedAddress) in expectedAddresses.enumerated() { - XCTAssertEqual(record.getEntry(for: .address(index)).value, expectedAddress) - } - } - - internal func testURLs() { - let expectedIsTLS: Bool = .random() - let expectedPort: Int = .random(in: 1_000 ... 9_000) - let expectedAddresses: [String] = .randomIpAddresses(maxLength: 5) - - let record = MockTXTRecord( - isTLS: expectedIsTLS, - port: expectedPort, - maximumCount: nil, - addresses: expectedAddresses, - filter: String.isIPv4NotLocalhost(_:) - ) - - let urls = record.urls(defaultPort: 0, defaultTLS: !expectedIsTLS, logger: nil) - let expectedURLs = expectedAddresses.compactMap { host -> URL? in - guard String.isIPv4NotLocalhost(host) else { - return nil - } - return URL(scheme: expectedIsTLS ? "https" : "http", host: host, port: expectedPort) - } - - XCTAssertEqual(urls, expectedURLs) - } -} diff --git a/Tests/SublimationTests/SublimationTests.swift b/Tests/SublimationTests/SublimationTests.swift new file mode 100644 index 0000000..c925dc7 --- /dev/null +++ b/Tests/SublimationTests/SublimationTests.swift @@ -0,0 +1,35 @@ +// +// SublimationTests.swift +// Sublimation +// +// Created by Leo Dion on 7/30/24. +// + +import XCTest + +final class SublimationTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Tests/SublimationVaporTests/NetworkResultTests.swift b/Tests/SublimationVaporTests/NetworkResultTests.swift deleted file mode 100644 index 92c9666..0000000 --- a/Tests/SublimationVaporTests/NetworkResultTests.swift +++ /dev/null @@ -1,217 +0,0 @@ -// -// NetworkResultTests.swift -// Sublimation -// -// Created by Leo Dion. -// Copyright © 2024 BrightDigit. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -import AsyncHTTPClient -import OpenAPIRuntime -@testable import SublimationTunnel -@testable import SublimationVapor -import XCTest - -internal func XCTAsyncAssert( - _ expression: @escaping () async throws -> Bool, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line -) async rethrows { - let expressionResult = try await expression() - XCTAssert(expressionResult, message(), file: file, line: line) -} - -internal class NetworkResultTests: XCTestCase { - // swiftlint:disable:next function_body_length - internal func testError() { - #if canImport(Network) - let posixError = HTTPClient.NWPOSIXError(.ECONNREFUSED, reason: "") - let clientPosixError = ClientError( - operationID: "", - operationInput: (), - causeDescription: "", - underlyingError: posixError - ) - let actualPosixError: HTTPClient.NWPOSIXError? = - NetworkResult(error: clientPosixError, isConnectionRefused: { $0.isConnectionRefused }).underlyingClientError() - XCTAssertEqual( - actualPosixError?.errorCode, - posixError.errorCode - ) - #endif - let timeoutError = HTTPClientError.connectTimeout - - let clientTimeoutError = ClientError( - operationID: "", - operationInput: (), - causeDescription: "", - underlyingError: timeoutError - ) - - let actualTimeoutError: HTTPClientError? = - NetworkResult(error: clientTimeoutError, isConnectionRefused: { $0.isConnectionRefused }).underlyingClientError() - XCTAssertEqual( - actualTimeoutError, - timeoutError - ) - - #if canImport(Network) - XCTAssert(NetworkResult(error: posixError, isConnectionRefused: { $0.isConnectionRefused }).isFailure) - #endif - XCTAssert(NetworkResult(error: timeoutError, isConnectionRefused: { $0.isConnectionRefused }).isFailure) - } - - // swiftlint:disable:next function_body_length - internal func testClosure() async { - #if canImport(Network) - let posixError = HTTPClient.NWPOSIXError(.ECONNREFUSED, reason: "") - let clientPosixError = ClientError( - operationID: "", - operationInput: (), - causeDescription: "", - underlyingError: posixError - ) - #endif - let timeoutError = HTTPClientError.connectTimeout - - let clientTimeoutError = ClientError( - operationID: "", - operationInput: (), - causeDescription: "", - underlyingError: timeoutError - ) - - #if canImport(Network) - let actualPosixError: HTTPClient.NWPOSIXError? = - await NetworkResult { throw clientPosixError } isConnectionRefused: { $0.isConnectionRefused }.underlyingClientError() - XCTAssertEqual( - actualPosixError?.errorCode, - posixError.errorCode - ) - #endif - - let actualTimeoutError: HTTPClientError? = - await NetworkResult { throw clientTimeoutError } isConnectionRefused: { $0.isConnectionRefused }.underlyingClientError() - XCTAssertEqual( - actualTimeoutError, - timeoutError - ) - - #if canImport(Network) - - await XCTAsyncAssert { await NetworkResult { throw posixError } isConnectionRefused: { $0.isConnectionRefused }.isFailure } - #endif - await XCTAsyncAssert { await NetworkResult { throw timeoutError } isConnectionRefused: { $0.isConnectionRefused }.isFailure } - await XCTAsyncAssert { await NetworkResult { throw timeoutError } isConnectionRefused: { $0.isConnectionRefused }.isFailure } - - await XCTAsyncAssert { await NetworkResult {} isConnectionRefused: { $0.isConnectionRefused }.isSuccess } - } - - // swiftlint:disable:next function_body_length - internal func testGet() async { - #if canImport(Network) - let posixError = HTTPClient.NWPOSIXError(.ECONNREFUSED, reason: "") - #endif - let timeoutError = HTTPClientError.connectTimeout - - #if canImport(Network) - let clientPosixError = ClientError( - operationID: "", - operationInput: (), - causeDescription: "", - underlyingError: posixError - ) - #endif - let clientTimeoutError = ClientError( - operationID: "", - operationInput: (), - causeDescription: "", - underlyingError: timeoutError - ) - let clientOtherError = ClientError( - operationID: "", - operationInput: (), - causeDescription: "", - underlyingError: URLError(.unknown) - ) - - #if canImport(Network) - do { - let value: Void? = try await NetworkResult { throw clientPosixError } isConnectionRefused: { $0.isConnectionRefused }.get() - XCTAssertNil(value) - } catch { - XCTAssertNil(error) - } - #endif - - do { - let value: Void? = try await NetworkResult { throw clientTimeoutError } isConnectionRefused: { $0.isConnectionRefused }.get() - XCTAssertNil(value) - } catch { - XCTAssertNil(error) - } - - var error: (any Error)? - do { - _ = try await NetworkResult { throw clientOtherError } isConnectionRefused: { $0.isConnectionRefused }.get() - error = nil - } catch let caughtError as ClientError { - error = caughtError - } catch { - XCTAssertNil(error) - } - XCTAssertNotNil(error) - - do { - let value: ()? = try await NetworkResult {} isConnectionRefused: { $0.isConnectionRefused }.get() - XCTAssertNotNil(value) - } catch { - XCTAssertNil(error) - } - } -} - -extension NetworkResult { - internal var isSuccess: Bool { - guard case .success = self else { - return false - } - return true - } - - internal var isFailure: Bool { - guard case .failure = self else { - return false - } - return true - } - - internal func underlyingClientError() -> Failure? where ConnectionErrorType == ClientError { - guard case let .connectionRefused(clientError) = self else { - return nil - } - return clientError.underlyingError as? Failure - } -}