|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +FTAPIKit is a declarative async/await REST API framework for Swift using Swift Concurrency and Codable. It provides a protocol-oriented approach to defining web services with standard implementation using URLSession and JSON encoder/decoder. The framework is built for Swift 6.1 with full concurrency safety. |
| 8 | + |
| 9 | +**Key Features:** |
| 10 | +- Declarative async/await API for defining web services |
| 11 | +- Protocol-oriented design with `URLServer` and `Endpoint` protocols |
| 12 | +- Multiple endpoint types for different use cases (GET, POST, multipart uploads, etc.) |
| 13 | +- Async buildRequest enabling token refresh, dynamic configuration, and rate limiting |
| 14 | +- `RequestConfiguring` protocol for per-request configuration at call site |
| 15 | +- `NetworkObserver` protocol for request lifecycle monitoring (logging, analytics) |
| 16 | +- Swift 6 concurrency safety with Sendable requirements |
| 17 | +- Cross-platform support: iOS 15+, macOS 12+, tvOS 15+, watchOS 8+ |
| 18 | + |
| 19 | +## Build and Test Commands |
| 20 | + |
| 21 | +### Building |
| 22 | +```bash |
| 23 | +# Use xcodebuild (preferred, avoids toolchain mismatch issues) |
| 24 | +xcodebuild build -scheme FTAPIKit -destination 'platform=macOS' |
| 25 | + |
| 26 | +# Or with Swift CLI |
| 27 | +swift build |
| 28 | +``` |
| 29 | + |
| 30 | +### Running Tests |
| 31 | +```bash |
| 32 | +# Use xcodebuild |
| 33 | +xcodebuild test -scheme FTAPIKit -destination 'platform=macOS' |
| 34 | + |
| 35 | +# Or with Swift CLI |
| 36 | +swift test |
| 37 | +``` |
| 38 | + |
| 39 | +### Linting |
| 40 | +```bash |
| 41 | +# Run SwiftLint with strict mode |
| 42 | +swiftlint --strict |
| 43 | +``` |
| 44 | + |
| 45 | +The project uses an extensive SwiftLint configuration (`.swiftlint.yml`) with many opt-in rules enabled. Linting must pass with `--strict` flag with zero violations before committing any code. |
| 46 | + |
| 47 | +## Architecture |
| 48 | + |
| 49 | +### Core Protocol Design |
| 50 | + |
| 51 | +The framework is built around two core protocols: |
| 52 | + |
| 53 | +1. **`URLServer` Protocol** - Represents a single web service |
| 54 | + - Defines `baseUri`, `urlSession`, `encoding`/`decoding`, `networkObservers` |
| 55 | + - Builds requests from endpoints via `buildRequest(endpoint:) async throws` |
| 56 | + - Provides default implementations for all properties except `baseUri` |
| 57 | + - Has `ErrorType` associated type (defaults to `APIError.Standard`) |
| 58 | + |
| 59 | +2. **`Endpoint` Protocol** - Represents access points for resources |
| 60 | + - Defines path, headers, query parameters, and HTTP method |
| 61 | + - Multiple specialized variants for different use cases |
| 62 | + |
| 63 | +### Endpoint Type Hierarchy |
| 64 | + |
| 65 | +- **`Endpoint`** - Base protocol with empty body (defaults to GET) |
| 66 | +- **`DataEndpoint`** - Sends raw data in body (defaults to POST) |
| 67 | +- **`UploadEndpoint`** - Uploads files using URLSession upload (defaults to POST) |
| 68 | +- **`MultipartEndpoint`** - Combines body parts into multipart request (defaults to POST) |
| 69 | +- **`URLEncodedEndpoint`** - Body in URL query format (defaults to POST) |
| 70 | +- **`RequestEndpoint`** - Has encodable request model (defaults to POST) |
| 71 | +- **`ResponseEndpoint`** - Has decodable response model |
| 72 | +- **`RequestResponseEndpoint`** - Typealias combining request and response endpoints |
| 73 | + |
| 74 | +### Key Architectural Patterns |
| 75 | + |
| 76 | +**URLServer Protocol**: The `URLServer` protocol provides all URLSession-based functionality with default implementations. Only `baseUri` must be provided by conforming types. |
| 77 | + |
| 78 | +**Network Observers**: The `NetworkObserver` protocol provides lifecycle callbacks (`willSendRequest`, `didReceiveResponse`, `didFail`) with type-safe context passing. Observer integration uses `BoundObserverContext` (private) for type erasure. |
| 79 | + |
| 80 | +**Request Configuration**: The `RequestConfiguring` protocol allows per-request async configuration at the call site, separate from server-level `buildRequest`. |
| 81 | + |
| 82 | +**Encoding/Decoding**: The `Encoding` protocol includes `configure(request:)` for setting content-type headers (with empty default). Both `Encoding` and `Decoding` require `Sendable`. |
| 83 | + |
| 84 | +**Swift 6 Concurrency Safety**: All `ResponseEndpoint.Response` and `RequestEndpoint.Request` associated types must conform to `Sendable`. |
| 85 | + |
| 86 | +### Module Organization |
| 87 | + |
| 88 | +**Source Structure** (`Sources/FTAPIKit/`): |
| 89 | +- Core protocols: `URLServer.swift`, `Endpoint.swift` |
| 90 | +- Request building: `URLRequestBuilder.swift`, `RequestConfiguring.swift` |
| 91 | +- Async execution: `URLServer+Async.swift` (includes download) |
| 92 | +- Observers: `NetworkObserver.swift` |
| 93 | +- Utilities: `Coding.swift`, `URLQuery.swift`, `MultipartFormData.swift`, etc. |
| 94 | +- Error handling: `APIError.swift`, `APIError+Standard.swift` |
| 95 | + |
| 96 | +**Test Structure** (`Tests/FTAPIKitTests/`): |
| 97 | +- Uses Swift Testing framework (`@Suite`, `@Test`, `#expect`) |
| 98 | +- Test files: `AsyncTests.swift`, `AsyncBuildRequestTests.swift`, `URLQueryTests.swift`, `NetworkObserverTests.swift`, `RequestConfiguringTests.swift`, `EndpointTypeTests.swift`, `ErrorHandlingTests.swift` |
| 99 | +- Test utilities in `Mockups/`: `Servers.swift`, `Endpoints.swift`, `Models.swift`, `Errors.swift`, `MockNetworkObserver.swift`, `MockTokenManager.swift`, `HTTPBinResponse.swift` |
| 100 | + |
| 101 | +### Call Execution Pattern |
| 102 | + |
| 103 | +The framework uses async/await exclusively: |
| 104 | + |
| 105 | +```swift |
| 106 | +// Basic call |
| 107 | +let response = try await server.call(response: endpoint) |
| 108 | + |
| 109 | +// Data call (raw Data response) |
| 110 | +let data = try await server.call(data: endpoint) |
| 111 | + |
| 112 | +// Void call (no response body) |
| 113 | +try await server.call(endpoint: endpoint) |
| 114 | + |
| 115 | +// Download call (temporary file — caller must move before returning) |
| 116 | +let fileURL = try await server.download(endpoint: endpoint) |
| 117 | + |
| 118 | +// Download call (moved to destination automatically) |
| 119 | +try await server.download(endpoint: endpoint, destination: destinationURL) |
| 120 | +``` |
| 121 | + |
| 122 | +### Error Handling |
| 123 | + |
| 124 | +- `APIError` protocol defines error handling interface |
| 125 | +- Default implementation: `APIError.Standard` (enum with connection, encoding, decoding, server, client, unhandled cases) |
| 126 | +- Network errors (`URLError`) and decoding errors are routed through `ErrorType` for consistent error handling |
| 127 | +- Encoding errors (from `buildStandardRequest`) propagate directly as `EncodingError` since they occur before the network request |
| 128 | +- Custom error types can be defined via `URLServer.ErrorType` associated type |
| 129 | + |
| 130 | +## Package Management |
| 131 | + |
| 132 | +The project uses **Swift Package Manager** exclusively. See `Package.swift`. |
| 133 | + |
| 134 | +### Platform Support |
| 135 | + |
| 136 | +Minimum deployment targets: |
| 137 | +- iOS 15+ |
| 138 | +- macOS 12+ |
| 139 | +- tvOS 15+ |
| 140 | +- watchOS 8+ |
| 141 | + |
| 142 | +## Testing Approach |
| 143 | + |
| 144 | +Tests use Swift Testing framework and mock servers (HTTPBin-based) defined in `Tests/FTAPIKitTests/Mockups/Servers.swift`: |
| 145 | +- `HTTPBinServer` - Standard test server with async authorization support |
| 146 | +- `NonExistingServer` - For testing error conditions |
| 147 | +- `ErrorThrowingServer` - Custom error type testing |
| 148 | +- `HTTPBinServerWithObservers` - Observer integration testing |
| 149 | + |
| 150 | +## CI/CD |
| 151 | + |
| 152 | +Single GitHub Actions workflow (`ci.yml`) runs on `macos-latest`: |
| 153 | +- `swiftlint --strict` |
| 154 | +- `swift build` |
| 155 | +- `swift test` |
0 commit comments