From db38b1b57e12da4f543a87f099c29adbb258cc62 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Wed, 10 May 2023 12:17:57 +0100 Subject: [PATCH] Initial commit Co-authored-by: Honza Dvorsky Co-authored-by: Si Beaumont Co-authored-by: David Nadoba --- .github/PULL_REQUEST_TEMPLATE.md | 15 + .gitignore | 11 + .spi.yml | 5 + .swift-format | 58 ++++ CODE_OF_CONDUCT.md | 5 + CONTRIBUTING.md | 84 ++++++ CONTRIBUTORS.txt | 20 ++ LICENSE.txt | 203 ++++++++++++++ NOTICE.txt | 34 +++ Package.swift | 51 ++++ README.md | 36 +++ .../AsyncHTTPClientTransport.swift | 256 ++++++++++++++++++ .../Documentation.docc/Documentation.md | 42 +++ .../Test_AsyncHTTPClientTransport.swift | 137 ++++++++++ docker/Dockerfile | 23 ++ docker/docker-compose.2204.58.yaml | 18 ++ docker/docker-compose.2204.59.yaml | 17 ++ docker/docker-compose.2204.main.yaml | 17 ++ docker/docker-compose.yaml | 37 +++ scripts/check-for-broken-symlinks.sh | 37 +++ scripts/check-for-docc-warnings.sh | 40 +++ scripts/check-for-unacceptable-language.sh | 37 +++ scripts/check-license-headers.sh | 89 ++++++ scripts/generate-contributors-list.sh | 52 ++++ scripts/run-swift-format.sh | 41 +++ scripts/soundness.sh | 44 +++ scripts/unacceptable-language.txt | 15 + 27 files changed, 1424 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .spi.yml create mode 100644 .swift-format create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTORS.txt create mode 100644 LICENSE.txt create mode 100644 NOTICE.txt create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift create mode 100644 Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md create mode 100644 Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.2204.58.yaml create mode 100644 docker/docker-compose.2204.59.yaml create mode 100644 docker/docker-compose.2204.main.yaml create mode 100644 docker/docker-compose.yaml create mode 100644 scripts/check-for-broken-symlinks.sh create mode 100644 scripts/check-for-docc-warnings.sh create mode 100644 scripts/check-for-unacceptable-language.sh create mode 100644 scripts/check-license-headers.sh create mode 100644 scripts/generate-contributors-list.sh create mode 100644 scripts/run-swift-format.sh create mode 100755 scripts/soundness.sh create mode 100644 scripts/unacceptable-language.txt diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0e3306f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +### Motivation + +_[Explain here the context, and why you're making that change. What is the problem you're trying to solve.]_ + +### Modifications + +_[Describe the modifications you've made.]_ + +### Result + +_[After your change, what will change.]_ + +### Test Plan + +_[Describe the steps you took, or will take, to qualify the change - such as adjusting tests and manual testing.]_ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6f5465 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.vscode +/Package.resolved +.ci/ +.docc-build/ diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 0000000..6c06685 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,5 @@ +version: 1 +builder: + configs: + - documentation_targets: + - OpenAPIAsyncHTTPClient diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..e6a70ed --- /dev/null +++ b/.swift-format @@ -0,0 +1,58 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 4 + }, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : true, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineLength" : 120, + "maximumBlankLines" : 1, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLowerCamelCase" : false, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : false, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : false, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : false, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : false, + "ReturnVoidInsteadOfEmptyTuple" : true, + "UseEarlyExits" : true, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : false, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 8, + "version" : 1 +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..76501d7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,5 @@ +# Code of Conduct + +The code of conduct for this project can be found at https://swift.org/code-of-conduct. + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0cef7b6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,84 @@ +## Legal + +By submitting a pull request, you represent that you have the right to license +your contribution to Apple and the community, and agree by submitting the patch +that your contributions are licensed under the Apache 2.0 license (see +`LICENSE.txt`). + +## How to submit a bug report + +Please report any issues related to this library in the [swift-openapi-generator](https://github.com/apple/swift-openapi-generator/issues) repository. + +Specify the following: + +* Commit hash +* Contextual information (e.g. what you were trying to achieve with swift-openapi-async-http-client) +* Simplest possible steps to reproduce + * More complex the steps are, lower the priority will be. + * A pull request with failing test case is preferred, but it's just fine to paste the test case into the issue description. +* Anything that might be relevant in your opinion, such as: + * Swift version or the output of `swift --version` + * OS version and the output of `uname -a` + * Network configuration + +### Example + +``` +Commit hash: b17a8a9f0f814c01a56977680cb68d8a779c951f + +Context: +While testing my application that uses with swift-openapi-async-http-client, I noticed that ... + +Steps to reproduce: +1. ... +2. ... +3. ... +4. ... + +$ swift --version +Swift version 4.0.2 (swift-4.0.2-RELEASE) +Target: x86_64-unknown-linux-gnu + +Operating system: Ubuntu Linux 16.04 64-bit + +$ uname -a +Linux beefy.machine 4.4.0-101-generic #124-Ubuntu SMP Fri Nov 10 18:29:59 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux + +My system has IPv6 disabled. +``` + +## Writing a Patch + +A good patch is: + +1. Concise, and contains as few changes as needed to achieve the end result. +2. Tested, ensuring that any tests provided failed before the patch and pass after it. +3. Documented, adding API documentation as needed to cover new functions and properties. +4. Accompanied by a great commit message, using our commit message template. + +### Run `./scripts/soundness.sh` + +The scripts directory contains a [soundness.sh script](https://github.com/swift-server/swift-openapi-async-http-client/blob/main/scripts/soundness.sh) +that enforces additional checks, like license headers and formatting style. + +Please make sure to `./scripts/soundness.sh` before pushing a change upstream, otherwise it is likely the PR validation will fail +on minor changes such as a missing `self.` or similar formatting issues. + +For frequent contributors, we recommend adding the script as a [git pre-push hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks), which you can do via executing the following command in the project root directory: + +```bash +cat << EOF > .git/hooks/pre-push + +if [[ -f "scripts/soundness.sh" ]]; then + scripts/soundness.sh +fi +EOF +``` + +Which makes the script execute, and only allow the `git push` to complete if the check has passed. + +In the case of formatting issues, you can then `git add` the formatting changes, and attempt the push again. + +## How to contribute your work + +Please open a pull request at https://github.com/swift-server/swift-openapi-async-http-client. Make sure the CI passes, and then wait for code review. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 0000000..afaa202 --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,20 @@ +For the purpose of tracking copyright, this is the list of individuals and +organizations who have contributed source code to SwiftOpenAPIGenerator. + +For employees of an organization/company where the copyright of work done +by employees of that company is held by the company itself, only the company +needs to be listed here. + +## COPYRIGHT HOLDERS + +- Apple Inc. (all contributors with '@apple.com') + +### Contributors + +- David Nadoba +- Honza Dvorsky +- Si Beaumont + +**Updating this list** + +Please do not edit this file manually. It is generated using `./scripts/generate-contributors-list.sh`. If a name is misspelled or appearing multiple times: add an entry in `./.mailmap` diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6b0b127 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..aff3078 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,34 @@ + + The SwiftOpenAPIGenerator Project + ================================= + +Please visit the SwiftOpenAPIGenerator web site for more information: + + * https://github.com/swift-server/swift-openapi-async-http-client + +Copyright 2023 The SwiftOpenAPIGenerator Project + +The SwiftOpenAPIGenerator Project licenses this file to you under the Apache +License, version 2.0 (the "License"); you may not use this file except in +compliance with the License. You may obtain a copy of the License at: + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. + +Also, please refer to each LICENSE.txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. + +------------------------------------------------------------------------------- + +This product contains derivations of various scripts from SwiftNIO. + + * LICENSE (Apache License 2.0): + * https://www.apache.org/licenses/LICENSE-2.0 + * HOMEPAGE: + * https://github.com/apple/swift-nio diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..1e2e087 --- /dev/null +++ b/Package.swift @@ -0,0 +1,51 @@ +// swift-tools-version: 5.8 +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import Foundation +import PackageDescription + +let package = Package( + name: "swift-openapi-async-http-client", + platforms: [ + .macOS(.v13), + ], + products: [ + .library( + name: "OpenAPIAsyncHTTPClient", + targets: ["OpenAPIAsyncHTTPClient"] + ), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio", from: "2.51.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.17.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.0")), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), + ], + targets: [ + .target( + name: "OpenAPIAsyncHTTPClient", + dependencies: [ + .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + ] + ), + .testTarget( + name: "OpenAPIAsyncHTTPClientTests", + dependencies: [ + "OpenAPIAsyncHTTPClient", + ] + ), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c77902 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# AsyncHTTPClient Transport for Swift OpenAPI Generator + +A client transport that uses the [HTTPClient](https://swift-server.github.io/async-http-client/docs/current/AsyncHTTPClient/Classes/HTTPClient) type from the [AsyncHTTPClient](https://github.com/swift-server/async-http-client) library to perform HTTP operations. + +Use the transport with client code generated by [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator). + +## Usage + +Add the package dependency in your `Package.swift`: + +```swift +.package( + url: "https://github.com/swift-server/swift-openapi-async-http-client", + .upToNextMinor(from: "0.1.0") +), +``` + +Note that this repository does not have a 1.0 tag yet, so the API is not stable. + +Next, in your target, add `OpenAPIAsyncHTTPClient` to your dependencies: + +```swift +.target(name: "MyTarget", dependencies: [ + .product(name: "OpenAPIAsyncHTTPClient", package: "swift-openapi-async-http-client"), +], +``` + +Then, to get started, check out `AsyncHTTPClientTransport`. + +## Reporting issues + +Please report any issues related to this library in the [swift-openapi-generator](https://github.com/apple/swift-openapi-generator/issues) repository. + +## Documentation + +To learn more, check out the full [documentation](https://swiftpackageindex.com/swift-server/swift-openapi-async-http-client/documentation). diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift new file mode 100644 index 0000000..63b38b4 --- /dev/null +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -0,0 +1,256 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import OpenAPIRuntime +import AsyncHTTPClient +import NIOCore +import NIOHTTP1 +import Foundation +import NIOFoundationCompat + +/// A client transport that performs HTTP operations using the HTTPClient type +/// provided by the AsyncHTTPClient library. +/// +/// ### Use the AsyncHTTPClient transport +/// +/// Create the underlying HTTP client: +/// +/// let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +/// +/// Either store a reference to the client elsewhere and shut it down during +/// cleanup, or add a defer block if the client is only used in the current +/// scope: +/// +/// defer { +/// try! httpClient.syncShutdown() +/// } +/// +/// Instantiate the transport and provide the HTTP client to it: +/// +/// let transport = AsyncHTTPClientTransport( +/// configuration: .init(client: httpClient) +/// ) +/// +/// Create the base URL of the server to call using your client. If the server +/// URL was defined in the OpenAPI document, you find a generated method for it +/// on the `Servers` type, for example: +/// +/// let serverURL = try Servers.server1() +/// +/// Instantiate the `Client` type generated by the Swift OpenAPI Generator for +/// your provided OpenAPI document. For example: +/// +/// let client = Client( +/// serverURL: serverURL, +/// transport: transport +/// ) +/// +/// Use the client to make HTTP calls defined in your OpenAPI document. For +/// example, if the OpenAPI document contains an HTTP operation with +/// the identifier `checkHealth`, call it from Swift with: +/// +/// let response = try await client.checkHealth(.init()) +/// // ... +public struct AsyncHTTPClientTransport: ClientTransport { + + /// A set of configuration values for the AsyncHTTPClient transport. + public struct Configuration: Sendable { + + /// The HTTP client used for performing HTTP calls. + public var client: HTTPClient + + /// The default request timeout. + public var timeout: TimeAmount + + /// Creates a new configuration with the specified client and timeout. + /// - Parameters: + /// - client: The underlying client used to perform HTTP operations. + /// - timeout: The request timeout, defaults to 1 minute. + public init(client: HTTPClient, timeout: TimeAmount = .minutes(1)) { + self.client = client + self.timeout = timeout + } + } + + /// A request to be sent by the transport. + internal typealias Request = HTTPClientRequest + + /// A response returned by the transport. + internal typealias Response = HTTPClientResponse + + /// Specialized error thrown by the transport. + internal enum Error: Swift.Error, CustomStringConvertible, LocalizedError { + + /// Invalid URL composed from base URL and received request. + case invalidRequestURL(request: OpenAPIRuntime.Request, baseURL: URL) + + // MARK: CustomStringConvertible + + var description: String { + switch self { + case let .invalidRequestURL(request: request, baseURL: baseURL): + return + "Invalid request URL from request path: \(request.path), query: \(request.query ?? "") relative to base URL: \(baseURL.absoluteString)" + } + } + + // MARK: LocalizedError + + var errorDescription: String? { + description + } + } + + /// A set of configuration values used by the transport. + public var configuration: Configuration + + /// Underlying request sender for the transport. + internal let requestSender: HTTPRequestSending + + /// Creates a new transport. + /// - Parameters: + /// - configuration: A set of configuration values used by the transport. + /// - requestSender: The underlying request sender. + internal init( + configuration: Configuration, + requestSender: HTTPRequestSending + ) { + self.configuration = configuration + self.requestSender = requestSender + } + + /// Creates a new transport. + /// - Parameters: + /// - configuration: A set of configuration values used by the transport. + public init(configuration: Configuration) { + self.init( + configuration: configuration, + requestSender: AsyncHTTPRequestSender() + ) + } + + // MARK: ClientTransport + + public func send( + _ request: OpenAPIRuntime.Request, + baseURL: URL, + operationID: String + ) async throws -> OpenAPIRuntime.Response { + let httpRequest = try Self.convertRequest(request, baseURL: baseURL) + let httpResponse = try await invokeSession(with: httpRequest) + let response = try await Self.convertResponse(httpResponse) + return response + } + + // MARK: Internal + + /// Converts the shared Request type into URLRequest. + internal static func convertRequest( + _ request: OpenAPIRuntime.Request, + baseURL: URL + ) throws -> HTTPClientRequest { + guard var baseUrlComponents = URLComponents(string: baseURL.absoluteString) else { + throw Error.invalidRequestURL(request: request, baseURL: baseURL) + } + baseUrlComponents.path += request.path + baseUrlComponents.percentEncodedQuery = request.query + guard let url = baseUrlComponents.url else { + throw Error.invalidRequestURL(request: request, baseURL: baseURL) + } + var clientRequest = HTTPClientRequest(url: url.absoluteString) + clientRequest.method = request.method.asHTTPMethod + for header in request.headerFields { + clientRequest.headers.add(name: header.name.lowercased(), value: header.value) + } + if let body = request.body { + clientRequest.body = .bytes(body) + } + return clientRequest + } + + /// Converts the received URLResponse into the shared Response. + internal static func convertResponse( + _ httpResponse: HTTPClientResponse + ) async throws -> OpenAPIRuntime.Response { + let headerFields: [OpenAPIRuntime.HeaderField] = httpResponse + .headers + .map { .init(name: $0, value: $1) } + let body = try await httpResponse.body.collect(upTo: .max) + let bodyData = Data(buffer: body, byteTransferStrategy: .noCopy) + let response = OpenAPIRuntime.Response( + statusCode: Int(httpResponse.status.code), + headerFields: headerFields, + body: bodyData + ) + return response + } + + // MARK: Private + + /// Makes the underlying HTTP call. + private func invokeSession(with request: Request) async throws -> Response { + try await requestSender.send( + request: request, + with: configuration.client, + timeout: configuration.timeout + ) + } +} + +extension OpenAPIRuntime.HTTPMethod { + var asHTTPMethod: NIOHTTP1.HTTPMethod { + switch self { + case .get: + return .GET + case .put: + return .PUT + case .post: + return .POST + case .delete: + return .DELETE + case .options: + return .OPTIONS + case .head: + return .HEAD + case .patch: + return .PATCH + case .trace: + return .TRACE + default: + return .RAW(value: rawValue) + } + } +} + +/// A type that performs HTTP operations using the HTTP client. +internal protocol HTTPRequestSending: Sendable { + func send( + request: AsyncHTTPClientTransport.Request, + with client: HTTPClient, + timeout: TimeAmount + ) async throws -> AsyncHTTPClientTransport.Response +} + +/// Performs HTTP calls using AsyncHTTPClient +internal struct AsyncHTTPRequestSender: HTTPRequestSending { + func send( + request: AsyncHTTPClientTransport.Request, + with client: AsyncHTTPClient.HTTPClient, + timeout: TimeAmount + ) async throws -> AsyncHTTPClientTransport.Response { + try await client.execute( + request, + timeout: timeout + ) + } +} diff --git a/Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md b/Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md new file mode 100644 index 0000000..03ba2b0 --- /dev/null +++ b/Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md @@ -0,0 +1,42 @@ +# ``OpenAPIAsyncHTTPClient`` + +Send HTTP requests to the server using the AsyncHTTPClient library. + +## Overview + +A client transport that uses the [HTTPClient](https://swift-server.github.io/async-http-client/docs/current/AsyncHTTPClient/Classes/HTTPClient) type from the [AsyncHTTPClient](https://github.com/swift-server/async-http-client) library to perform HTTP operations. + +Use the transport with client code generated by [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator). + +### Usage + +Add the package dependency in your `Package.swift`: + +```swift +.package( + url: "https://github.com/swift-server/swift-openapi-async-http-client", + .upToNextMinor(from: "0.1.0") +), +``` + +Note that this repository does not have a 1.0 tag yet, so the API is not stable. + +Next, in your target, add `OpenAPIAsyncHTTPClient` to your dependencies: + +```swift +.target(name: "MyTarget", dependencies: [ + .product(name: "OpenAPIAsyncHTTPClient", package: "swift-openapi-async-http-client"), +], +``` + +Then, to get started, check out ``AsyncHTTPClientTransport``. + +### Reporting issues + +Please report any issues related to this library in the [swift-openapi-generator](https://github.com/apple/swift-openapi-generator/issues) repository. + +## Topics + +### Usage + +- ``AsyncHTTPClientTransport`` diff --git a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift new file mode 100644 index 0000000..b81fd02 --- /dev/null +++ b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import XCTest +import OpenAPIRuntime +import NIOCore +import NIOPosix +import AsyncHTTPClient +@testable import OpenAPIAsyncHTTPClient + +class Test_AsyncHTTPClientTransport: XCTestCase { + + static var testData: Data { + get throws { + try XCTUnwrap(#"[{}]"#.data(using: .utf8)) + } + } + + static var testBuffer: ByteBuffer { + ByteBuffer(string: #"[{}]"#) + } + + static var testUrl: URL { + get throws { + try XCTUnwrap(URL(string: "http://example.com/api/v1/hello/Maria?greeting=Howdy")) + } + } + + func testConvertRequest() throws { + let request: OpenAPIRuntime.Request = .init( + path: "/hello/Maria", + query: "greeting=Howdy", + method: .post, + headerFields: [ + .init(name: "content-type", value: "application/json") + ], + body: try Self.testData + ) + let httpRequest = try AsyncHTTPClientTransport.convertRequest( + request, + baseURL: try XCTUnwrap(URL(string: "http://example.com/api/v1")) + ) + XCTAssertEqual(httpRequest.url, "http://example.com/api/v1/hello/Maria?greeting=Howdy") + XCTAssertEqual(httpRequest.method, .POST) + XCTAssertEqual( + httpRequest.headers, + [ + "content-type": "application/json" + ] + ) + // TODO: Not sure how to test that httpRequest.body is what we expect, can't + // find an API for reading it back. + } + + func testConvertResponse() async throws { + let httpResponse: HTTPClientResponse = .init( + status: .ok, + headers: [ + "content-type": "application/json" + ], + body: .bytes(Self.testBuffer) + ) + let response = try await AsyncHTTPClientTransport.convertResponse(httpResponse) + XCTAssertEqual(response.statusCode, 200) + XCTAssertEqual( + response.headerFields, + [ + .init(name: "content-type", value: "application/json") + ] + ) + XCTAssertEqual(response.body, try Self.testData) + } + + func testSend() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let httpClient: HTTPClient = .init( + eventLoopGroupProvider: .shared(eventLoopGroup), + configuration: .init() + ) + defer { + try! httpClient.syncShutdown() + } + let transport = AsyncHTTPClientTransport( + configuration: .init(client: httpClient), + requestSender: TestSender.test + ) + let request: OpenAPIRuntime.Request = .init( + path: "/api/v1/hello/Maria", + method: .get, + headerFields: [ + .init(name: "x-request", value: "yes") + ] + ) + let response = try await transport.send( + request, + baseURL: Self.testUrl, + operationID: "sayHello" + ) + XCTAssertEqual(response.statusCode, 200) + } +} + +struct TestSender: HTTPRequestSending { + var sendClosure: + @Sendable (AsyncHTTPClientTransport.Request, HTTPClient, TimeAmount) async throws -> AsyncHTTPClientTransport + .Response + func send( + request: AsyncHTTPClientTransport.Request, + with client: HTTPClient, + timeout: TimeAmount + ) async throws -> AsyncHTTPClientTransport.Response { + try await sendClosure(request, client, timeout) + } + + static var test: Self { + TestSender { request, _, _ in + XCTAssertEqual(request.headers.first(name: "x-request"), "yes") + return HTTPClientResponse( + status: .ok, + headers: [ + "content-type": "application/json" + ], + body: .bytes(Test_AsyncHTTPClientTransport.testBuffer) + ) + } + } +} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..4b7014a --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,23 @@ +ARG swift_version=5.8 +ARG ubuntu_version=jammy +ARG base_image=swift:${swift_version}-${ubuntu_version} + +FROM ${base_image} +ARG swift_version +ARG ubuntu_version + +# set as UTF-8 +RUN apt-get update && apt-get install -y locales locales-all +ENV LC_ALL en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US.UTF-8 + +# tools +RUN mkdir -p $HOME/.tools +RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile + +# swift-format +ARG swiftformat_version=508.0.0 +RUN git clone --branch $swiftformat_version --depth 1 https://github.com/apple/swift-format $HOME/.tools/swift-format-source +RUN cd $HOME/.tools/swift-format-source && swift build -c release +RUN ln -s $HOME/.tools/swift-format-source/.build/release/swift-format $HOME/.tools/swift-format \ No newline at end of file diff --git a/docker/docker-compose.2204.58.yaml b/docker/docker-compose.2204.58.yaml new file mode 100644 index 0000000..eb4be59 --- /dev/null +++ b/docker/docker-compose.2204.58.yaml @@ -0,0 +1,18 @@ +version: "3" + +services: + runtime-setup: + image: &image swift-openapi-ahc:22.04-5.8 + build: + args: + ubuntu_version: "jammy" + swift_version: "5.8" + + test: + image: *image + environment: + - WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors + - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error + + shell: + image: *image diff --git a/docker/docker-compose.2204.59.yaml b/docker/docker-compose.2204.59.yaml new file mode 100644 index 0000000..a475877 --- /dev/null +++ b/docker/docker-compose.2204.59.yaml @@ -0,0 +1,17 @@ +version: "3" + +services: + runtime-setup: + image: &image swift-openapi-ahc:22.04-5.9 + build: + args: + base_image: "swiftlang/swift:nightly-5.9-jammy" + + test: + image: *image + environment: + - WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors + - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error + + shell: + image: *image diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml new file mode 100644 index 0000000..31dbe00 --- /dev/null +++ b/docker/docker-compose.2204.main.yaml @@ -0,0 +1,17 @@ +version: "3" + +services: + runtime-setup: + image: &image swift-openapi-ahc:22.04-main + build: + args: + base_image: "swiftlang/swift:nightly-main-jammy" + + test: + image: *image + environment: + - WARN_AS_ERROR_ARG=-Xswiftc -warnings-as-errors + - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error + + shell: + image: *image diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 0000000..057ea4e --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,37 @@ +# NOTE: This file is not designed to be run independently. +# +# Instead, use it with a file for a specific OS and Swift version, for example: +# +# % docker-compose \ +# -f docker/docker-compose.yaml \ +# -f docker/docker-compose.2204.58.yaml \ +# run test +# +version: "3" + +services: + runtime-setup: + image: &image swift-openapi-ahc:default + build: + context: . + dockerfile: Dockerfile + + common: &common + image: *image + depends_on: [runtime-setup] + volumes: + - ~/.ssh:/root/.ssh + - ..:/code:z + working_dir: /code + + soundness: + <<: *common + command: /bin/bash -xcl "swift -version && uname -a && ./scripts/soundness.sh" + + test: + <<: *common + command: /bin/bash -xcl "swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-}" + + shell: + <<: *common + entrypoint: /bin/bash diff --git a/scripts/check-for-broken-symlinks.sh b/scripts/check-for-broken-symlinks.sh new file mode 100644 index 0000000..4df8c92 --- /dev/null +++ b/scripts/check-for-broken-symlinks.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$(git -C "${CURRENT_SCRIPT_DIR}" rev-parse --show-toplevel)" + +log "Checking for broken symlinks..." +NUM_BROKEN_SYMLINKS=0 +while read -r -d '' file; do + if ! test -e "${REPO_ROOT}/${file}"; then + error "Broken symlink: ${file}" + ((NUM_BROKEN_SYMLINKS++)) + fi +done < <(git -C "${REPO_ROOT}" ls-files -z) + +if [ "${NUM_BROKEN_SYMLINKS}" -gt 0 ]; then + fatal "❌ Found ${NUM_BROKEN_SYMLINKS} symlinks." +fi + +log "✅ Found 0 symlinks." diff --git a/scripts/check-for-docc-warnings.sh b/scripts/check-for-docc-warnings.sh new file mode 100644 index 0000000..88215d4 --- /dev/null +++ b/scripts/check-for-docc-warnings.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## + +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +log "Checking required environment variables..." +test -n "${DOCC_TARGET:-}" || fatal "DOCC_TARGET unset" + +CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$(git -C "${CURRENT_SCRIPT_DIR}" rev-parse --show-toplevel)" + +swift package --package-path "${REPO_ROOT}" plugin generate-documentation \ + --product "${DOCC_TARGET}" \ + --analyze \ + --level detailed \ + --warnings-as-errors \ + && DOCC_PLUGIN_RC=$? || DOCC_PLUGIN_RC=$? + +if [ "${DOCC_PLUGIN_RC}" -ne 0 ]; then + fatal "❌ Generating documentation produced warnings and/or errors." + exit "${DOCC_PLUGIN_RC}" +fi + +log "✅ Generated documentation with no warnings." diff --git a/scripts/check-for-unacceptable-language.sh b/scripts/check-for-unacceptable-language.sh new file mode 100644 index 0000000..94f79df --- /dev/null +++ b/scripts/check-for-unacceptable-language.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$(git -C "${CURRENT_SCRIPT_DIR}" rev-parse --show-toplevel)" +UNACCEPTABLE_LANGUAGE_PATTERNS_PATH="${CURRENT_SCRIPT_DIR}/unacceptable-language.txt" + +log "Checking for unacceptable language..." +PATHS_WITH_UNACCEPTABLE_LANGUAGE=$(git -C "${REPO_ROOT}" grep \ + -l -F -w \ + -f "${UNACCEPTABLE_LANGUAGE_PATTERNS_PATH}" \ + -- \ + ":(exclude)${UNACCEPTABLE_LANGUAGE_PATTERNS_PATH}" \ +) || true | /usr/bin/paste -s -d " " - + +if [ -n "${PATHS_WITH_UNACCEPTABLE_LANGUAGE}" ]; then + fatal "❌ Found unacceptable language in files: ${PATHS_WITH_UNACCEPTABLE_LANGUAGE}." +fi + +log "✅ Found no unacceptable language." diff --git a/scripts/check-license-headers.sh b/scripts/check-license-headers.sh new file mode 100644 index 0000000..4fefc6b --- /dev/null +++ b/scripts/check-license-headers.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$(git -C "${CURRENT_SCRIPT_DIR}" rev-parse --show-toplevel)" + +EXPECTED_FILE_HEADER_TEMPLATE="@@===----------------------------------------------------------------------===@@ +@@ +@@ This source file is part of the SwiftOpenAPIGenerator open source project +@@ +@@ Copyright (c) YEARS Apple Inc. and the SwiftOpenAPIGenerator project authors +@@ Licensed under Apache License v2.0 +@@ +@@ See LICENSE.txt for license information +@@ See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +@@ +@@ SPDX-License-Identifier: Apache-2.0 +@@ +@@===----------------------------------------------------------------------===@@" + +PATHS_WITH_MISSING_LICENSE=( ) + +read -ra PATHS_TO_CHECK_FOR_LICENSE <<< "$( \ + git -C "${REPO_ROOT}" ls-files -z \ + ":(exclude).github/*" \ + ":(exclude).gitignore" \ + ":(exclude).spi.yml" \ + ":(exclude).swift-format" \ + ":(exclude)CODE_OF_CONDUCT.md" \ + ":(exclude)CONTRIBUTING.md" \ + ":(exclude)CONTRIBUTORS.txt" \ + ":(exclude)LICENSE.txt" \ + ":(exclude)NOTICE.txt" \ + ":(exclude)Package.swift" \ + ":(exclude)README.md" \ + ":(exclude)scripts/unacceptable-language.txt" \ + ":(exclude)docker/*" \ + ":(exclude)**/*.docc/*" \ + | xargs -0 \ +)" + +for FILE_PATH in "${PATHS_TO_CHECK_FOR_LICENSE[@]}"; do + FILE_BASENAME=$(basename -- "${FILE_PATH}") + FILE_EXTENSION="${FILE_BASENAME##*.}" + + case "${FILE_EXTENSION}" in + swift) EXPECTED_FILE_HEADER=$(sed -e 's|@@|//|g' <<<"${EXPECTED_FILE_HEADER_TEMPLATE}") ;; + yml) EXPECTED_FILE_HEADER=$(sed -e 's|@@|##|g' <<<"${EXPECTED_FILE_HEADER_TEMPLATE}") ;; + sh) EXPECTED_FILE_HEADER=$(cat <(echo '#!/usr/bin/env bash') <(sed -e 's|@@|##|g' <<<"${EXPECTED_FILE_HEADER_TEMPLATE}")) ;; + *) fatal "Unsupported file extension for file (exclude or update this script): ${FILE_PATH}" ;; + esac + EXPECTED_FILE_HEADER_LINECOUNT=$(wc -l <<<"${EXPECTED_FILE_HEADER}") + + FILE_HEADER=$(head -n "${EXPECTED_FILE_HEADER_LINECOUNT}" "${FILE_PATH}") + NORMALIZED_FILE_HEADER=$( + echo "${FILE_HEADER}" \ + | sed -e 's/202[3]-202[3]/YEARS/' -e 's/202[3]/YEARS/' \ + ) + + if ! diff -u \ + --label "Expected header" <(echo "${EXPECTED_FILE_HEADER}") \ + --label "${FILE_PATH}" <(echo "${NORMALIZED_FILE_HEADER}") + then + PATHS_WITH_MISSING_LICENSE+=("${FILE_PATH} ") + fi +done + +if [ "${#PATHS_WITH_MISSING_LICENSE[@]}" -gt 0 ]; then + fatal "❌ Found missing license header in files: ${PATHS_WITH_MISSING_LICENSE[*]}." +fi + +log "✅ Found no files with missing license header." diff --git a/scripts/generate-contributors-list.sh b/scripts/generate-contributors-list.sh new file mode 100644 index 0000000..99f072e --- /dev/null +++ b/scripts/generate-contributors-list.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftNIO open source project +## +## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftNIO project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## + +set -eu +here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +contributors=$( cd "$here"/.. && git shortlog -es | cut -f2 | sed 's/^/- /' ) + +cat > "$here/../CONTRIBUTORS.txt" <<- EOF + For the purpose of tracking copyright, this is the list of individuals and + organizations who have contributed source code to SwiftOpenAPIGenerator. + + For employees of an organization/company where the copyright of work done + by employees of that company is held by the company itself, only the company + needs to be listed here. + + ## COPYRIGHT HOLDERS + + - Apple Inc. (all contributors with '@apple.com') + + ### Contributors + + $contributors + + **Updating this list** + + Please do not edit this file manually. It is generated using \`./scripts/generate-contributors-list.sh\`. If a name is misspelled or appearing multiple times: add an entry in \`./.mailmap\` +EOF diff --git a/scripts/run-swift-format.sh b/scripts/run-swift-format.sh new file mode 100644 index 0000000..e2011cf --- /dev/null +++ b/scripts/run-swift-format.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$(git -C "${CURRENT_SCRIPT_DIR}" rev-parse --show-toplevel)" + +SWIFTFORMAT_BIN=${SWIFTFORMAT_BIN:-$(command -v swift-format)} || fatal "❌ SWIFTFORMAT_BIN unset and no swift-format on PATH" + +"${SWIFTFORMAT_BIN}" lint \ + --parallel --recursive --strict \ + "${REPO_ROOT}/Sources" "${REPO_ROOT}/Tests" \ + && SWIFT_FORMAT_RC=$? || SWIFT_FORMAT_RC=$? + +if [ "${SWIFT_FORMAT_RC}" -ne 0 ]; then + fatal "❌ Running swift-format produced errors. + + To fix, run the following command: + + % swift-format format --parallel --recursive --in-place Sources Tests + " + exit "${SWIFT_FORMAT_RC}" +fi + +log "✅ Ran swift-format with no errors." diff --git a/scripts/soundness.sh b/scripts/soundness.sh new file mode 100755 index 0000000..1baf17f --- /dev/null +++ b/scripts/soundness.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the SwiftOpenAPIGenerator open source project +## +## Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +NUM_CHECKS_FAILED=0 +export DOCC_TARGET=OpenAPIAsyncHTTPClient + +SCRIPT_PATHS=( + "${CURRENT_SCRIPT_DIR}/check-for-broken-symlinks.sh" + "${CURRENT_SCRIPT_DIR}/check-for-unacceptable-language.sh" + "${CURRENT_SCRIPT_DIR}/check-license-headers.sh" + "${CURRENT_SCRIPT_DIR}/run-swift-format.sh" + "${CURRENT_SCRIPT_DIR}/check-for-docc-warnings.sh" +) + +for SCRIPT_PATH in "${SCRIPT_PATHS[@]}"; do + log "Running ${SCRIPT_PATH}..." + if ! bash "${SCRIPT_PATH}"; then + ((NUM_CHECKS_FAILED+=1)) + fi +done + +if [ "${NUM_CHECKS_FAILED}" -gt 0 ]; then + fatal "❌ ${NUM_CHECKS_FAILED} soundness check(s) failed." +fi + +log "✅ All soundness check(s) passed." diff --git a/scripts/unacceptable-language.txt b/scripts/unacceptable-language.txt new file mode 100644 index 0000000..6ac4a98 --- /dev/null +++ b/scripts/unacceptable-language.txt @@ -0,0 +1,15 @@ +blacklist +whitelist +slave +master +sane +sanity +insane +insanity +kill +killed +killing +hang +hung +hanged +hanging