Skip to content

Commit

Permalink
Improve error types
Browse files Browse the repository at this point in the history
  • Loading branch information
fpseverino committed Nov 15, 2024
1 parent 45634e6 commit 88553a3
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 35 deletions.
78 changes: 60 additions & 18 deletions Sources/ImperialCore/Errors/ImperialError.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,67 @@
/// Represents various errors that can occur when attempting to unwrap an optional value.
public enum ImperialError: Error, CustomStringConvertible {

public struct ImperialError: Error, Sendable, Equatable {
public struct ErrorType: Sendable, Hashable, CustomStringConvertible, Equatable {
enum Base: String, Sendable, Equatable {
case missingEnvVar
}

let base: Base

private init(_ base: Base) {
self.base = base
}

public static let missingEnvVar = Self(.missingEnvVar)

public var description: String {
base.rawValue
}
}

private struct Backing: Sendable, Equatable {
fileprivate let errorType: ErrorType
fileprivate let variable: String?

init(errorType: ErrorType, variable: String? = nil) {
self.errorType = errorType
self.variable = variable
}

static func == (lhs: ImperialError.Backing, rhs: ImperialError.Backing) -> Bool {
lhs.errorType == rhs.errorType
}
}

private var backing: Backing

public var errorType: ErrorType { backing.errorType }
public var variable: String? { backing.variable }

private init(backing: Backing) {
self.backing = backing
}

/// Thrown when no environment varibale is found with a given name.
/// - warning: This error is never thrown; rather, the application will fatal error.
case missingEnvVar(String)

/// Thrown when we attempt to create a `FederatedCreatable` model and there is
/// no JSON in the response from the the request to `dataUri`.
case missingJSONFromResponse(String)



/// Thrown when `request.fetch` is called with a type that has not been run through `request.create`.
case typeNotInitialized(String)

public static func missingEnvVar(_ variable: String) -> Self {
.init(backing: .init(errorType: .missingEnvVar, variable: variable))
}

public static func == (lhs: ImperialError, rhs: ImperialError) -> Bool {
lhs.backing == rhs.backing
}
}

extension ImperialError: CustomStringConvertible {
/// A human readable version of the error thrown.
public var description: String {
switch self {
case let .missingEnvVar(variable): return "Missing enviroment variable '\(variable)'"
case let .missingJSONFromResponse(uri): return "Reponse returned from '\(uri)' does not contain JSON"
case let .typeNotInitialized(type): return "No instence of type '\(type)' has been created"
var result = #"ImperialError(errorType: \#(self.errorType)"#

if let variable {
result.append(", missing enviroment variable: \(variable)")
}

result.append(")")

return result
}
}
90 changes: 77 additions & 13 deletions Sources/ImperialCore/Errors/ServiceError.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,84 @@
/// Represents an error that occurs during a service action.
public enum ServiceError: Error, CustomStringConvertible {

public struct ServiceError: Error, Sendable, Equatable {
public struct ErrorType: Sendable, Hashable, CustomStringConvertible, Equatable {
enum Base: String, Sendable, Equatable {
case noServiceFound
case noServiceEndpoint
}

let base: Base

private init(_ base: Base) {
self.base = base
}

public static let noServiceFound = Self(.noServiceFound)
public static let noServiceEndpoint = Self(.noServiceEndpoint)

public var description: String {
base.rawValue
}
}

private struct Backing: Sendable, Equatable {
fileprivate let errorType: ErrorType
fileprivate let name: String?
fileprivate let endpoint: String?

init(
errorType: ErrorType,
name: String? = nil,
endpoint: String? = nil
) {
self.errorType = errorType
self.name = name
self.endpoint = endpoint
}

static func == (lhs: ServiceError.Backing, rhs: ServiceError.Backing) -> Bool {
lhs.errorType == rhs.errorType
}
}

private var backing: Backing

public var errorType: ErrorType { backing.errorType }
public var name: String? { backing.name }
public var endpoint: String? { backing.endpoint }

private init(backing: Backing) {
self.backing = backing
}

/// Thrown when no service is registered with a given name.
case noServiceFound(String)

/// Thrown when no `FederatedSewrvice` type is found whgen creating a `Service` from JSON.
case noExistingService(String)

public static func noServiceFound(_ name: String) -> Self {
.init(backing: .init(errorType: .noServiceFound, name: name))
}

/// Thrown when a `FederatedCreatable` type has a `serviceKey` that does not match any available endpoints in the service.
case noServiceEndpoint(String)

public static func noServiceEndpoint(_ endpoint: String) -> Self {
.init(backing: .init(errorType: .noServiceEndpoint, endpoint: endpoint))
}

public static func == (lhs: ServiceError, rhs: ServiceError) -> Bool {
lhs.backing == rhs.backing
}
}

extension ServiceError: CustomStringConvertible {
public var description: String {
switch self {
case let .noServiceFound(name): return "No service was found with the name '\(name)'"
case let .noExistingService(name): return "No service exists with the name '\(name)'"
case let .noServiceEndpoint(endpoint): return "Service does not have available endpoint for key '\(endpoint)'"
var result = #"ServiceError(errorType: \#(self.errorType)"#

if let name {
result.append(", no service was found with the name: \(name)")
}

if let endpoint {
result.append(", service does not have available endpoint for key: \(endpoint)")
}

result.append(")")

return result
}
}
2 changes: 0 additions & 2 deletions Sources/ImperialCore/Helpers/Request+Imperial.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ extension Request {
/// - Parameters:
/// - model: A type that conforms to `FederatedCreatable`.
/// - Returns: An instance of the type passed in that has been stored in the request.
/// - Throws:
/// - `ImperialError.typeNotInitialized`: If there is no value stored in the request for the type passed in.
func fetch<T: FederatedCreatable>(_ model: T.Type) throws -> T {
return try session.get("imperial-\(model)", as: T.self)
}
Expand Down
12 changes: 11 additions & 1 deletion Tests/ImperialTests/ImperialTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import Testing

@testable import ImperialCore

@Suite("ImperialCore Tests")
struct ImperialTests {
@Test func empty() {}
@Test("ImperialError & ServiceError")
func errors() {
#expect(ImperialError.missingEnvVar("test").description == "ImperialError(errorType: missingEnvVar, missing enviroment variable: test)")
#expect(ImperialError.missingEnvVar("foo") == ImperialError.missingEnvVar("bar"))

#expect(ServiceError.noServiceFound("test").description == "ServiceError(errorType: noServiceFound, no service was found with the name: test)")
#expect(ServiceError.noServiceEndpoint("test").description == "ServiceError(errorType: noServiceEndpoint, service does not have available endpoint for key: test)")
#expect(ServiceError.noServiceFound("foo") == ServiceError.noServiceFound("bar"))
#expect(ServiceError.noServiceEndpoint("foo") == ServiceError.noServiceEndpoint("bar"))
}
}
2 changes: 1 addition & 1 deletion Tests/ImperialTests/ShopifyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Testing

@testable import ImperialShopify

@Suite("Shopify Tests")
@Suite("ImperialShopify Tests")
struct ShopifyTests {
@Test("Valid Shopify Domain") func domainCheck() throws {
let domain = "davidmuzi.myshopify.com"
Expand Down

0 comments on commit 88553a3

Please sign in to comment.