From 6f5796852e5dee51148c9abe9e6df89a7b9b2d51 Mon Sep 17 00:00:00 2001 From: Alex Deem Date: Sun, 2 Jun 2024 11:12:25 +1000 Subject: [PATCH] WIP improve variable interface --- .../Internal/Components.swift | 17 ++++--- Sources/ScreamURITemplate/URITemplate.swift | 46 +++++++++++++++++-- .../TestFileTests.swift | 2 +- Tests/ScreamURITemplateTests/TestModels.swift | 27 +++++------ 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/Sources/ScreamURITemplate/Internal/Components.swift b/Sources/ScreamURITemplate/Internal/Components.swift index 299cc32..2eeab10 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 { return nil } do { - if let stringValue = value as? String { + switch value { + case let .string(stringValue): return try stringValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) - } else if let arrayValue = value as? [String] { + case let .array(arrayValue): switch variableSpec.modifier { case .prefix: throw FormatError.failure(reason: "Prefix operator can only be applied to string") @@ -83,7 +84,7 @@ struct ExpressionComponent: Component { case .none: return try arrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) } - } else if let dictionaryValue = value as? [String: String] { + case let .dictionary(dictionaryValue): switch variableSpec.modifier { case .prefix: throw FormatError.failure(reason: "Prefix operator can only be applied to string") @@ -92,8 +93,6 @@ struct ExpressionComponent: Component { case .none: return try dictionaryValue.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/URITemplate.swift b/Sources/ScreamURITemplate/URITemplate.swift index 2b8cb34..3ea0505 100644 --- a/Sources/ScreamURITemplate/URITemplate.swift +++ b/Sources/ScreamURITemplate/URITemplate.swift @@ -14,10 +14,42 @@ import Foundation -public protocol VariableValue {} -extension String: VariableValue {} -extension Array: VariableValue where Element: StringProtocol {} -extension Dictionary: VariableValue where Key: StringProtocol, Value: StringProtocol {} +/* + Public interface for variables + */ +public enum VariableValue { + case string(String) + case array([String]) + case dictionary([String: String]) +} + +public protocol VariableProvider { + subscript(_: String) -> VariableValue? { get } +} + +extension [String: VariableValue]: VariableProvider {} + +public protocol VariableValueConvertible { + var asURITemplateVariableValue: VariableValue? { get } +} + +extension String: VariableValueConvertible { + public var asURITemplateVariableValue: VariableValue? { + return .string(self) + } +} + +extension [String]: VariableValueConvertible { + public var asURITemplateVariableValue: VariableValue? { + return .array(self) + } +} + +extension [String: String]: VariableValueConvertible { + public var asURITemplateVariableValue: VariableValue? { + return .dictionary(self) + } +} public struct URITemplate { public enum Error: Swift.Error { @@ -38,7 +70,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 +78,10 @@ public struct URITemplate { return result } + public func process(variables: [String: VariableValueConvertible]) throws -> String { + return try process(variables: variables.compactMapValues { $0.asURITemplateVariableValue }) + } + public var variableNames: [String] { return components.flatMap { component in return component.variableNames diff --git a/Tests/ScreamURITemplateTests/TestFileTests.swift b/Tests/ScreamURITemplateTests/TestFileTests.swift index a38ac7e..9ab48b0 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: [String: VariableValueConvertible]! 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..b507c13 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: [String: VariableValueConvertible] public let testcases: [TestCase] } @@ -38,17 +38,17 @@ public struct TestCase { public let failReason: String? } -extension JSONValue { - func toVariableValue() -> VariableValue? { +extension JSONValue: VariableValueConvertible { + public var asURITemplateVariableValue: ScreamURITemplate.VariableValue? { switch self { case let .int(int): - return String(int) + return .string(String(int)) case let .double(double): - return String(double) + return .string(String(double)) case let .string(string): - return string + return .string(string) case let .object(object): - return object.mapValues { element -> String? in + return .dictionary(object.mapValues { element -> String? in switch element { case let .string(string): return string @@ -56,16 +56,16 @@ extension JSONValue { return nil } }.filter { $0.value != nil } - .mapValues { $0! } + .mapValues { $0! }) case let .array(array): - return array.compactMap { element -> String? in + return .array(array.compactMap { element -> String? in switch element { case let .string(string): return string default: return nil } - } + }) default: return nil } @@ -143,15 +143,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) } }