// // README.md // NetworkLayer // // Created by Mickey Anthony Gudiel Reyes on 1/7/25. //
A protocol-oriented Swift library for building type-safe, testable REST API clients with async/await.
- Protocol-Oriented Design: Easy to test and mock for reliable unit tests.
- Modern Concurrency: Built with
async/awaitfor clean, modern asynchronous code. - Generic: Can decode any
Codablemodel for both success and error responses. - Smart Error Handling: Can decode custom error models directly from API responses.
- Automatic Encoding: Automatically encodes
Encodableobjects for request bodies, reducing boilerplate.
You can add NetworkLayer to your project using Swift Package Manager.
- In Xcode, go to File > Add Packages...
- Paste the repository URL:
https://github.com/MickeyGR/NetworkLayer.git - Xcode will show the package product (
NetworkLayer). Ensure it is selected and that your main app is chosen in the "Add to Target" column before clicking Add Package.
If after adding the package you get an error like No such module 'NetworkLayer', it means the library was not linked correctly to your app. To fix it manually:
- In the Project Navigator, click on your project's blue icon at the top.
- Select your app's target.
- Go to the "General" tab.
- Scroll down to the "Frameworks, Libraries, and Embedded Content" section.
- Click the
+button, selectNetworkLayerfrom the list, and click Add.
Here is a real example of how to fetch a "Digimon" from a public API.
First, define the Swift models that match the API's JSON response and the endpoint you want to call.
import NetworkLayer
// Model for the API response
struct Digimon: Codable {
let id: Int
let name: String
}
// Model for the API's specific error response
struct ApiError: Codable, Error, LocalizedError {
let error: Int
let message: String
var errorDescription: String? { return message }
}
// Define the specific endpoint
enum DigimonEndpoint {
case getDigimon(id: Int)
}
extension DigimonEndpoint: Endpoint {
typealias ErrorModel = ApiError
var baseURL: String { "[https://digi-api.com](https://digi-api.com)" }
var path: String {
switch self {
case .getDigimon(let id):
return "/api/v1/digimon/\(id)"
}
}
var method: HTTPMethod { .get }
}Create an instance of the service and make the request.
import NetworkLayer
// Using the typealias for convenience
let networkService = NetworkLayer.Service()
func fetchDigimon(id: Int) async {
let endpoint = DigimonEndpoint.getDigimon(id: id)
do {
let digimon: Digimon = try await networkService.request(endpoint: endpoint)
print("✅ Successfully fetched: \(digimon.name)")
} catch let error as ApiError {
print("🛑 API Error: \(error.localizedDescription)")
} catch {
print("🛑 Network Error: \(error.localizedDescription)")
}
}
// Example calls
await fetchDigimon(id: 5) // Success
await fetchDigimon(id: 9999) // API ErrorHere is how you would define POST, PUT, and DELETE requests using the EncodableEndpoint protocol for automatic body encoding.
First, add new cases for the new operations and define a Payload struct for the data you want to send.
struct DigimonPayload: Codable {
let name: String
let level: String
}
enum DigimonEndpoint {
// ... existing cases
case addDigimon(payload: DigimonPayload)
case updateDigimon(id: Int, payload: DigimonPayload)
case deleteDigimon(id: Int)
}Conform to EncodableEndpoint and add the logic for the new cases.
// Conform to EncodableEndpoint to handle request bodies automatically
extension DigimonEndpoint: EncodableEndpoint {
typealias BodyModel = DigimonPayload
// ... other properties like baseURL, path, etc.
var method: HTTPMethod {
switch self {
case .getDigimon: .get
case .addDigimon: .post
case .updateDigimon: .put
case .deleteDigimon: .delete
}
}
// You only need to provide the object. The library handles the encoding.
var encodableBody: DigimonPayload? {
switch self {
case .addDigimon(let payload), .updateDigimon(_, let payload):
return payload
default:
return nil
}
}
}