Skip to content

Commit

Permalink
Leverage Typed Throws
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdeem committed Dec 1, 2024
1 parent b97572b commit 90ad249
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 47 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The error cases contain associated values specifying a string reason for the err
```swift
do {
_ = try URITemplate(string: "https://api.github.com/repos/{}/{repository}")
} catch let error as URITemplate.Error {
} catch {
// error.reason = "Empty Variable Name"
// error.position = 29th character
}
Expand Down
47 changes: 27 additions & 20 deletions Sources/ScreamURITemplate/Internal/Components.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Foundation
typealias ComponentBase = Sendable

protocol Component: ComponentBase {
func expand(variables: TypedVariableProvider) throws -> String
func expand(variables: TypedVariableProvider) throws(URITemplate.Error) -> String
var variableNames: [String] { get }
}

Expand All @@ -33,7 +33,7 @@ struct LiteralComponent: Component {
literal = string
}

func expand(variables _: TypedVariableProvider) throws -> String {
func expand(variables _: TypedVariableProvider) throws(URITemplate.Error) -> String {
let expansion = String(literal)
guard let encodedExpansion = expansion.addingPercentEncoding(withAllowedCharacters: reservedAndUnreservedCharacterSet) else {
throw URITemplate.Error(type: .expansionFailure, position: literal.startIndex, reason: "Percent Encoding Failed")
Expand All @@ -48,7 +48,7 @@ struct LiteralPercentEncodedTripletComponent: Component {
literal = string
}

func expand(variables _: TypedVariableProvider) throws -> String {
func expand(variables _: TypedVariableProvider) throws(URITemplate.Error) -> String {
return String(literal)
}
}
Expand All @@ -64,28 +64,35 @@ struct ExpressionComponent: Component {
self.templatePosition = templatePosition
}

func expand(variables: TypedVariableProvider) throws -> String {
func expand(variables: TypedVariableProvider) throws(URITemplate.Error) -> String {
let configuration = expressionOperator.expansionConfiguration()
let expansions = try variableList.compactMap { variableSpec -> String? in
guard let value = variables[String(variableSpec.name)] else {
return nil
do {
let expansions = try variableList.compactMap { variableSpec throws(URITemplate.Error) -> String? in
guard let value = variables[String(variableSpec.name)] else {
return nil
}
do throws(FormatError) {
return try value.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration)
} catch {
throw URITemplate.Error(type: .expansionFailure, position: templatePosition, reason: "Failed expanding variable \"\(variableSpec.name)\": \(error.reason)")
}
}
do {
return try value.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration)
} catch let error as FormatError {
throw URITemplate.Error(type: .expansionFailure, position: templatePosition, reason: "Failed expanding variable \"\(variableSpec.name)\": \(error.reason)")
}
}

if expansions.count == 0 {
return ""
}
if expansions.count == 0 {
return ""
}

let joinedExpansions = expansions.joined(separator: configuration.separator)
if let prefix = configuration.prefix {
return prefix + joinedExpansions
let joinedExpansions = expansions.joined(separator: configuration.separator)
if let prefix = configuration.prefix {
return prefix + joinedExpansions
}
return joinedExpansions
} catch let error as URITemplate.Error {
throw error
} catch {
// compactMap is not marked up for Typed Throws, the compiler therefore does not know that this is not possible
throw URITemplate.Error(type: .expansionFailure, position: templatePosition, reason: "Failed expanding variable: \(error.localizedDescription)")
}
return joinedExpansions
}

var variableNames: [String] {
Expand Down
16 changes: 8 additions & 8 deletions Sources/ScreamURITemplate/Internal/Scanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct Scanner {
return currentIndex >= unicodeScalars.endIndex
}

mutating func scanComponent() throws -> Component {
mutating func scanComponent() throws(URITemplate.Error) -> Component {
let nextScalar = unicodeScalars[currentIndex]

switch nextScalar {
Expand All @@ -48,7 +48,7 @@ struct Scanner {
}
}

private mutating func scanExpressionComponent() throws -> Component {
private mutating func scanExpressionComponent() throws(URITemplate.Error) -> Component {
assert(unicodeScalars[currentIndex] == "{")
let expressionStartIndex = currentIndex
currentIndex = unicodeScalars.index(after: currentIndex)
Expand All @@ -59,7 +59,7 @@ struct Scanner {
return ExpressionComponent(expressionOperator: expressionOperator, variableList: variableList, templatePosition: expressionStartIndex)
}

private mutating func scanExpressionOperator() throws -> ExpressionOperator {
private mutating func scanExpressionOperator() throws(URITemplate.Error) -> ExpressionOperator {
let expressionOperator: ExpressionOperator
if expressionOperatorCharacterSet.contains(unicodeScalars[currentIndex]) {
guard let `operator` = ExpressionOperator(rawValue: unicodeScalars[currentIndex]) else {
Expand All @@ -73,7 +73,7 @@ struct Scanner {
return expressionOperator
}

private mutating func scanVariableList() throws -> [VariableSpec] {
private mutating func scanVariableList() throws(URITemplate.Error) -> [VariableSpec] {
var variableList: [VariableSpec] = []

var complete = false
Expand Down Expand Up @@ -106,7 +106,7 @@ struct Scanner {
return variableList
}

private mutating func scanVariableName() throws -> Substring {
private mutating func scanVariableName() throws(URITemplate.Error) -> Substring {
let endIndex = scanUpTo(characterSet: invertedVarnameCharacterSet)
let variableName = string[currentIndex..<endIndex]
if variableName.isEmpty {
Expand All @@ -129,7 +129,7 @@ struct Scanner {
return variableName
}

private mutating func scanVariableModifier() throws -> VariableSpec.Modifier {
private mutating func scanVariableModifier() throws(URITemplate.Error) -> VariableSpec.Modifier {
switch unicodeScalars[currentIndex] {
case "*":
currentIndex = unicodeScalars.index(after: currentIndex)
Expand Down Expand Up @@ -157,7 +157,7 @@ struct Scanner {
}
}

private mutating func scanLiteralComponent() throws -> Component {
private mutating func scanLiteralComponent() throws(URITemplate.Error) -> Component {
assert(literalCharacterSet.contains(unicodeScalars[currentIndex]))

let startIndex = currentIndex
Expand All @@ -166,7 +166,7 @@ struct Scanner {
return LiteralComponent(string[startIndex..<endIndex])
}

private mutating func scanPercentEncodingComponent() throws -> Component {
private mutating func scanPercentEncodingComponent() throws(URITemplate.Error) -> Component {
assert(unicodeScalars[currentIndex] == "%")

let startIndex = currentIndex
Expand Down
22 changes: 11 additions & 11 deletions Sources/ScreamURITemplate/Internal/ValueFormatting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct FormatError: Error {
}

extension TypedVariableValue {
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration configuration: ExpansionConfiguration) throws -> String? {
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration configuration: ExpansionConfiguration) throws(FormatError) -> String? {
switch self {
case let .string(plainValue):
return try plainValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration)
Expand All @@ -45,7 +45,7 @@ extension TypedVariableValue {
}
}

private func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: CharacterSet, allowPercentEncodedTriplets: Bool) throws -> String {
private func percentEncode(string: String, withAllowedCharacters allowedCharacterSet: CharacterSet, allowPercentEncodedTriplets: Bool) throws(FormatError) -> String {
guard var encoded = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) else {
throw FormatError(reason: "Percent Encoding Failed")
}
Expand Down Expand Up @@ -73,7 +73,7 @@ private func percentEncode(string: String, withAllowedCharacters allowedCharacte
}

private extension StringProtocol {
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String {
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws(FormatError) -> String {
let modifiedValue = if let prefixLength = variableSpec.prefixLength() {
String(prefix(prefixLength))
} else {
Expand All @@ -91,9 +91,9 @@ private extension StringProtocol {
}

private extension Array where Element: StringProtocol {
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? {
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws(FormatError) -> String? {
let separator = ","
let encodedExpansions = try map { element -> String in
let encodedExpansions = try map { element throws(FormatError) -> String in
return try percentEncode(string: String(element), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets)
}
if encodedExpansions.count == 0 {
Expand All @@ -109,9 +109,9 @@ private extension Array where Element: StringProtocol {
return expansion
}

func explodeForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? {
func explodeForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws(FormatError) -> String? {
let separator = expansionConfiguration.separator
let encodedExpansions = try map { element -> String in
let encodedExpansions = try map { element throws(FormatError) -> String in
let encodedElement = try percentEncode(string: String(element), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet, allowPercentEncodedTriplets: expansionConfiguration.allowPercentEncodedTriplets)
if expansionConfiguration.named {
if encodedElement.isEmpty && expansionConfiguration.omitOrphanedEquals {
Expand All @@ -129,8 +129,8 @@ private extension Array where Element: StringProtocol {
}

private extension [(key: String, value: String)] {
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? {
let encodedExpansions = try map { key, value -> String in
func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws(FormatError) -> String? {
let encodedExpansions = try map { key, value throws(FormatError) -> 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)
return "\(encodedKey),\(encodedValue)"
Expand All @@ -145,9 +145,9 @@ private extension [(key: String, value: String)] {
return expansion
}

func explodeForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String? {
func explodeForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws(FormatError) -> String? {
let separator = expansionConfiguration.separator
let encodedExpansions = try map { key, value -> String in
let encodedExpansions = try map { key, value throws(FormatError) -> 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.omitOrphanedEquals {
Expand Down
8 changes: 4 additions & 4 deletions Sources/ScreamURITemplate/URITemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct URITemplate {
/// - Parameter string: the string representation of the URI Template
///
/// - Throws: `URITemplate.Error` with `type = .malformedTemplate` if the string is not a valid URI Template
public init(string: String) throws {
public init(string: String) throws(URITemplate.Error) {
var components: [Component] = []
var scanner = Scanner(string: string)
while !scanner.isComplete {
Expand All @@ -56,7 +56,7 @@ public struct URITemplate {
/// - Returns: The result of processing the template
///
/// - Throws: `URITemplate.Error` with `type = .expansionFailure` if an error occurs processing the template
public func process(variables: TypedVariableProvider) throws -> String {
public func process(variables: TypedVariableProvider) throws(URITemplate.Error) -> String {
var result = ""
for component in components {
result += try component.expand(variables: variables)
Expand All @@ -73,7 +73,7 @@ public struct URITemplate {
/// - Returns: The result of processing the template
///
/// - Throws: `URITemplate.Error` with `type = .expansionFailure` if an error occurs processing the template
public func process(variables: VariableProvider) throws -> String {
public func process(variables: VariableProvider) throws(URITemplate.Error) -> String {
struct TypedVariableProviderWrapper: TypedVariableProvider {
let variables: VariableProvider

Expand All @@ -94,7 +94,7 @@ public struct URITemplate {
/// - Returns: The result of processing the template
///
/// - Throws: `URITemplate.Error` with `type = .expansionFailure` if an error occurs processing the template
public func process(variables: [String: String]) throws -> String {
public func process(variables: [String: String]) throws(URITemplate.Error) -> String {
return try process(variables: variables as VariableDictionary)
}

Expand Down
4 changes: 1 addition & 3 deletions Tests/ScreamURITemplateTests/TestFileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@ class TestFileTests: XCTestCase {
let template = try URITemplate(string: templateString)
_ = try template.process(variables: variables)
XCTFail("Did not throw")
} catch let error as URITemplate.Error {
} catch {
if failReason != nil {
XCTAssertEqual(failReason, error.reason)
}
if failPosition != nil {
let characters = templateString[..<error.position].count
XCTAssertEqual(failPosition, characters)
}
} catch {
XCTFail("Threw an unexpected error")
}
}

Expand Down

0 comments on commit 90ad249

Please sign in to comment.