A Swift package for fetching web resources, built using modern Swift concurrency. It supports features like retry attempts with duration and fetching nested resources by a given key path. This solution was inspired by Paul Hudson's Modern, safe networking.
import WebAPIClient
typealias AppEnvironment = WebAPIClient.Environment
extension AppEnvironment {
#if DEBUG
static let development = Self(
name: "Development",
baseURL: URL(string: "https://theapp.dev")!,
session: {
let config = URLSessionConfiguration.ephemeral
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
config.httpAdditionalHeaders = ["ApiKey" : "test-key"]
return URLSession(configuration: config)
}()
)
#endif
static let production = Self(
name: "Production",
baseURL: URL(string: "https://theapp")!,
session: {
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["ApiKey": "production-key"]
return URLSession(configuration: config)
}()
)
}
typealias Resource = WebAPIClient.Resource
extension Resource where Value == [User] {
static let users = Self(
path: "users",
type: [User].self
)
}
extension Resource where Value == [Message] {
static let messages = Self(
path: "messages",
type: [Message].self
)
}
do {
let client = WebAPIClient(environment: .development)
let users = try await client.fetch(.users)
let messages = try await client.fetch(.messages)
// ...
} catch {
// error handling
}
do {
let user = try await client.fetch(
.user,
attempts: 3,
delay: .seconds(1)
)
// ...
} catch {
// error handling
}
extension Resource where Value == String {
static let nestedString = Self(
path: "some/big/response",
keyPath: "response.user.address.city",
type: String.self
)
}
do {
let city = try await client.fetch(.nestedString)
// ...
} catch {
// error handling
}
The client can be passed into the environment like this
struct WebAPIClientKey: EnvironmentKey {
static var defaultValue = WebAPIClient(environment: .development)
}
extension EnvironmentValues {
var apiClient: WebAPIClient {
get { self[WebAPIClientKey.self] }
set { self[WebAPIClientKey.self] = newValue }
}
}
@main
struct NetworkingAppApp: App {
let client = WebAPIClient(environment: .development)
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.apiClient, client)
}
}
}
struct UsersList: View {
@Environment(\.apiClient) var client
@State private var users: [User] = []
var body: some View {
// ...
}
.task {
users = try? await client.fetch(.users)
}
var multipart = MultipartData()
multipart.add(key: "model_type", value: "A")
multipart.add(key: "voice_type", value: "Jane")
multipart.add(key: "source_audio_file", fileName: "recording.wav", fileData: audioData, mimeType: "audio/wav")
var request = URLRequest(url: uploadURL)
request.httpBody = multipart.data
request.allHTTPHeaderFields = [
"Authorization" : "app-token",
"Content-Type" : multipart.contentType
]