southkin/fullyrestful
This library lets you define APIs like this:
β¨ What makes it different?
This library lets you define APIs like this:
let responseModel = try await MyAPI().request(param: .init(...)).model...instead of writing all this:
var request = URLRequest(url: URL(string: "...")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(MyAPI.Request(...))
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
let responseModel = try JSONDecoder().decode(MyAPI.Response.self, from: data)π€ Why FullyRESTful?
- You want a clean, Swift-native way to call APIs.
- You hate writing boilerplate URLRequests.
- You want WebSocket and REST in the same system.
- You want your API layer to feel like SwiftUI.
- You want it to just work.
FullyRESTful is a Swift library supporting both RESTful APIs and WebSocket connections in a simple, declarative, and composable way.
π¨ FullyRESTful 3.0.0
Breaking Changes in WebSocket Support
The internal WebSocket system has been completely restructured. If you were using WebSocket features in version 2.x, please update your usage accordingly.
π Features
- Declarative API Definitions
Define REST or WebSocket APIs in a single Swift structure.
- RESTful API Support
Simple request/response modeling with JSON encoding/decoding.
- Multipart Upload Support
Upload files and form data with minimal configuration.
- Modern WebSocket Support
Connect once and share the connection via Combine.
- Modular Design
Use only the features you need.
π¦ Installation
Swift Package Manager
dependencies: [
.package(url: "https://github.com/southkin/FullyRESTful.git", .upToNextMajor(from: "3.0.0"))
]π RESTful API Usage
Define a Server
let myServer = ServerInfo(domain: "https://api.example.com", defaultHeader: [:])Define an API
struct MyAPI: APIITEM {
var server = myServer
struct Request: Codable {
let param1: String?
let param2: [Int]
let param3: [String: Float]
}
struct Response: Codable {
let result1: [String]
let result2: [Int]?
let result3: [String: Float]?
}
var requestModel = Request.self
var responseModel = Response.self
var method: HTTPMethod = .POST
var path: String = "/myapi/path"
}Request
let data = try? await MyAPI().getData(param: .init(param1: "example", param2: [1, 2, 3], param3: ["key": 1.23])).data
let model = try? await MyAPI().request(param: .init(param1: "example", param2: [1, 2, 3], param3: ["key": 1.23])).modelπ Multipart Upload
Define an API with File Upload
struct MyUploadAPI: APIITEM, MultipartUpload {
var server = myServer
struct Request: Codable {
let title: String
let image: MultipartItem
}
struct Response: Codable {
let uploadedURL: String
}
var requestModel = Request.self
var responseModel = Response.self
var method: HTTPMethod = .POST
var path: String = "/upload"
}π WebSocket Usage
WebSockets in FullyRESTful are:
- automatically managed (single connection per endpoint)
- Combine-based, and cancellable
- pinged at regular intervals to stay alive
- capable of sending .text, .binary, or .codable(...) payloads
Define a WebSocket
struct EchoSocket: WebSocketITEM {
var server = ServerInfo(domain: "wss://echo.websocket.org", defaultHeader: [:])
var path: String = ""
// Optional: override ping interval (default = 10 sec)
var pingInterval: TimeInterval { 5 }
}Connect and Subscribe
let socket = EchoSocket()
socket.listen()
.sink { message in
print("π₯", message ?? "nil")
}
.store(in: &cancellables)Send Text Message
try await socket.send(.text("Hello WebSocket!"))Send a Codable Message
struct ChatMessage: Codable {
let type: String
let content: String
}
let message = ChatMessage(type: "chat", content: "Hello from Codable!")
try await socket.send(.codable(message))Decode Received Messages
socket.listen()
.compactMap {
guard case let .text(text) = $0,
let data = text.data(using: .utf8),
let decoded = try? JSONDecoder().decode(ChatMessage.self, from: data)
else { return nil }
return decoded
}
.sink { decoded in
print("π© Decoded:", decoded)
}
.store(in: &cancellables)π§ͺ Debugging
Enable cURL log output for debugging:
struct MyAPI: APIITEM {
var curlLog = true
}π License
MIT License
Package Metadata
Repository: southkin/fullyrestful
Default branch: main
README: README.md