A Swift package for URLRequest handling with structured error handling.
- Overview
- Features
- Installation
- Quick Start
- Usage Examples
- API Reference
- Testing
- Related Packages
- Requirements
- License
- Contributing
A type-safe URLRequest handling system with automatic envelope/direct response decoding, structured error handling, and dependency injection via swift-dependencies.
- Type-safe request handling with automatic response decoding
- Envelope pattern support (automatically handles both envelope-wrapped and direct JSON responses)
- Structured error handling with detailed error types and context
- Privacy-conscious logging (automatic sanitization of sensitive headers)
- Dependency injection via swift-dependencies
- Configurable JSON decoding with sensible defaults
- Debug mode for testing with enhanced logging
- Testable URLSession abstraction
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/coenttb/swift-urlrequest-handler", from: "0.0.1")
]Then add URLRequestHandler to your target dependencies:
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "URLRequestHandler", package: "swift-urlrequest-handler")
]
)
]import URLRequestHandler
import Dependencies
struct MyAPI {
@Dependency(\.defaultRequestHandler) var requestHandler
func fetchUser(id: String) async throws -> User {
let request = URLRequest(url: URL(string: "https://api.example.com/users/\(id)")!)
return try await requestHandler(
for: request,
decodingTo: User.self
)
}
}
struct User: Decodable {
let id: String
let name: String
let email: String
}The handler automatically attempts to decode responses as envelope-wrapped first, then falls back to direct decoding:
// Handles this envelope response:
// {
// "success": true,
// "data": { "id": "123", "name": "John" },
// "message": "User fetched successfully",
// "timestamp": "2024-01-01T00:00:00Z"
// }
// And also handles direct response:
// { "id": "123", "name": "John" }
let user: User = try await requestHandler(
for: request,
decodingTo: User.self
)var handler = URLRequest.Handler()
handler.decoder.dateDecodingStrategy = .secondsSince1970
handler.decoder.keyDecodingStrategy = .useDefaultKeys
let response = try await handler(
for: request,
decodingTo: Response.self
)For requests that don't return a response body:
let request = URLRequest(url: URL(string: "https://api.example.com/logout")!)
request.httpMethod = "POST"
try await requestHandler(for: request)do {
let user = try await requestHandler(
for: request,
decodingTo: User.self
)
} catch RequestError.httpError(let statusCode, let message) {
print("HTTP Error \(statusCode): \(message)")
} catch RequestError.decodingError(let context) {
print("Decoding failed: \(context.description)")
} catch RequestError.envelopeDataMissing {
print("Envelope response contained no data")
} catch RequestError.invalidResponse {
print("Invalid response from server")
}In tests, the handler automatically enables debug mode:
import URLRequestHandler
import DependenciesTestSupport
import Testing
@Test
func testAPICall() async throws {
try await withDependencies {
$0.defaultSession = { request in
// Mock response
let data = """
{"id": "123", "name": "Test User"}
""".data(using: .utf8)!
let response = HTTPURLResponse(
url: request.url!,
statusCode: 200,
httpVersion: nil,
headerFields: nil
)!
return (data, response)
}
} operation: {
@Dependency(\.defaultRequestHandler) var handler
let request = URLRequest(url: URL(string: "https://api.example.com/user")!)
let user: User = try await handler(
for: request,
decodingTo: User.self
)
#expect(user.id == "123")
#expect(user.name == "Test User")
}
}Override the default session for custom configurations:
try await withDependencies {
$0.defaultSession = { request in
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
let session = URLSession(configuration: config)
return try await session.data(for: request)
}
} operation: {
// Your code using the custom session
}The main request handler with configurable options:
public struct Handler: Sendable {
public var debug: Bool = false
public var decoder: JSONDecoder
public init(debug: Bool = false, decoder: JSONDecoder = Self.defaultDecoder)
}Comprehensive error types for different failure scenarios:
public enum RequestError: Error, Equatable {
case invalidResponse
case httpError(statusCode: Int, message: String)
case decodingError(DecodingContext)
case envelopeDataMissing
}Generic envelope type for wrapped API responses:
public struct Envelope<T> {
public let success: Bool
public let data: T?
public let message: String?
public let timestamp: Date
}swift test- Swift 6.0+
- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
- swift-dependencies (1.9.2+)
- xctest-dynamic-overlay (1.4.3+)
- swift-logging-extras (0.0.1+)
- swift-logging-extras: A Swift package for integrating swift-logging with swift-dependencies.
- swift-mailgun-live: A Swift package with live implementations for Mailgun.
- swift-server-foundation: A Swift package with tools to simplify server development.
- pointfreeco/swift-dependencies: A dependency management library for controlling dependencies in Swift.
- pointfreeco/xctest-dynamic-overlay: Define XCTest assertion helpers directly in production code.
This package is licensed under the Apache License 2.0. See LICENSE for details.
Contributions are welcome! Please open an issue or submit a pull request.