From e7f13a2f4396dedd7e63abfa69a5139473c702fe Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sat, 21 Jan 2023 22:41:20 +1100 Subject: [PATCH 01/36] Fail tests if a test file fails to be parsed --- Tests/ScreamURITemplateTests/TestFileTests.swift | 5 ++++- Tests/ScreamURITemplateTests/TestModels.swift | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Tests/ScreamURITemplateTests/TestFileTests.swift b/Tests/ScreamURITemplateTests/TestFileTests.swift index 22c0685..a38ac7e 100644 --- a/Tests/ScreamURITemplateTests/TestFileTests.swift +++ b/Tests/ScreamURITemplateTests/TestFileTests.swift @@ -80,7 +80,10 @@ class TestFileTests: XCTestCase { return fileTestSuite } - let testGroups = parseTestFile(URL: testURL) + guard let testGroups = parseTestFile(URL: testURL) else { + fileTestSuite.addTest(TestFileTests(selector: #selector(TestFileTests.testFileParseFailed))) + return fileTestSuite + } for group in testGroups { let groupTestSuite = XCTestSuite(name: "Group: \(group.name)") for test in group.testcases { diff --git a/Tests/ScreamURITemplateTests/TestModels.swift b/Tests/ScreamURITemplateTests/TestModels.swift index 37efab9..7352989 100644 --- a/Tests/ScreamURITemplateTests/TestModels.swift +++ b/Tests/ScreamURITemplateTests/TestModels.swift @@ -135,11 +135,11 @@ extension TestCase { } } -public func parseTestFile(URL: URL) -> [TestGroup] { +public func parseTestFile(URL: URL) -> [TestGroup]? { guard let testData = try? Data(contentsOf: URL), let testCollection = try? JSONDecoder().decode(TestFile.self, from: testData) else { print("Failed to decode test file \(URL)") - return [] + return nil } return testCollection.map { testGroupName, testGroupData in From f5d3f0eefb51e94caeb6745ebd7d34290b70c913 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sat, 9 Sep 2023 16:44:17 +1000 Subject: [PATCH 02/36] Fix swiftformat violations --- .../Internal/CharacterSets.swift | 20 +++++++++---------- .../Internal/Components.swift | 12 +++++------ .../Internal/ExpansionConfiguration.swift | 2 +- .../Internal/ExpressionOperator.swift | 2 +- .../ScreamURITemplate/Internal/Scanner.swift | 2 +- .../Internal/ValueFormatting.swift | 10 +++++----- .../Internal/VariableSpec.swift | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/CharacterSets.swift b/Sources/ScreamURITemplate/Internal/CharacterSets.swift index ec56760..6c3b1f0 100644 --- a/Sources/ScreamURITemplate/Internal/CharacterSets.swift +++ b/Sources/ScreamURITemplate/Internal/CharacterSets.swift @@ -14,15 +14,15 @@ import Foundation -internal let unreservedCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-._~")) +let unreservedCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-._~")) private let genDelimsCharacterSet = CharacterSet(charactersIn: ":/?#[]@") private let subDelimsCharacterSet = CharacterSet(charactersIn: "!$&'()*+,;=") -internal let reservedCharacterSet = genDelimsCharacterSet.union(subDelimsCharacterSet) -internal let reservedAndUnreservedCharacterSet = reservedCharacterSet.union(unreservedCharacterSet) -internal let invertedLiteralCharacterSet = CharacterSet.illegalCharacters.union(CharacterSet.controlCharacters).union(CharacterSet(charactersIn: " \"%<>\\^`{|}")) -internal let literalCharacterSet = invertedLiteralCharacterSet.inverted -internal let hexCharacterSet = CharacterSet(charactersIn: "0123456789abcdefABCDEF") -internal let varnameCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "_%.")) -internal let invertedVarnameCharacterSet = varnameCharacterSet.inverted -internal let expressionOperatorCharacterSet = CharacterSet(charactersIn: "+#./;?&=,!@|") -internal let invertedDecimalDigitsCharacterSet = CharacterSet.decimalDigits.inverted +let reservedCharacterSet = genDelimsCharacterSet.union(subDelimsCharacterSet) +let reservedAndUnreservedCharacterSet = reservedCharacterSet.union(unreservedCharacterSet) +let invertedLiteralCharacterSet = CharacterSet.illegalCharacters.union(CharacterSet.controlCharacters).union(CharacterSet(charactersIn: " \"%<>\\^`{|}")) +let literalCharacterSet = invertedLiteralCharacterSet.inverted +let hexCharacterSet = CharacterSet(charactersIn: "0123456789abcdefABCDEF") +let varnameCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "_%.")) +let invertedVarnameCharacterSet = varnameCharacterSet.inverted +let expressionOperatorCharacterSet = CharacterSet(charactersIn: "+#./;?&=,!@|") +let invertedDecimalDigitsCharacterSet = CharacterSet.decimalDigits.inverted diff --git a/Sources/ScreamURITemplate/Internal/Components.swift b/Sources/ScreamURITemplate/Internal/Components.swift index c6d7db0..306ed03 100644 --- a/Sources/ScreamURITemplate/Internal/Components.swift +++ b/Sources/ScreamURITemplate/Internal/Components.swift @@ -15,12 +15,12 @@ import Foundation #if swift(>=5.5) - internal typealias ComponentBase = Sendable + typealias ComponentBase = Sendable #else - internal protocol ComponentBase {} + protocol ComponentBase {} #endif -internal protocol Component: ComponentBase { +protocol Component: ComponentBase { func expand(variables: [String: VariableValue]) throws -> String var variableNames: [String] { get } } @@ -31,7 +31,7 @@ extension Component { } } -internal struct LiteralComponent: Component { +struct LiteralComponent: Component { let literal: Substring init(_ string: Substring) { literal = string @@ -46,7 +46,7 @@ internal struct LiteralComponent: Component { } } -internal struct LiteralPercentEncodedTripletComponent: Component { +struct LiteralPercentEncodedTripletComponent: Component { let literal: Substring init(_ string: Substring) { literal = string @@ -57,7 +57,7 @@ internal struct LiteralPercentEncodedTripletComponent: Component { } } -internal struct ExpressionComponent: Component { +struct ExpressionComponent: Component { let expressionOperator: ExpressionOperator let variableList: [VariableSpec] let templatePosition: String.Index diff --git a/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift b/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift index f4c2a49..fb2fad7 100644 --- a/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift +++ b/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift @@ -14,7 +14,7 @@ import Foundation -internal struct ExpansionConfiguration { +struct ExpansionConfiguration { let percentEncodingAllowedCharacterSet: CharacterSet let allowPercentEncodedTriplets: Bool let prefix: String? diff --git a/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift b/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift index cfd13d0..f48133f 100644 --- a/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift +++ b/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift @@ -14,7 +14,7 @@ import Foundation -internal enum ExpressionOperator: Unicode.Scalar { +enum ExpressionOperator: Unicode.Scalar { case simple = "\0" case reserved = "+" case fragment = "#" diff --git a/Sources/ScreamURITemplate/Internal/Scanner.swift b/Sources/ScreamURITemplate/Internal/Scanner.swift index 2ea9bf5..4bd2d02 100644 --- a/Sources/ScreamURITemplate/Internal/Scanner.swift +++ b/Sources/ScreamURITemplate/Internal/Scanner.swift @@ -18,7 +18,7 @@ private func ~= (lhs: CharacterSet, rhs: Unicode.Scalar) -> Bool { return lhs.contains(rhs) } -internal struct Scanner { +struct Scanner { let string: String let unicodeScalars: String.UnicodeScalarView var currentIndex: String.Index diff --git a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift index 0432976..3a6626e 100644 --- a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift +++ b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift @@ -14,11 +14,11 @@ import Foundation -internal enum FormatError: Error { +enum FormatError: Error { case failure(reason: String) } -internal func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: CharacterSet, allowPercentEncodedTriplets: Bool) throws -> String { +func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: CharacterSet, allowPercentEncodedTriplets: Bool) throws -> String { guard var encoded = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) else { throw FormatError.failure(reason: "Percent Encoding Failed") } @@ -45,7 +45,7 @@ internal func percentEncode(string: String, withAllowedCharacters allowedCharact return encoded } -internal extension StringProtocol { +extension StringProtocol { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String { let modifiedValue: String if let prefixLength = variableSpec.prefixLength() { @@ -64,7 +64,7 @@ internal extension StringProtocol { } } -internal extension Array where Element: StringProtocol { +extension Array where Element: StringProtocol { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? { let separator = "," let encodedExpansions = try map { element -> String in @@ -102,7 +102,7 @@ internal extension Array where Element: StringProtocol { } } -internal extension Dictionary where Key: StringProtocol, Value: StringProtocol { +extension Dictionary where Key: StringProtocol, Value: StringProtocol { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? { let encodedExpansions = try map { key, value -> String in let encodedKey = try percentEncode(string: String(key), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) diff --git a/Sources/ScreamURITemplate/Internal/VariableSpec.swift b/Sources/ScreamURITemplate/Internal/VariableSpec.swift index 56800cf..8834570 100644 --- a/Sources/ScreamURITemplate/Internal/VariableSpec.swift +++ b/Sources/ScreamURITemplate/Internal/VariableSpec.swift @@ -14,7 +14,7 @@ import Foundation -internal struct VariableSpec { +struct VariableSpec { enum Modifier { case prefix(length: Int) case explode From adca5613a0bcc15461b893d034f74f68d031c221 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sat, 9 Sep 2023 16:44:34 +1000 Subject: [PATCH 03/36] Specify versions of mint dependencies --- Mintfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mintfile b/Mintfile index 8a1487a..7be5b72 100644 --- a/Mintfile +++ b/Mintfile @@ -1,2 +1,2 @@ -realm/SwiftLint -nicklockwood/SwiftFormat +realm/SwiftLint@0.52.4 +nicklockwood/SwiftFormat@0.52.3 From 47cc1e701e76b765695cc7690420c3bdec6d12bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Sep 2023 07:26:31 +0000 Subject: [PATCH 04/36] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- .github/workflows/lint.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87acf80..b2eed29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Build @@ -28,7 +28,7 @@ jobs: build-5_4_2: runs-on: macos-11 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Check Swift version diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f32e050..468d392 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: SwiftLint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: irgaly/setup-mint@v1 - name: SwiftLint run: mint run swiftlint --strict @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest needs: SwiftLint steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: irgaly/setup-mint@v1 - name: SwiftFormat Lint run: mint run swiftformat --lint . From ae3e5ac88c069fc4ed46e8f0050c7f7a9d5d69a6 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 20 Dec 2023 23:57:39 +1100 Subject: [PATCH 05/36] Use macos runner for lint github action swiftlint crashes on latest ubuntu runner --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 468d392..d77520d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,14 +8,14 @@ on: jobs: SwiftLint: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: irgaly/setup-mint@v1 - name: SwiftLint run: mint run swiftlint --strict SwiftFormat: - runs-on: ubuntu-latest + runs-on: macos-latest needs: SwiftLint steps: - uses: actions/checkout@v4 From 3800348b8eaaeb235b07331df061c4c1b6dd9217 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 10 Dec 2023 21:10:43 +1100 Subject: [PATCH 06/36] Update swiftlint to 0.54.0 --- .github/workflows/lint.yml | 4 +++- Mintfile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d77520d..da834a8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,9 +8,11 @@ on: jobs: SwiftLint: - runs-on: macos-latest + runs-on: macos-13 steps: - uses: actions/checkout@v4 + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.app/ - uses: irgaly/setup-mint@v1 - name: SwiftLint run: mint run swiftlint --strict diff --git a/Mintfile b/Mintfile index 7be5b72..69eb4bb 100644 --- a/Mintfile +++ b/Mintfile @@ -1,2 +1,2 @@ -realm/SwiftLint@0.52.4 +realm/SwiftLint@0.54.0 nicklockwood/SwiftFormat@0.52.3 From 2f73acea9d08b4c9103bfc19d4e1c6cec5a05efc Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 10 Dec 2023 21:13:45 +1100 Subject: [PATCH 07/36] Update swiftformat to 0.52.11 --- .github/workflows/lint.yml | 4 +++- Mintfile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index da834a8..1535ef9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,10 +17,12 @@ jobs: - name: SwiftLint run: mint run swiftlint --strict SwiftFormat: - runs-on: macos-latest + runs-on: macos-13 needs: SwiftLint steps: - uses: actions/checkout@v4 + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.app/ - uses: irgaly/setup-mint@v1 - name: SwiftFormat Lint run: mint run swiftformat --lint . diff --git a/Mintfile b/Mintfile index 69eb4bb..064bc1d 100644 --- a/Mintfile +++ b/Mintfile @@ -1,2 +1,2 @@ realm/SwiftLint@0.54.0 -nicklockwood/SwiftFormat@0.52.3 +nicklockwood/SwiftFormat@0.52.11 From 1d830e410c9ac3c4ceae255fab7e71b9e673fdfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 12:05:18 +0000 Subject: [PATCH 08/36] Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2eed29..2927f43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Prepare coverage file run: xcrun llvm-cov export -format="lcov" .build/debug/ScreamURITemplatePackageTests.xctest/Contents/MacOS/ScreamURITemplatePackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: true verbose: true From 382be1d3870a61d12186edc69e139a564530568c Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 29 May 2024 22:19:39 +1000 Subject: [PATCH 09/36] supply codecov token --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2927f43..f78f7dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: run: xcrun llvm-cov export -format="lcov" .build/debug/ScreamURITemplatePackageTests.xctest/Contents/MacOS/ScreamURITemplatePackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: fail_ci_if_error: true verbose: true From 68e3283b2c87c1f3fc3ab1619b10fd00c1f2e0f9 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 29 May 2024 21:58:20 +1000 Subject: [PATCH 10/36] Update swiftlint to 0.55.1 --- Mintfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mintfile b/Mintfile index 064bc1d..cbe3a0e 100644 --- a/Mintfile +++ b/Mintfile @@ -1,2 +1,2 @@ -realm/SwiftLint@0.54.0 +realm/SwiftLint@0.55.1 nicklockwood/SwiftFormat@0.52.11 From fbe3872da97d202b10a2ed331232218b1f104588 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 29 May 2024 21:58:37 +1000 Subject: [PATCH 11/36] Update swiftformat to 0.53.10 --- Mintfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mintfile b/Mintfile index cbe3a0e..fd88089 100644 --- a/Mintfile +++ b/Mintfile @@ -1,2 +1,2 @@ realm/SwiftLint@0.55.1 -nicklockwood/SwiftFormat@0.52.11 +nicklockwood/SwiftFormat@0.53.10 From 7053bdcaae808a68b252a9e336c42141cb51ba23 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 29 May 2024 22:52:49 +1000 Subject: [PATCH 12/36] Use macos-latest for lint github workflow --- .github/workflows/lint.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1535ef9..d77520d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,21 +8,17 @@ on: jobs: SwiftLint: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v4 - - name: Select Xcode 15 - run: sudo xcode-select -s /Applications/Xcode_15.0.app/ - uses: irgaly/setup-mint@v1 - name: SwiftLint run: mint run swiftlint --strict SwiftFormat: - runs-on: macos-13 + runs-on: macos-latest needs: SwiftLint steps: - uses: actions/checkout@v4 - - name: Select Xcode 15 - run: sudo xcode-select -s /Applications/Xcode_15.0.app/ - uses: irgaly/setup-mint@v1 - name: SwiftFormat Lint run: mint run swiftformat --lint . From 5f693e844bed336352e8cb39c592817b5e483337 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Fri, 31 May 2024 20:21:07 +1000 Subject: [PATCH 13/36] Fix spm unhandled files warning --- Package.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Package.swift b/Package.swift index fd7ed4d..f493b07 100644 --- a/Package.swift +++ b/Package.swift @@ -18,6 +18,12 @@ let package = Package( .testTarget( name: "ScreamURITemplateTests", dependencies: ["ScreamURITemplate"], + exclude: [ + "data/uritemplate-test/json2xml.xslt", + "data/uritemplate-test/LICENSE", + "data/uritemplate-test/README.md", + "data/uritemplate-test/transform-json-tests.xslt", + ], resources: [ .process("data/tests.json"), .process("data/uritemplate-test/spec-examples.json"), From 8430a6aaeae622b34e6f75e671485ac9560b0f64 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 2 Jun 2024 10:46:28 +1000 Subject: [PATCH 14/36] Move to Swift 5.9 as the minimum version --- .github/workflows/ci.yml | 14 -------------- Package.swift | 13 ++++--------- .../ScreamURITemplate/Internal/Components.swift | 6 +----- Sources/ScreamURITemplate/URITemplate.swift | 4 +--- Tests/ScreamURITemplateTests/Tests.swift | 12 +++++------- 5 files changed, 11 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f78f7dd..c9f6dbb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,17 +26,3 @@ jobs: with: fail_ci_if_error: true verbose: true - - build-5_4_2: - runs-on: macos-11 - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Check Swift version - run: | - sudo xcode-select -s /Applications/Xcode_12.5.1.app/ - export TOOLCHAINS=swift - swift --version - - name: Run tests - run: swift test diff --git a/Package.swift b/Package.swift index f493b07..69d682c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.4 +// swift-tools-version: 5.9 import PackageDescription @@ -31,13 +31,8 @@ let package = Package( .process("data/uritemplate-test/extended-tests.json"), .process("data/uritemplate-test/negative-tests.json"), ]), - ], - swiftLanguageVersions: [.v5]) - -#if swift(>=5.6) || os(macOS) || os(Linux) - package.targets.append( .executableTarget( name: "ScreamURITemplateExample", - dependencies: ["ScreamURITemplate"]) - ) -#endif + dependencies: ["ScreamURITemplate"]), + ], + swiftLanguageVersions: [.v5]) diff --git a/Sources/ScreamURITemplate/Internal/Components.swift b/Sources/ScreamURITemplate/Internal/Components.swift index 306ed03..299cc32 100644 --- a/Sources/ScreamURITemplate/Internal/Components.swift +++ b/Sources/ScreamURITemplate/Internal/Components.swift @@ -14,11 +14,7 @@ import Foundation -#if swift(>=5.5) - typealias ComponentBase = Sendable -#else - protocol ComponentBase {} -#endif +typealias ComponentBase = Sendable protocol Component: ComponentBase { func expand(variables: [String: VariableValue]) throws -> String diff --git a/Sources/ScreamURITemplate/URITemplate.swift b/Sources/ScreamURITemplate/URITemplate.swift index 8c31bc7..2b8cb34 100644 --- a/Sources/ScreamURITemplate/URITemplate.swift +++ b/Sources/ScreamURITemplate/URITemplate.swift @@ -53,9 +53,7 @@ public struct URITemplate { } } -#if swift(>=5.5) - extension URITemplate: Sendable {} -#endif +extension URITemplate: Sendable {} extension URITemplate: CustomStringConvertible { public var description: String { diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index dc447ba..51a7098 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -16,13 +16,11 @@ import ScreamURITemplate import XCTest class Tests: XCTestCase { - #if swift(>=5.5) - func testSendable() { - let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let sendable = template as Sendable - XCTAssertNotNil(sendable) - } - #endif + func testSendable() { + let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let sendable = template as Sendable + XCTAssertNotNil(sendable) + } func testCustomStringConvertible() { let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" From 0cc13b146592fe386bb20dd2e96c11928372947a Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 2 Jun 2024 10:47:30 +1000 Subject: [PATCH 15/36] Opt in to swift 5.9 rules for swiftformat --- .swift-version | 1 + .swiftformat | 1 - Sources/ScreamURITemplate/Internal/ValueFormatting.swift | 7 +++---- 3 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..b883184 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +5.9 \ No newline at end of file diff --git a/.swiftformat b/.swiftformat index 24a794d..9857c7f 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,4 +1,3 @@ ---swiftversion 5.7 --exclude .build # rules diff --git a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift index 3a6626e..3684f78 100644 --- a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift +++ b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift @@ -47,11 +47,10 @@ func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: Ch extension StringProtocol { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String { - let modifiedValue: String - if let prefixLength = variableSpec.prefixLength() { - modifiedValue = String(prefix(prefixLength)) + let modifiedValue = if let prefixLength = variableSpec.prefixLength() { + String(prefix(prefixLength)) } else { - modifiedValue = String(self) + String(self) } let encodedExpansion = try percentEncode(string: modifiedValue, withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) if expansionConfiguration.named { From 234d7942f8dd14dccc54ab689be2a3b0d8985d66 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 2 Jun 2024 10:55:05 +1000 Subject: [PATCH 16/36] Configure dependabot for SPM --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e6ba580..e8eccdf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,3 +15,11 @@ updates: day: "friday" timezone: "Australia/Sydney" target-branch: "develop" + + - package-ecosystem: "swift" + directory: "/" + schedule: + interval: "weekly" + day: "friday" + timezone: "Australia/Sydney" + target-branch: "develop" From 4c2b9fe9ef5eac59eee8b5e7f1b9b0b35a21f748 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 2 Jun 2024 14:16:10 +1000 Subject: [PATCH 17/36] Refactor throwing tests --- .../TestFileTests.swift | 12 ++---- Tests/ScreamURITemplateTests/Tests.swift | 41 +++++++------------ 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/Tests/ScreamURITemplateTests/TestFileTests.swift b/Tests/ScreamURITemplateTests/TestFileTests.swift index a38ac7e..781456c 100644 --- a/Tests/ScreamURITemplateTests/TestFileTests.swift +++ b/Tests/ScreamURITemplateTests/TestFileTests.swift @@ -26,14 +26,10 @@ class TestFileTests: XCTestCase { XCTFail("Test File Parse Failed") } - func testSuccessfulProcess() { - do { - let template = try URITemplate(string: templateString) - let result = try template.process(variables: variables) - XCTAssertTrue(acceptableExpansions.contains(result)) - } catch { - XCTFail("Unexpected Throw") - } + func testSuccessfulProcess() throws { + let template = try URITemplate(string: templateString) + let result = try template.process(variables: variables) + XCTAssertTrue(acceptableExpansions.contains(result)) } func testFailedProcess() { diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index 51a7098..e6de45d 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -27,12 +27,9 @@ class Tests: XCTestCase { XCTAssertEqual(template.description, "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") } - func testExpressibleByStringLiteral() { + func testExpressibleByStringLiteral() throws { let templateA: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - guard let templateB = try? URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") else { - XCTFail("invalid template") - return - } + let templateB = try URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") XCTAssertEqual(templateA, templateB) } @@ -71,29 +68,21 @@ class Tests: XCTestCase { XCTAssertEqual(variableNames, expected) } - func testEncoding() { - do { - let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let template = try URITemplate(string: templateString) - let jsonData = try JSONEncoder().encode(["a": template]) - let expectedData = try JSONEncoder().encode(["a": templateString]) - XCTAssertEqual(jsonData, expectedData) - } catch { - XCTFail("unexpected throw") - } + func testEncoding() throws { + let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let template = try URITemplate(string: templateString) + let jsonData = try JSONEncoder().encode(["a": template]) + let expectedData = try JSONEncoder().encode(["a": templateString]) + XCTAssertEqual(jsonData, expectedData) } - func testDecoding() { - do { - let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let jsonString = "{\"a\":\"\(templateString)\"}" - let jsonData = jsonString.data(using: .utf8)! - let object = try JSONDecoder().decode([String: URITemplate].self, from: jsonData) - let expectedTemplate = try URITemplate(string: templateString) - XCTAssertEqual(object["a"], expectedTemplate) - } catch { - XCTFail("unexpected throw") - } + func testDecoding() throws { + let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let jsonString = "{\"a\":\"\(templateString)\"}" + let jsonData = jsonString.data(using: .utf8)! + let object = try JSONDecoder().decode([String: URITemplate].self, from: jsonData) + let expectedTemplate = try URITemplate(string: templateString) + XCTAssertEqual(object["a"], expectedTemplate) } func testInitPerformance() { From ad49467856acb8bb0adb8482fa5d92424c702379 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 9 Jun 2024 22:16:51 +1000 Subject: [PATCH 18/36] Improve variable interface --- .../Internal/Components.swift | 25 +++--- .../Internal/ValueFormatting.swift | 2 +- Sources/ScreamURITemplate/URITemplate.swift | 11 ++- .../ScreamURITemplate/VariableProvider.swift | 22 ++++++ Sources/ScreamURITemplate/VariableValue.swift | 79 +++++++++++++++++++ .../TestFileTests.swift | 2 +- Tests/ScreamURITemplateTests/TestModels.swift | 51 ++++++------ Tests/ScreamURITemplateTests/Tests.swift | 66 ++++++++++++++++ 8 files changed, 208 insertions(+), 50 deletions(-) create mode 100644 Sources/ScreamURITemplate/VariableProvider.swift create mode 100644 Sources/ScreamURITemplate/VariableValue.swift diff --git a/Sources/ScreamURITemplate/Internal/Components.swift b/Sources/ScreamURITemplate/Internal/Components.swift index 299cc32..ccd66fb 100644 --- a/Sources/ScreamURITemplate/Internal/Components.swift +++ b/Sources/ScreamURITemplate/Internal/Components.swift @@ -17,7 +17,7 @@ import Foundation typealias ComponentBase = Sendable protocol Component: ComponentBase { - func expand(variables: [String: VariableValue]) throws -> String + func expand(variables: VariableProvider) throws -> String var variableNames: [String] { get } } @@ -33,7 +33,7 @@ struct LiteralComponent: Component { literal = string } - func expand(variables _: [String: VariableValue]) throws -> String { + func expand(variables _: VariableProvider) throws -> String { let expansion = String(literal) guard let encodedExpansion = expansion.addingPercentEncoding(withAllowedCharacters: reservedAndUnreservedCharacterSet) else { throw URITemplate.Error.expansionFailure(position: literal.startIndex, reason: "Percent Encoding Failed") @@ -48,7 +48,7 @@ struct LiteralPercentEncodedTripletComponent: Component { literal = string } - func expand(variables _: [String: VariableValue]) throws -> String { + func expand(variables _: VariableProvider) throws -> String { return String(literal) } } @@ -65,16 +65,17 @@ struct ExpressionComponent: Component { } // swiftlint:disable:next cyclomatic_complexity - func expand(variables: [String: VariableValue]) throws -> String { + func expand(variables: VariableProvider) throws -> String { let configuration = expressionOperator.expansionConfiguration() let expansions = try variableList.compactMap { variableSpec -> String? in - guard let value = variables[String(variableSpec.name)] else { + guard let value = variables[String(variableSpec.name)]?.asTypedVariableValue() else { return nil } do { - if let stringValue = value as? String { - return try stringValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) - } else if let arrayValue = value as? [String] { + switch value { + case let .string(plainValue): + return try plainValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + case let .list(arrayValue): switch variableSpec.modifier { case .prefix: throw FormatError.failure(reason: "Prefix operator can only be applied to string") @@ -83,17 +84,15 @@ struct ExpressionComponent: Component { case .none: return try arrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) } - } else if let dictionaryValue = value as? [String: String] { + case let .associativeArray(associativeArrayValue): switch variableSpec.modifier { case .prefix: throw FormatError.failure(reason: "Prefix operator can only be applied to string") case .explode: - return try dictionaryValue.explodeForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + return try associativeArrayValue.explodeForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) case .none: - return try dictionaryValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + return try associativeArrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) } - } else { - throw FormatError.failure(reason: "Invalid Value Type") } } catch let FormatError.failure(reason) { throw URITemplate.Error.expansionFailure(position: templatePosition, reason: "Failed expanding variable \"\(variableSpec.name)\": \(reason)") diff --git a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift index 3684f78..1915440 100644 --- a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift +++ b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift @@ -101,7 +101,7 @@ extension Array where Element: StringProtocol { } } -extension Dictionary where Key: StringProtocol, Value: StringProtocol { +extension [TypedVariableValue.AssociativeArrayElement] { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? { let encodedExpansions = try map { key, value -> String in let encodedKey = try percentEncode(string: String(key), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) diff --git a/Sources/ScreamURITemplate/URITemplate.swift b/Sources/ScreamURITemplate/URITemplate.swift index 2b8cb34..70175c5 100644 --- a/Sources/ScreamURITemplate/URITemplate.swift +++ b/Sources/ScreamURITemplate/URITemplate.swift @@ -14,11 +14,6 @@ import Foundation -public protocol VariableValue {} -extension String: VariableValue {} -extension Array: VariableValue where Element: StringProtocol {} -extension Dictionary: VariableValue where Key: StringProtocol, Value: StringProtocol {} - public struct URITemplate { public enum Error: Swift.Error { case malformedTemplate(position: String.Index, reason: String) @@ -38,7 +33,7 @@ public struct URITemplate { self.components = components } - public func process(variables: [String: VariableValue]) throws -> String { + public func process(variables: VariableProvider) throws -> String { var result = "" for component in components { result += try component.expand(variables: variables) @@ -46,6 +41,10 @@ public struct URITemplate { return result } + public func process(variables: [String: String]) throws -> String { + return try process(variables: variables as VariableDictionary) + } + public var variableNames: [String] { return components.flatMap { component in return component.variableNames diff --git a/Sources/ScreamURITemplate/VariableProvider.swift b/Sources/ScreamURITemplate/VariableProvider.swift new file mode 100644 index 0000000..739d94b --- /dev/null +++ b/Sources/ScreamURITemplate/VariableProvider.swift @@ -0,0 +1,22 @@ +// +// 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. + +import Foundation + +public protocol VariableProvider { + subscript(_: String) -> VariableValue? { get } +} + +public typealias VariableDictionary = [String: VariableValue] + +extension VariableDictionary: VariableProvider {} diff --git a/Sources/ScreamURITemplate/VariableValue.swift b/Sources/ScreamURITemplate/VariableValue.swift new file mode 100644 index 0000000..de945d7 --- /dev/null +++ b/Sources/ScreamURITemplate/VariableValue.swift @@ -0,0 +1,79 @@ +// Copyright 2018-2023 Alex Deem +// +// 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. + +import Foundation + +public enum TypedVariableValue { + public typealias AssociativeArrayElement = (key: String, value: String) + + case string(String) + case list([String]) + case associativeArray([AssociativeArrayElement]) +} + +public protocol VariableValue { + func asTypedVariableValue() -> TypedVariableValue? +} + +public protocol StringVariableValue: VariableValue { + func asStringVariableValue() -> String +} + +public extension StringVariableValue { + func asTypedVariableValue() -> TypedVariableValue? { + .string(asStringVariableValue()) + } +} + +extension [StringVariableValue]: VariableValue { + public func asTypedVariableValue() -> TypedVariableValue? { + .list(map { $0.asStringVariableValue() }) + } +} + +extension KeyValuePairs: VariableValue { + public func asTypedVariableValue() -> TypedVariableValue? { + .associativeArray(map { ($0, $1.asStringVariableValue()) }) + } +} + +extension [String: StringVariableValue]: VariableValue { + public func asTypedVariableValue() -> TypedVariableValue? { + .associativeArray(map { ($0, $1.asStringVariableValue()) }) + } +} + +public extension LosslessStringConvertible { + func asStringVariableValue() -> String { + description + } +} + +extension String: StringVariableValue {} +extension Bool: StringVariableValue {} +extension Character: StringVariableValue {} +extension Double: StringVariableValue {} +extension Float: StringVariableValue {} +extension Int: StringVariableValue {} +extension Int16: StringVariableValue {} +extension Int32: StringVariableValue {} +extension Int64: StringVariableValue {} +extension Int8: StringVariableValue {} +extension Substring: StringVariableValue {} +extension UInt: StringVariableValue {} +extension UInt16: StringVariableValue {} +extension UInt32: StringVariableValue {} +extension UInt64: StringVariableValue {} +extension UInt8: StringVariableValue {} +extension Unicode.Scalar: StringVariableValue {} diff --git a/Tests/ScreamURITemplateTests/TestFileTests.swift b/Tests/ScreamURITemplateTests/TestFileTests.swift index 781456c..c008a8f 100644 --- a/Tests/ScreamURITemplateTests/TestFileTests.swift +++ b/Tests/ScreamURITemplateTests/TestFileTests.swift @@ -17,7 +17,7 @@ import XCTest class TestFileTests: XCTestCase { private var templateString: String! - private var variables: [String: VariableValue]! + private var variables: VariableDictionary! private var acceptableExpansions: [String]! private var failPosition: Int? private var failReason: String? diff --git a/Tests/ScreamURITemplateTests/TestModels.swift b/Tests/ScreamURITemplateTests/TestModels.swift index 7352989..7b7feab 100644 --- a/Tests/ScreamURITemplateTests/TestModels.swift +++ b/Tests/ScreamURITemplateTests/TestModels.swift @@ -26,7 +26,7 @@ private struct TestGroupDecodable: Decodable { public struct TestGroup { public let name: String public let level: Int? - public let variables: [String: VariableValue] + public let variables: VariableDictionary public let testcases: [TestCase] } @@ -38,8 +38,25 @@ public struct TestCase { public let failReason: String? } -extension JSONValue { - func toVariableValue() -> VariableValue? { +extension JSONValue: VariableValue { + public func asTypedVariableValue() -> ScreamURITemplate.TypedVariableValue? { + switch self { + case let .int(int): + return int.asTypedVariableValue() + case let .double(double): + return double.asTypedVariableValue() + case let .string(string): + return string.asTypedVariableValue() + case let .object(object): + return object.compactMapValues { $0.asString() }.asTypedVariableValue() + case let .array(array): + return array.compactMap { $0.asString() }.asTypedVariableValue() + case .null, .bool: + return nil + } + } + + private func asString() -> String? { switch self { case let .int(int): return String(int) @@ -47,26 +64,7 @@ extension JSONValue { return String(double) case let .string(string): return string - case let .object(object): - return object.mapValues { element -> String? in - switch element { - case let .string(string): - return string - default: - return nil - } - }.filter { $0.value != nil } - .mapValues { $0! } - case let .array(array): - return array.compactMap { element -> String? in - switch element { - case let .string(string): - return string - default: - return nil - } - } - default: + case .null, .bool, .object, .array: return nil } } @@ -143,15 +141,10 @@ public func parseTestFile(URL: URL) -> [TestGroup]? { } return testCollection.map { testGroupName, testGroupData in - let variables = testGroupData.variables.mapValues { element in - return element.toVariableValue() - }.filter { return $0.value != nil } - .mapValues { return $0! } - let testcases = testGroupData.testcases.compactMap { element in return TestCase(element) } - return TestGroup(name: testGroupName, level: testGroupData.level, variables: variables, testcases: testcases) + return TestGroup(name: testGroupName, level: testGroupData.level, variables: testGroupData.variables, testcases: testcases) } } diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index e6de45d..bc54bb3 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -15,7 +15,73 @@ import ScreamURITemplate import XCTest +struct TestVariableProvider: VariableProvider { + subscript(_ key: String) -> VariableValue? { + return "_\(key)_" + } +} + class Tests: XCTestCase { + func testVariableProvider() throws { + let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let urlString = try template.process(variables: TestVariableProvider()) + XCTAssertEqual(urlString, "https://api.github.com/repos/_owner_/_repo_/collaborators/_username_") + } + + func testStringStringDictionary() throws { + let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let variables = ["owner": "SwiftScream", + "repo": "URITemplate", + "username": "alexdeem"] + let urlString = try template.process(variables: variables) + XCTAssertEqual(urlString, "https://api.github.com/repos/SwiftScream/URITemplate/collaborators/alexdeem") + } + + func testVariableDictionaryPlain() throws { + let template: URITemplate = "https://api.example.com/{string}/{int}/{bool}" + let variables: VariableDictionary = [ + "string": "SwiftScream", + "int": 42, + "bool": true, + ] + let urlString = try template.process(variables: variables) + XCTAssertEqual(urlString, "https://api.example.com/SwiftScream/42/true") + } + + func testVariableDictionaryList() throws { + let template: URITemplate = "https://api.example.com/{list}" + let variables: VariableDictionary = [ + "list": ["SwiftScream", 42, true], + ] + let urlString = try template.process(variables: variables) + XCTAssertEqual(urlString, "https://api.example.com/SwiftScream,42,true") + } + + func testVariableDictionaryAssocList() throws { + let template: URITemplate = "https://api.example.com/path{?unordered*,ordered*}" + let variables: VariableDictionary = [ + "unordered": [ + "b": 42, + "a": "A", + "c": true, + ], + "ordered": [ + "b2": 42, + "a2": "A", + "c2": true, + ] as KeyValuePairs, + ] + let urlString = try template.process(variables: variables) + XCTAssertTrue([ + "https://api.example.com/path?a=A&b=42&c=true&b2=42&a2=A&c2=true", + "https://api.example.com/path?a=A&c=true&b=42&b2=42&a2=A&c2=true", + "https://api.example.com/path?b=42&a=A&c=true&b2=42&a2=A&c2=true", + "https://api.example.com/path?b=42&c=true&a=A&b2=42&a2=A&c2=true", + "https://api.example.com/path?c=true&a=A&b=42&b2=42&a2=A&c2=true", + "https://api.example.com/path?c=true&b=42&a=A&b2=42&a2=A&c2=true", + ].contains(urlString)) + } + func testSendable() { let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" let sendable = template as Sendable From 42b579070fa4176a05c201d7d5159e475b8e5cef Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sat, 8 Jun 2024 22:28:16 +1000 Subject: [PATCH 19/36] Support UUID as a StringVariableValue --- Sources/ScreamURITemplate/VariableValue.swift | 6 ++++++ Tests/ScreamURITemplateTests/Tests.swift | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/Sources/ScreamURITemplate/VariableValue.swift b/Sources/ScreamURITemplate/VariableValue.swift index de945d7..dd94079 100644 --- a/Sources/ScreamURITemplate/VariableValue.swift +++ b/Sources/ScreamURITemplate/VariableValue.swift @@ -77,3 +77,9 @@ extension UInt32: StringVariableValue {} extension UInt64: StringVariableValue {} extension UInt8: StringVariableValue {} extension Unicode.Scalar: StringVariableValue {} + +extension UUID: StringVariableValue { + public func asStringVariableValue() -> String { + uuidString + } +} diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index bc54bb3..2c093d0 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -82,6 +82,15 @@ class Tests: XCTestCase { ].contains(urlString)) } + func testUUIDVariable() throws { + let template: URITemplate = "https://api.example.com/{id}" + let variables: VariableDictionary = [ + "id": UUID(uuidString: "1740A1A9-B3AD-4AE9-954B-918CEDE95285")!, + ] + let urlString = try template.process(variables: variables) + XCTAssertEqual(urlString, "https://api.example.com/1740A1A9-B3AD-4AE9-954B-918CEDE95285") + } + func testSendable() { let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" let sendable = template as Sendable From fa2dcf56cdb27a3240b66c1948fd5dd4aa8ab5df Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 9 Jun 2024 22:17:42 +1000 Subject: [PATCH 20/36] Sort variables provided as a dictionary for stability --- Sources/ScreamURITemplate/VariableValue.swift | 2 +- Tests/ScreamURITemplateTests/TestModels.swift | 13 +------------ Tests/ScreamURITemplateTests/Tests.swift | 9 +-------- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/Sources/ScreamURITemplate/VariableValue.swift b/Sources/ScreamURITemplate/VariableValue.swift index dd94079..a2f05b6 100644 --- a/Sources/ScreamURITemplate/VariableValue.swift +++ b/Sources/ScreamURITemplate/VariableValue.swift @@ -50,7 +50,7 @@ extension KeyValuePairs: VariableValue { extension [String: StringVariableValue]: VariableValue { public func asTypedVariableValue() -> TypedVariableValue? { - .associativeArray(map { ($0, $1.asStringVariableValue()) }) + .associativeArray(map { ($0, $1.asStringVariableValue()) }.sorted { $0.0 < $1.0 }) } } diff --git a/Tests/ScreamURITemplateTests/TestModels.swift b/Tests/ScreamURITemplateTests/TestModels.swift index 7b7feab..b3ac1d2 100644 --- a/Tests/ScreamURITemplateTests/TestModels.swift +++ b/Tests/ScreamURITemplateTests/TestModels.swift @@ -84,18 +84,7 @@ extension TestCase { let expansionsData = data[1] switch expansionsData { case let .string(string): - // HACK: ensure the tests support alternate ordering for dictionary explode tests - // A PR has been raised to add support for the alternate ordering https://github.com/uri-templates/uritemplate-test/pull/58 - switch string { - case "key1,val1%2F,key2,val2%2F": - acceptableExpansions = [string, "key2,val2%2F,key1,val1%2F"] - case "#key1,val1%2F,key2,val2%2F": - acceptableExpansions = [string, "#key2,val2%2F,key1,val1%2F"] - case "key1,val1%252F,key2,val2%252F": - acceptableExpansions = [string, "key2,val2%252F,key1,val1%252F"] - default: - acceptableExpansions = [string] - } + acceptableExpansions = [string] shouldFail = false case let .array(array): acceptableExpansions = array.compactMap { value in diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index 2c093d0..681c762 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -72,14 +72,7 @@ class Tests: XCTestCase { ] as KeyValuePairs, ] let urlString = try template.process(variables: variables) - XCTAssertTrue([ - "https://api.example.com/path?a=A&b=42&c=true&b2=42&a2=A&c2=true", - "https://api.example.com/path?a=A&c=true&b=42&b2=42&a2=A&c2=true", - "https://api.example.com/path?b=42&a=A&c=true&b2=42&a2=A&c2=true", - "https://api.example.com/path?b=42&c=true&a=A&b2=42&a2=A&c2=true", - "https://api.example.com/path?c=true&a=A&b=42&b2=42&a2=A&c2=true", - "https://api.example.com/path?c=true&b=42&a=A&b2=42&a2=A&c2=true", - ].contains(urlString)) + XCTAssertEqual("https://api.example.com/path?a=A&b=42&c=true&b2=42&a2=A&c2=true", urlString) } func testUUIDVariable() throws { From c63916df3aff04b2c56621deafabf11587c45af3 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 9 Jun 2024 22:32:36 +1000 Subject: [PATCH 21/36] Add SequenceVariableProvider --- .../ScreamURITemplate/VariableProvider.swift | 17 +++++++++++++++++ Tests/ScreamURITemplateTests/Tests.swift | 16 +++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Sources/ScreamURITemplate/VariableProvider.swift b/Sources/ScreamURITemplate/VariableProvider.swift index 739d94b..2467e9c 100644 --- a/Sources/ScreamURITemplate/VariableProvider.swift +++ b/Sources/ScreamURITemplate/VariableProvider.swift @@ -20,3 +20,20 @@ public protocol VariableProvider { public typealias VariableDictionary = [String: VariableValue] extension VariableDictionary: VariableProvider {} + +public struct SequenceVariableProvider: VariableProvider, ExpressibleByArrayLiteral { + let sequence: any Sequence + + public init(arrayLiteral elements: VariableProvider...) { + sequence = elements + } + + public subscript(_ key: String) -> VariableValue? { + for provider in sequence { + if let value = provider[key] { + return value + } + } + return nil + } +} diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index 681c762..630a4ad 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -17,7 +17,12 @@ import XCTest struct TestVariableProvider: VariableProvider { subscript(_ key: String) -> VariableValue? { - return "_\(key)_" + switch key { + case "missing": + return nil + default: + return "_\(key)_" + } } } @@ -28,6 +33,15 @@ class Tests: XCTestCase { XCTAssertEqual(urlString, "https://api.github.com/repos/_owner_/_repo_/collaborators/_username_") } + func testSequenceVariableProvider() throws { + let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}{missing}" + let urlString = try template.process(variables: [ + ["owner": "SwiftScream"], + TestVariableProvider(), + ] as SequenceVariableProvider) + XCTAssertEqual(urlString, "https://api.github.com/repos/SwiftScream/_repo_/collaborators/_username_") + } + func testStringStringDictionary() throws { let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" let variables = ["owner": "SwiftScream", From c40686d07d350f835a1c10d0d440d924e8d605a5 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Mon, 10 Jun 2024 22:07:58 +1000 Subject: [PATCH 22/36] Refactor variable expansion This change eliminates a swiftlint cyclomatic_complexity issue --- .../Internal/Components.swift | 24 +------------ .../Internal/ValueFormatting.swift | 35 ++++++++++++++++--- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/Components.swift b/Sources/ScreamURITemplate/Internal/Components.swift index ccd66fb..9e63fbc 100644 --- a/Sources/ScreamURITemplate/Internal/Components.swift +++ b/Sources/ScreamURITemplate/Internal/Components.swift @@ -64,7 +64,6 @@ struct ExpressionComponent: Component { self.templatePosition = templatePosition } - // swiftlint:disable:next cyclomatic_complexity func expand(variables: VariableProvider) throws -> String { let configuration = expressionOperator.expansionConfiguration() let expansions = try variableList.compactMap { variableSpec -> String? in @@ -72,28 +71,7 @@ struct ExpressionComponent: Component { return nil } do { - switch value { - case let .string(plainValue): - return try plainValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) - case let .list(arrayValue): - switch variableSpec.modifier { - case .prefix: - throw FormatError.failure(reason: "Prefix operator can only be applied to string") - case .explode: - return try arrayValue.explodeForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) - case .none: - return try arrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) - } - case let .associativeArray(associativeArrayValue): - switch variableSpec.modifier { - case .prefix: - throw FormatError.failure(reason: "Prefix operator can only be applied to string") - case .explode: - return try associativeArrayValue.explodeForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) - case .none: - return try associativeArrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) - } - } + return try value.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) } catch let FormatError.failure(reason) { throw URITemplate.Error.expansionFailure(position: templatePosition, reason: "Failed expanding variable \"\(variableSpec.name)\": \(reason)") } diff --git a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift index 1915440..6619176 100644 --- a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift +++ b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift @@ -18,7 +18,34 @@ enum FormatError: Error { case failure(reason: String) } -func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: CharacterSet, allowPercentEncodedTriplets: Bool) throws -> String { +extension TypedVariableValue { + func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration configuration: ExpansionConfiguration) throws -> String? { + switch self { + case let .string(plainValue): + return try plainValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + case let .list(arrayValue): + switch variableSpec.modifier { + case .prefix: + throw FormatError.failure(reason: "Prefix operator can only be applied to string") + case .explode: + return try arrayValue.explodeForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + case .none: + return try arrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + } + case let .associativeArray(associativeArrayValue): + switch variableSpec.modifier { + case .prefix: + throw FormatError.failure(reason: "Prefix operator can only be applied to string") + case .explode: + return try associativeArrayValue.explodeForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + case .none: + return try associativeArrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) + } + } + } +} + +private func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: CharacterSet, allowPercentEncodedTriplets: Bool) throws -> String { guard var encoded = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) else { throw FormatError.failure(reason: "Percent Encoding Failed") } @@ -45,7 +72,7 @@ func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: Ch return encoded } -extension StringProtocol { +private extension StringProtocol { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String { let modifiedValue = if let prefixLength = variableSpec.prefixLength() { String(prefix(prefixLength)) @@ -63,7 +90,7 @@ extension StringProtocol { } } -extension Array where Element: StringProtocol { +private extension Array where Element: StringProtocol { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? { let separator = "," let encodedExpansions = try map { element -> String in @@ -101,7 +128,7 @@ extension Array where Element: StringProtocol { } } -extension [TypedVariableValue.AssociativeArrayElement] { +private extension [TypedVariableValue.AssociativeArrayElement] { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? { let encodedExpansions = try map { key, value -> String in let encodedKey = try percentEncode(string: String(key), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) From b0d833a2f6f7b25fbfc73f97e0131539312037cd Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Mon, 10 Jun 2024 22:10:55 +1000 Subject: [PATCH 23/36] Fix spelling error --- .../Internal/ExpansionConfiguration.swift | 2 +- .../Internal/ExpressionOperator.swift | 16 ++++++++-------- .../Internal/ValueFormatting.swift | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift b/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift index fb2fad7..bbe038a 100644 --- a/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift +++ b/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift @@ -20,5 +20,5 @@ struct ExpansionConfiguration { let prefix: String? let separator: String let named: Bool - let omittOrphanedEquals: Bool + let omitOrphanedEquals: Bool } diff --git a/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift b/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift index f48133f..6aae900 100644 --- a/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift +++ b/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift @@ -33,56 +33,56 @@ enum ExpressionOperator: Unicode.Scalar { prefix: nil, separator: ",", named: false, - omittOrphanedEquals: false) + omitOrphanedEquals: false) case .reserved: return ExpansionConfiguration(percentEncodingAllowedCharacterSet: reservedAndUnreservedCharacterSet, allowPercentEncodedTriplets: true, prefix: nil, separator: ",", named: false, - omittOrphanedEquals: false) + omitOrphanedEquals: false) case .fragment: return ExpansionConfiguration(percentEncodingAllowedCharacterSet: reservedAndUnreservedCharacterSet, allowPercentEncodedTriplets: true, prefix: "#", separator: ",", named: false, - omittOrphanedEquals: false) + omitOrphanedEquals: false) case .label: return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, allowPercentEncodedTriplets: false, prefix: ".", separator: ".", named: false, - omittOrphanedEquals: false) + omitOrphanedEquals: false) case .pathSegment: return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, allowPercentEncodedTriplets: false, prefix: "/", separator: "/", named: false, - omittOrphanedEquals: false) + omitOrphanedEquals: false) case .pathStyle: return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, allowPercentEncodedTriplets: false, prefix: ";", separator: ";", named: true, - omittOrphanedEquals: true) + omitOrphanedEquals: true) case .query: return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, allowPercentEncodedTriplets: false, prefix: "?", separator: "&", named: true, - omittOrphanedEquals: false) + omitOrphanedEquals: false) case .queryContinuation: return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, allowPercentEncodedTriplets: false, prefix: "&", separator: "&", named: true, - omittOrphanedEquals: false) + omitOrphanedEquals: false) } } } diff --git a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift index 6619176..61b6266 100644 --- a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift +++ b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift @@ -81,7 +81,7 @@ private extension StringProtocol { } let encodedExpansion = try percentEncode(string: modifiedValue, withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) if expansionConfiguration.named { - if encodedExpansion.isEmpty && expansionConfiguration.omittOrphanedEquals { + if encodedExpansion.isEmpty && expansionConfiguration.omitOrphanedEquals { return String(variableSpec.name) } return "\(variableSpec.name)=\(encodedExpansion)" @@ -101,7 +101,7 @@ private extension Array where Element: StringProtocol { } let expansion = encodedExpansions.joined(separator: separator) if expansionConfiguration.named { - if expansion.isEmpty && expansionConfiguration.omittOrphanedEquals { + if expansion.isEmpty && expansionConfiguration.omitOrphanedEquals { return String(variableSpec.name) } return "\(variableSpec.name)=\(expansion)" @@ -114,7 +114,7 @@ private extension Array where Element: StringProtocol { let encodedExpansions = try map { element -> String in let encodedElement = try percentEncode(string: String(element), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) if expansionConfiguration.named { - if encodedElement.isEmpty && expansionConfiguration.omittOrphanedEquals { + if encodedElement.isEmpty && expansionConfiguration.omitOrphanedEquals { return String(variableSpec.name) } return "\(variableSpec.name)=\(encodedElement)" @@ -150,7 +150,7 @@ private extension [TypedVariableValue.AssociativeArrayElement] { let encodedExpansions = try map { key, value -> String in let encodedKey = try percentEncode(string: String(key), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) let encodedValue = try percentEncode(string: String(value), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) - if expansionConfiguration.named && encodedValue.isEmpty && expansionConfiguration.omittOrphanedEquals { + if expansionConfiguration.named && encodedValue.isEmpty && expansionConfiguration.omitOrphanedEquals { return String(variableSpec.name) } return "\(encodedKey)=\(encodedValue)" From cac20ba0d6ac7fdf710824a783f66ede2b2ff483 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 12 Jun 2024 22:13:06 +1000 Subject: [PATCH 24/36] Refine swiftlint config --- .swiftlint.yml | 6 +++++- Sources/ScreamURITemplateExample/main.swift | 8 +++++--- Tests/ScreamURITemplateTests/Tests.swift | 16 ++++++++++------ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index ca1a784..87d4f48 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,6 +1,5 @@ disabled_rules: - line_length - - trailing_comma - redundant_optional_initialization opt_in_rules: @@ -10,3 +9,8 @@ included: excluded: .build +trailing_comma: + mandatory_comma: true + +vertical_whitespace: + max_empty_lines: 2 diff --git a/Sources/ScreamURITemplateExample/main.swift b/Sources/ScreamURITemplateExample/main.swift index 2fcd5d4..7c5870d 100644 --- a/Sources/ScreamURITemplateExample/main.swift +++ b/Sources/ScreamURITemplateExample/main.swift @@ -16,9 +16,11 @@ import Foundation import ScreamURITemplate let template = try URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") -let variables = ["owner": "SwiftScream", - "repo": "URITemplate", - "username": "alexdeem"] +let variables = [ + "owner": "SwiftScream", + "repo": "URITemplate", + "username": "alexdeem", +] let urlString = try template.process(variables: variables) diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index 630a4ad..f093174 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -44,9 +44,11 @@ class Tests: XCTestCase { func testStringStringDictionary() throws { let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let variables = ["owner": "SwiftScream", - "repo": "URITemplate", - "username": "alexdeem"] + let variables = [ + "owner": "SwiftScream", + "repo": "URITemplate", + "username": "alexdeem", + ] let urlString = try template.process(variables: variables) XCTAssertEqual(urlString, "https://api.github.com/repos/SwiftScream/URITemplate/collaborators/alexdeem") } @@ -177,9 +179,11 @@ class Tests: XCTestCase { func testProcessPerformance() { let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let variables = ["owner": "SwiftScream", - "repo": "URITemplate", - "username": "alexdeem"] + let variables = [ + "owner": "SwiftScream", + "repo": "URITemplate", + "username": "alexdeem", + ] measure { for _ in 1...5000 { From 28f4c811e302a96a0103d839b30134279da21675 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 12 Jun 2024 22:13:54 +1000 Subject: [PATCH 25/36] Rename StringVariableValue.asStringVariableValue() -> asString() --- Sources/ScreamURITemplate/VariableValue.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/ScreamURITemplate/VariableValue.swift b/Sources/ScreamURITemplate/VariableValue.swift index a2f05b6..25b0a7f 100644 --- a/Sources/ScreamURITemplate/VariableValue.swift +++ b/Sources/ScreamURITemplate/VariableValue.swift @@ -27,35 +27,35 @@ public protocol VariableValue { } public protocol StringVariableValue: VariableValue { - func asStringVariableValue() -> String + func asString() -> String } public extension StringVariableValue { func asTypedVariableValue() -> TypedVariableValue? { - .string(asStringVariableValue()) + .string(asString()) } } extension [StringVariableValue]: VariableValue { public func asTypedVariableValue() -> TypedVariableValue? { - .list(map { $0.asStringVariableValue() }) + .list(map { $0.asString() }) } } extension KeyValuePairs: VariableValue { public func asTypedVariableValue() -> TypedVariableValue? { - .associativeArray(map { ($0, $1.asStringVariableValue()) }) + .associativeArray(map { ($0, $1.asString()) }) } } extension [String: StringVariableValue]: VariableValue { public func asTypedVariableValue() -> TypedVariableValue? { - .associativeArray(map { ($0, $1.asStringVariableValue()) }.sorted { $0.0 < $1.0 }) + .associativeArray(map { ($0, $1.asString()) }.sorted { $0.0 < $1.0 }) } } public extension LosslessStringConvertible { - func asStringVariableValue() -> String { + func asString() -> String { description } } @@ -79,7 +79,7 @@ extension UInt8: StringVariableValue {} extension Unicode.Scalar: StringVariableValue {} extension UUID: StringVariableValue { - public func asStringVariableValue() -> String { + public func asString() -> String { uuidString } } From f038ca2d0f4cb7553669ed7726375c7637a1be4b Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 09:53:54 +1000 Subject: [PATCH 26/36] Support processing by directly providing TypedVariableValue --- .../Internal/Components.swift | 10 ++--- Sources/ScreamURITemplate/URITemplate.swift | 14 ++++++- .../ScreamURITemplate/VariableProvider.swift | 8 ++++ Tests/ScreamURITemplateTests/Tests.swift | 39 +++++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/Components.swift b/Sources/ScreamURITemplate/Internal/Components.swift index 9e63fbc..a8eae7e 100644 --- a/Sources/ScreamURITemplate/Internal/Components.swift +++ b/Sources/ScreamURITemplate/Internal/Components.swift @@ -17,7 +17,7 @@ import Foundation typealias ComponentBase = Sendable protocol Component: ComponentBase { - func expand(variables: VariableProvider) throws -> String + func expand(variables: TypedVariableProvider) throws -> String var variableNames: [String] { get } } @@ -33,7 +33,7 @@ struct LiteralComponent: Component { literal = string } - func expand(variables _: VariableProvider) throws -> String { + func expand(variables _: TypedVariableProvider) throws -> String { let expansion = String(literal) guard let encodedExpansion = expansion.addingPercentEncoding(withAllowedCharacters: reservedAndUnreservedCharacterSet) else { throw URITemplate.Error.expansionFailure(position: literal.startIndex, reason: "Percent Encoding Failed") @@ -48,7 +48,7 @@ struct LiteralPercentEncodedTripletComponent: Component { literal = string } - func expand(variables _: VariableProvider) throws -> String { + func expand(variables _: TypedVariableProvider) throws -> String { return String(literal) } } @@ -64,10 +64,10 @@ struct ExpressionComponent: Component { self.templatePosition = templatePosition } - func expand(variables: VariableProvider) throws -> String { + func expand(variables: TypedVariableProvider) throws -> String { let configuration = expressionOperator.expansionConfiguration() let expansions = try variableList.compactMap { variableSpec -> String? in - guard let value = variables[String(variableSpec.name)]?.asTypedVariableValue() else { + guard let value = variables[String(variableSpec.name)] else { return nil } do { diff --git a/Sources/ScreamURITemplate/URITemplate.swift b/Sources/ScreamURITemplate/URITemplate.swift index 70175c5..770f348 100644 --- a/Sources/ScreamURITemplate/URITemplate.swift +++ b/Sources/ScreamURITemplate/URITemplate.swift @@ -33,7 +33,7 @@ public struct URITemplate { self.components = components } - public func process(variables: VariableProvider) throws -> String { + public func process(variables: TypedVariableProvider) throws -> String { var result = "" for component in components { result += try component.expand(variables: variables) @@ -41,6 +41,18 @@ public struct URITemplate { return result } + public func process(variables: VariableProvider) throws -> String { + struct TypedVariableProviderWrapper: TypedVariableProvider { + let variables: VariableProvider + + subscript(_ key: String) -> TypedVariableValue? { + return variables[key]?.asTypedVariableValue() + } + } + + return try process(variables: TypedVariableProviderWrapper(variables: variables)) + } + public func process(variables: [String: String]) throws -> String { return try process(variables: variables as VariableDictionary) } diff --git a/Sources/ScreamURITemplate/VariableProvider.swift b/Sources/ScreamURITemplate/VariableProvider.swift index 2467e9c..17c4cf6 100644 --- a/Sources/ScreamURITemplate/VariableProvider.swift +++ b/Sources/ScreamURITemplate/VariableProvider.swift @@ -17,10 +17,18 @@ public protocol VariableProvider { subscript(_: String) -> VariableValue? { get } } +public protocol TypedVariableProvider { + subscript(_: String) -> TypedVariableValue? { get } +} + public typealias VariableDictionary = [String: VariableValue] extension VariableDictionary: VariableProvider {} +public typealias TypedVariableDictionary = [String: TypedVariableValue] + +extension TypedVariableDictionary: TypedVariableProvider {} + public struct SequenceVariableProvider: VariableProvider, ExpressibleByArrayLiteral { let sequence: any Sequence diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index f093174..281ab24 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -100,6 +100,45 @@ class Tests: XCTestCase { XCTAssertEqual(urlString, "https://api.example.com/1740A1A9-B3AD-4AE9-954B-918CEDE95285") } + func testVariableDictionaryVariousTypes() throws { + let template: URITemplate = "https://api.example.com{/string,int,bool,list}{?unordered*,ordered*}" + let variables: VariableDictionary = [ + "string": "SwiftScream", + "int": 42, + "bool": true, + "list": ["SwiftScream", 42, true], + "unordered": [ + "b": 42, + "a": "A", + "c": true, + ], + "ordered": [ + "b2": 42, + "a2": "A", + "c2": true, + ] as KeyValuePairs, + ] + let urlString = try template.process(variables: variables) + XCTAssertEqual("https://api.example.com/SwiftScream/42/true/SwiftScream,42,true?a=A&b=42&c=true&b2=42&a2=A&c2=true", urlString) + } + + func testTypedVariableDictionaryVariousTypes() throws { + let template: URITemplate = "https://api.example.com{/string,int,bool,list}{?unordered*,ordered*}" + let variables: TypedVariableDictionary = [ + "string": .string("SwiftScream"), + "int": .string("42"), + "bool": .string("true"), + "list": .list(["SwiftScream", "42", "true"]), + "ordered": .associativeArray([ + ("b", "42"), + ("a", "A"), + ("c", "true"), + ]), + ] + let urlString = try template.process(variables: variables) + XCTAssertEqual("https://api.example.com/SwiftScream/42/true/SwiftScream,42,true?b=42&a=A&c=true", urlString) + } + func testSendable() { let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" let sendable = template as Sendable From 1ff37d925c04e99acdf29b7ef097164a5ea17b42 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 12 Jun 2024 23:10:24 +1000 Subject: [PATCH 27/36] Remove AssociativeArrayElement typealias It added little value, and confuses documentation --- Sources/ScreamURITemplate/Internal/ValueFormatting.swift | 2 +- Sources/ScreamURITemplate/VariableValue.swift | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift index 61b6266..9b31efb 100644 --- a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift +++ b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift @@ -128,7 +128,7 @@ private extension Array where Element: StringProtocol { } } -private extension [TypedVariableValue.AssociativeArrayElement] { +private extension [(key: String, value: String)] { func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? { let encodedExpansions = try map { key, value -> String in let encodedKey = try percentEncode(string: String(key), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets) diff --git a/Sources/ScreamURITemplate/VariableValue.swift b/Sources/ScreamURITemplate/VariableValue.swift index 25b0a7f..a4e7314 100644 --- a/Sources/ScreamURITemplate/VariableValue.swift +++ b/Sources/ScreamURITemplate/VariableValue.swift @@ -15,11 +15,9 @@ import Foundation public enum TypedVariableValue { - public typealias AssociativeArrayElement = (key: String, value: String) - case string(String) case list([String]) - case associativeArray([AssociativeArrayElement]) + case associativeArray([(key: String, value: String)]) } public protocol VariableValue { From 4c2057230a269ff2762b8e17d4ff2124450dd155 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 12 Jun 2024 23:40:35 +1000 Subject: [PATCH 28/36] Correct access modifiers --- .../ScreamURITemplate/Internal/Scanner.swift | 6 ++--- Tests/ScreamURITemplateTests/TestModels.swift | 24 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/Scanner.swift b/Sources/ScreamURITemplate/Internal/Scanner.swift index 4bd2d02..331a80d 100644 --- a/Sources/ScreamURITemplate/Internal/Scanner.swift +++ b/Sources/ScreamURITemplate/Internal/Scanner.swift @@ -23,17 +23,17 @@ struct Scanner { let unicodeScalars: String.UnicodeScalarView var currentIndex: String.Index - public init(string: String) { + init(string: String) { self.string = string unicodeScalars = string.unicodeScalars currentIndex = string.startIndex } - public var isComplete: Bool { + var isComplete: Bool { return currentIndex >= unicodeScalars.endIndex } - public mutating func scanComponent() throws -> Component { + mutating func scanComponent() throws -> Component { let nextScalar = unicodeScalars[currentIndex] switch nextScalar { diff --git a/Tests/ScreamURITemplateTests/TestModels.swift b/Tests/ScreamURITemplateTests/TestModels.swift index b3ac1d2..15f6776 100644 --- a/Tests/ScreamURITemplateTests/TestModels.swift +++ b/Tests/ScreamURITemplateTests/TestModels.swift @@ -23,19 +23,19 @@ private struct TestGroupDecodable: Decodable { let testcases: [[JSONValue]] } -public struct TestGroup { - public let name: String - public let level: Int? - public let variables: VariableDictionary - public let testcases: [TestCase] +struct TestGroup { + let name: String + let level: Int? + let variables: VariableDictionary + let testcases: [TestCase] } -public struct TestCase { - public let template: String - public let acceptableExpansions: [String] - public let shouldFail: Bool - public let failPosition: Int? - public let failReason: String? +struct TestCase { + let template: String + let acceptableExpansions: [String] + let shouldFail: Bool + let failPosition: Int? + let failReason: String? } extension JSONValue: VariableValue { @@ -122,7 +122,7 @@ extension TestCase { } } -public func parseTestFile(URL: URL) -> [TestGroup]? { +func parseTestFile(URL: URL) -> [TestGroup]? { guard let testData = try? Data(contentsOf: URL), let testCollection = try? JSONDecoder().decode(TestFile.self, from: testData) else { print("Failed to decode test file \(URL)") From f62add56f27f408dd0604b365ef0ec278f5b6063 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 08:22:51 +1000 Subject: [PATCH 29/36] Specify parameter name --- Sources/ScreamURITemplate/VariableProvider.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ScreamURITemplate/VariableProvider.swift b/Sources/ScreamURITemplate/VariableProvider.swift index 17c4cf6..7a18efc 100644 --- a/Sources/ScreamURITemplate/VariableProvider.swift +++ b/Sources/ScreamURITemplate/VariableProvider.swift @@ -36,9 +36,9 @@ public struct SequenceVariableProvider: VariableProvider, ExpressibleByArrayLite sequence = elements } - public subscript(_ key: String) -> VariableValue? { + public subscript(_ name: String) -> VariableValue? { for provider in sequence { - if let value = provider[key] { + if let value = provider[name] { return value } } From 5640eae01f98cc6d695ddebcbe9b2e1511a19eca Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 08:23:05 +1000 Subject: [PATCH 30/36] Add sequence initialiser --- Sources/ScreamURITemplate/VariableProvider.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/ScreamURITemplate/VariableProvider.swift b/Sources/ScreamURITemplate/VariableProvider.swift index 7a18efc..fe1677b 100644 --- a/Sources/ScreamURITemplate/VariableProvider.swift +++ b/Sources/ScreamURITemplate/VariableProvider.swift @@ -32,8 +32,12 @@ extension TypedVariableDictionary: TypedVariableProvider {} public struct SequenceVariableProvider: VariableProvider, ExpressibleByArrayLiteral { let sequence: any Sequence + public init(sequence: any Sequence) { + self.sequence = sequence + } + public init(arrayLiteral elements: VariableProvider...) { - sequence = elements + self.init(sequence: elements) } public subscript(_ name: String) -> VariableValue? { From 9b8d0bea9dd9e1db6b3ad56fccd4cb8d32663089 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Wed, 20 Dec 2023 23:28:32 +1100 Subject: [PATCH 31/36] add dependency on swift-docc-plugin --- Package.resolved | 25 +++++++++++++++++++++++++ Package.swift | 1 + 2 files changed, 26 insertions(+) create mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..9ad5a83 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftDocCPlugin", + "repositoryURL": "https://github.com/apple/swift-docc-plugin", + "state": { + "branch": null, + "revision": "26ac5758409154cc448d7ab82389c520fa8a8247", + "version": "1.3.0" + } + }, + { + "package": "SymbolKit", + "repositoryURL": "https://github.com/apple/swift-docc-symbolkit", + "state": { + "branch": null, + "revision": "b45d1f2ed151d057b54504d653e0da5552844e34", + "version": "1.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 69d682c..5006247 100644 --- a/Package.swift +++ b/Package.swift @@ -10,6 +10,7 @@ let package = Package( targets: ["ScreamURITemplate"]), ], dependencies: [ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ .target( From 9ff4d59912ce088f8382515eae9618365bb85981 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 22:49:28 +1000 Subject: [PATCH 32/36] Add docc documentation comments --- Sources/ScreamURITemplate/URITemplate.swift | 33 +++++++++++++++++++ .../ScreamURITemplate/VariableProvider.swift | 26 +++++++++++++++ Sources/ScreamURITemplate/VariableValue.swift | 25 ++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/Sources/ScreamURITemplate/URITemplate.swift b/Sources/ScreamURITemplate/URITemplate.swift index 770f348..c1e309b 100644 --- a/Sources/ScreamURITemplate/URITemplate.swift +++ b/Sources/ScreamURITemplate/URITemplate.swift @@ -14,15 +14,23 @@ import Foundation +/// An [RFC6570](https://tools.ietf.org/html/rfc6570) URI Template public struct URITemplate { + /// An error that may be thrown when parsing or processing a template public enum Error: Swift.Error { + /// Represents an error parsing a string into a URI Template case malformedTemplate(position: String.Index, reason: String) + /// Represents an error processing a template case expansionFailure(position: String.Index, reason: String) } private let string: String private let components: [Component] + /// Initializes a URITemplate from a string + /// - Parameter string: the string representation of the URI Template + /// + /// - Throws: `URITemplate.Error.malformedTemplate` if the string is not a valid URI Template public init(string: String) throws { var components: [Component] = [] var scanner = Scanner(string: string) @@ -33,6 +41,12 @@ public struct URITemplate { self.components = components } + /// Process a URI Template with the specified variables + /// - Parameter variables: A ``TypedVariableProvider`` that can provide values for the templates variables + /// + /// - Returns: The result of processing the template + /// + /// - Throws: `URITemplate.Error.expansionFailure` if an error occurs processing the template public func process(variables: TypedVariableProvider) throws -> String { var result = "" for component in components { @@ -41,6 +55,15 @@ public struct URITemplate { return result } + /// Process a URI Template with the specified variables + /// + /// This method allows for specifying variables in a more ergonomic manner compared to using ``TypedVariableValue`` directly + /// + /// - Parameter variables: A ``VariableProvider`` that can provide values for the templates variables + /// + /// - Returns: The result of processing the template + /// + /// - Throws: `URITemplate.Error.expansionFailure` if an error occurs processing the template public func process(variables: VariableProvider) throws -> String { struct TypedVariableProviderWrapper: TypedVariableProvider { let variables: VariableProvider @@ -53,10 +76,20 @@ public struct URITemplate { return try process(variables: TypedVariableProviderWrapper(variables: variables)) } + /// Process a URI Template with the specified string variables + /// + /// This method is an override allowing for the special case of string-only variables without needing to typecast + /// + /// - Parameter variables: A [String: String] dictionary representing the variables + /// + /// - Returns: The result of processing the template + /// + /// - Throws: `URITemplate.Error.expansionFailure` if an error occurs processing the template public func process(variables: [String: String]) throws -> String { return try process(variables: variables as VariableDictionary) } + /// An array of all variable names used in the template public var variableNames: [String] { return components.flatMap { component in return component.variableNames diff --git a/Sources/ScreamURITemplate/VariableProvider.swift b/Sources/ScreamURITemplate/VariableProvider.swift index fe1677b..cbf8d0f 100644 --- a/Sources/ScreamURITemplate/VariableProvider.swift +++ b/Sources/ScreamURITemplate/VariableProvider.swift @@ -13,22 +13,48 @@ import Foundation +/// A type that provides variable values to use in template processing +/// +/// This type provides values using ``VariableValue`` which allows for an ergonomic way to provide values. public protocol VariableProvider { + /// Get the ``VariableValue`` for a given variable + /// + /// - Parameters: + /// - _: the name of the variable + /// + /// - Returns: the ``VariableValue`` for the variable, or `nil` if the variable has no value subscript(_: String) -> VariableValue? { get } } +/// A type that provides variable values to use in template processing +/// +/// This type provides values using ``TypedVariableValue`` +/// +/// Consider using ``VariableProvider`` for a more ergonomic way of providing variable values. public protocol TypedVariableProvider { + /// Get the ``TypedVariableValue`` for a given variable + /// + /// - Parameters: + /// - _: the name of the variable + /// + /// - Returns: the ``TypedVariableValue`` for the variable, or `nil` if the variable has no value subscript(_: String) -> TypedVariableValue? { get } } +/// A typealias for the most simple ``VariableProvider`` implementation: `[String: VariableValue]` public typealias VariableDictionary = [String: VariableValue] extension VariableDictionary: VariableProvider {} +/// A typealias for the most simple ``TypedVariableProvider`` implementation: `[String: TypedVariableValue]` public typealias TypedVariableDictionary = [String: TypedVariableValue] extension TypedVariableDictionary: TypedVariableProvider {} +/// An object that aggregates a `Sequence` of ``VariableProvider`` as a single ``VariableProvider`` +/// +/// This object allows using a prioritised sequence of VariableProvider as a single VariableProvider. +/// The first VariableProvider in the sequence that provides a value for a given variable name is the value that is returned. public struct SequenceVariableProvider: VariableProvider, ExpressibleByArrayLiteral { let sequence: any Sequence diff --git a/Sources/ScreamURITemplate/VariableValue.swift b/Sources/ScreamURITemplate/VariableValue.swift index a4e7314..23cea2b 100644 --- a/Sources/ScreamURITemplate/VariableValue.swift +++ b/Sources/ScreamURITemplate/VariableValue.swift @@ -14,21 +14,45 @@ import Foundation +/// The value of a URITemplate variable to use during processing +/// +/// This type represents the value of a variable, as defined by [RFC6570](https://tools.ietf.org/html/rfc6570), to be used in +/// template processing. +/// +/// Variables can be either a string, a list of strings, or an associative array of string key, value pairs. +/// +/// While you can process a template by providing variable values using this type (via ``TypedVariableProvider``) you may find it +/// more ergonomic to provide ``VariableValue`` using ``VariableProvider``, or for simple cases simply `[String: String]` public enum TypedVariableValue { + /// A simple string value case string(String) + /// An ordered list of strings case list([String]) + /// An associative array of string key, value pairs + /// + /// Note that the elements are ordered case associativeArray([(key: String, value: String)]) } +/// A protocol enabling ergonomic expression of variable values +/// +/// Conforming a type to this protocol will enable it to be directly provided as a variable value via ``VariableProvider`` public protocol VariableValue { + /// Converts this value to a TypedVariableValue to be used for template processing func asTypedVariableValue() -> TypedVariableValue? } +/// A protocol enabling ergonomic expression of simple string variable values +/// +/// Conforming a type to this protocol will enable it to be directly provided as a variable value, or as an element in a list or +/// associative array value via ``VariableProvider`` public protocol StringVariableValue: VariableValue { + /// Converts this value to a `String` to be used for template processing func asString() -> String } public extension StringVariableValue { + /// Converts this value to a TypedVariableValue to be used for template processing func asTypedVariableValue() -> TypedVariableValue? { .string(asString()) } @@ -53,6 +77,7 @@ extension [String: StringVariableValue]: VariableValue { } public extension LosslessStringConvertible { + /// Converts this value to a `String` to be used for template processing func asString() -> String { description } From f0cbb075cbb2a0b4d6c1402de43c8d665b21456e Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 22:55:22 +1000 Subject: [PATCH 33/36] Have swiftlint complain about missing documentation --- .swiftlint.yml | 1 + Sources/ScreamURITemplate/URITemplate.swift | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 87d4f48..63ffd9b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -3,6 +3,7 @@ disabled_rules: - redundant_optional_initialization opt_in_rules: + - missing_docs included: diff --git a/Sources/ScreamURITemplate/URITemplate.swift b/Sources/ScreamURITemplate/URITemplate.swift index c1e309b..dbd1398 100644 --- a/Sources/ScreamURITemplate/URITemplate.swift +++ b/Sources/ScreamURITemplate/URITemplate.swift @@ -41,8 +41,8 @@ public struct URITemplate { self.components = components } - /// Process a URI Template with the specified variables - /// - Parameter variables: A ``TypedVariableProvider`` that can provide values for the templates variables + /// Process a URI Template specifying variables with a ``TypedVariableProvider`` + /// - Parameter variables: A ``TypedVariableProvider`` that can provide values for the template variables /// /// - Returns: The result of processing the template /// @@ -55,11 +55,11 @@ public struct URITemplate { return result } - /// Process a URI Template with the specified variables + /// Process a URI Template specifying variables with a ``VariableProvider`` /// /// This method allows for specifying variables in a more ergonomic manner compared to using ``TypedVariableValue`` directly /// - /// - Parameter variables: A ``VariableProvider`` that can provide values for the templates variables + /// - Parameter variables: A ``VariableProvider`` that can provide values for the template variables /// /// - Returns: The result of processing the template /// @@ -76,7 +76,7 @@ public struct URITemplate { return try process(variables: TypedVariableProviderWrapper(variables: variables)) } - /// Process a URI Template with the specified string variables + /// Process a URI Template where the variable values are all of type string /// /// This method is an override allowing for the special case of string-only variables without needing to typecast /// From 8f853a832561f90ea73156d8afed8647aa8cf155 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 22:56:58 +1000 Subject: [PATCH 34/36] Update Copyright Headers --- Sources/ScreamURITemplate/Internal/CharacterSets.swift | 2 +- Sources/ScreamURITemplate/Internal/Components.swift | 2 +- Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift | 2 +- Sources/ScreamURITemplate/Internal/ExpressionOperator.swift | 2 +- Sources/ScreamURITemplate/Internal/Scanner.swift | 2 +- Sources/ScreamURITemplate/Internal/ValueFormatting.swift | 2 +- Sources/ScreamURITemplate/Internal/VariableSpec.swift | 2 +- Sources/ScreamURITemplate/URITemplate.swift | 2 +- Sources/ScreamURITemplate/VariableProvider.swift | 1 + Sources/ScreamURITemplate/VariableValue.swift | 2 +- Sources/ScreamURITemplateExample/main.swift | 2 +- Tests/ScreamURITemplateTests/JSONValue.swift | 2 +- Tests/ScreamURITemplateTests/TestFileTests.swift | 2 +- Tests/ScreamURITemplateTests/TestModels.swift | 2 +- Tests/ScreamURITemplateTests/Tests.swift | 2 +- 15 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/CharacterSets.swift b/Sources/ScreamURITemplate/Internal/CharacterSets.swift index 6c3b1f0..40565cd 100644 --- a/Sources/ScreamURITemplate/Internal/CharacterSets.swift +++ b/Sources/ScreamURITemplate/Internal/CharacterSets.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/Internal/Components.swift b/Sources/ScreamURITemplate/Internal/Components.swift index a8eae7e..4280d4f 100644 --- a/Sources/ScreamURITemplate/Internal/Components.swift +++ b/Sources/ScreamURITemplate/Internal/Components.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift b/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift index bbe038a..5e533c7 100644 --- a/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift +++ b/Sources/ScreamURITemplate/Internal/ExpansionConfiguration.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift b/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift index 6aae900..08d3b53 100644 --- a/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift +++ b/Sources/ScreamURITemplate/Internal/ExpressionOperator.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/Internal/Scanner.swift b/Sources/ScreamURITemplate/Internal/Scanner.swift index 331a80d..24af5cd 100644 --- a/Sources/ScreamURITemplate/Internal/Scanner.swift +++ b/Sources/ScreamURITemplate/Internal/Scanner.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift index 9b31efb..bf6d7bc 100644 --- a/Sources/ScreamURITemplate/Internal/ValueFormatting.swift +++ b/Sources/ScreamURITemplate/Internal/ValueFormatting.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/Internal/VariableSpec.swift b/Sources/ScreamURITemplate/Internal/VariableSpec.swift index 8834570..370edf1 100644 --- a/Sources/ScreamURITemplate/Internal/VariableSpec.swift +++ b/Sources/ScreamURITemplate/Internal/VariableSpec.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/URITemplate.swift b/Sources/ScreamURITemplate/URITemplate.swift index dbd1398..2ec7c13 100644 --- a/Sources/ScreamURITemplate/URITemplate.swift +++ b/Sources/ScreamURITemplate/URITemplate.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/VariableProvider.swift b/Sources/ScreamURITemplate/VariableProvider.swift index cbf8d0f..d969a61 100644 --- a/Sources/ScreamURITemplate/VariableProvider.swift +++ b/Sources/ScreamURITemplate/VariableProvider.swift @@ -1,3 +1,4 @@ +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplate/VariableValue.swift b/Sources/ScreamURITemplate/VariableValue.swift index 23cea2b..a3a66fa 100644 --- a/Sources/ScreamURITemplate/VariableValue.swift +++ b/Sources/ScreamURITemplate/VariableValue.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Sources/ScreamURITemplateExample/main.swift b/Sources/ScreamURITemplateExample/main.swift index 7c5870d..13d8d9d 100644 --- a/Sources/ScreamURITemplateExample/main.swift +++ b/Sources/ScreamURITemplateExample/main.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/ScreamURITemplateTests/JSONValue.swift b/Tests/ScreamURITemplateTests/JSONValue.swift index 16e39eb..13a574b 100644 --- a/Tests/ScreamURITemplateTests/JSONValue.swift +++ b/Tests/ScreamURITemplateTests/JSONValue.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/ScreamURITemplateTests/TestFileTests.swift b/Tests/ScreamURITemplateTests/TestFileTests.swift index c008a8f..f96bdff 100644 --- a/Tests/ScreamURITemplateTests/TestFileTests.swift +++ b/Tests/ScreamURITemplateTests/TestFileTests.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/ScreamURITemplateTests/TestModels.swift b/Tests/ScreamURITemplateTests/TestModels.swift index 15f6776..4cf5a2a 100644 --- a/Tests/ScreamURITemplateTests/TestModels.swift +++ b/Tests/ScreamURITemplateTests/TestModels.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/ScreamURITemplateTests/Tests.swift b/Tests/ScreamURITemplateTests/Tests.swift index 281ab24..a7d8908 100644 --- a/Tests/ScreamURITemplateTests/Tests.swift +++ b/Tests/ScreamURITemplateTests/Tests.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2023 Alex Deem +// Copyright 2018-2024 Alex Deem // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 77b2b8bfdc9c15e8bcfb46d47137eafb755e363d Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 23:28:20 +1000 Subject: [PATCH 35/36] Add SPI manifest --- .spi.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .spi.yml diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 0000000..5ff4ae0 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,4 @@ +version: 1 +builder: + configs: + - documentation_targets: [ScreamURITemplate] \ No newline at end of file From 220a283ae81dcbda035b8ae26e6dd0d02f32380f Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Thu, 13 Jun 2024 23:13:37 +1000 Subject: [PATCH 36/36] 4.0.0 Release Notes --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 276ed26..eac767c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. + +# [4.0.0](https://github.com/SwiftScream/URITemplate/compare/3.1.0...4.0.0) (2024-06-13) + +- Refine interface for specifying variables +- Add docc documentation +- Move to swift 5.9 as minimum supported version + + # [3.1.0](https://github.com/SwiftScream/URITemplate/compare/3.0.1...3.1.0) (2023-01-20)