Migrate
This commit is contained in:
254
microservices/.agent/skills/swift-networking/SKILL.md
Normal file
254
microservices/.agent/skills/swift-networking/SKILL.md
Normal file
@@ -0,0 +1,254 @@
|
||||
---
|
||||
name: swift-networking
|
||||
description: HTTP client, API handling, Error management cho Swift Enterprise. Use for REST APIs, URLSession, async networking, hoặc khi cần structured API layer.
|
||||
compatibility: "Swift 5.9+, iOS 17+, Foundation"
|
||||
metadata:
|
||||
author: Velik Ho
|
||||
version: "1.0"
|
||||
references: "URLSession, Swift Concurrency"
|
||||
---
|
||||
|
||||
# Swift Networking Patterns
|
||||
|
||||
HTTP client và API handling patterns cho Swift Enterprise applications.
|
||||
|
||||
## When to Use This Skill / Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when:
|
||||
- Building REST API client / Xây dựng REST API client
|
||||
- Need URLSession wrapper / Cần wrapper cho URLSession
|
||||
- Implementing token-based auth / Triển khai auth token-based
|
||||
- Error handling for APIs / Xử lý lỗi API
|
||||
|
||||
## Core Patterns / Mẫu Chính
|
||||
|
||||
### API Error Enum
|
||||
|
||||
```swift
|
||||
// Services/APIError.swift
|
||||
|
||||
/// API error types
|
||||
/// Các loại lỗi API
|
||||
enum APIError: Error, LocalizedError {
|
||||
case invalidURL
|
||||
case noData
|
||||
case decodingError(Error)
|
||||
case networkError(Error)
|
||||
case serverError(statusCode: Int, message: String?)
|
||||
case unauthorized
|
||||
case forbidden
|
||||
case notFound
|
||||
case rateLimited
|
||||
case unknown
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
return "Invalid URL / URL không hợp lệ"
|
||||
case .noData:
|
||||
return "No data received / Không nhận được dữ liệu"
|
||||
case .decodingError(let error):
|
||||
return "Decoding error: \(error.localizedDescription)"
|
||||
case .networkError(let error):
|
||||
return "Network error: \(error.localizedDescription)"
|
||||
case .serverError(let code, let message):
|
||||
return "Server error (\(code)): \(message ?? "Unknown")"
|
||||
case .unauthorized:
|
||||
return "Unauthorized / Chưa xác thực"
|
||||
case .forbidden:
|
||||
return "Access forbidden / Truy cập bị từ chối"
|
||||
case .notFound:
|
||||
return "Resource not found / Không tìm thấy"
|
||||
case .rateLimited:
|
||||
return "Rate limited / Giới hạn request"
|
||||
case .unknown:
|
||||
return "Unknown error / Lỗi không xác định"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Method Enum
|
||||
|
||||
```swift
|
||||
/// HTTP request methods
|
||||
/// Các phương thức HTTP
|
||||
enum HTTPMethod: String {
|
||||
case get = "GET"
|
||||
case post = "POST"
|
||||
case put = "PUT"
|
||||
case patch = "PATCH"
|
||||
case delete = "DELETE"
|
||||
}
|
||||
```
|
||||
|
||||
### API Service Protocol
|
||||
|
||||
```swift
|
||||
/// API service protocol for DI
|
||||
/// Protocol API service cho DI
|
||||
protocol APIServiceProtocol {
|
||||
func request<T: Decodable>(
|
||||
endpoint: String,
|
||||
method: HTTPMethod,
|
||||
body: Encodable?,
|
||||
headers: [String: String]?
|
||||
) async throws -> T
|
||||
}
|
||||
```
|
||||
|
||||
### API Service Implementation
|
||||
|
||||
```swift
|
||||
// Services/APIService.swift
|
||||
|
||||
/// Main API service
|
||||
/// Dịch vụ API chính
|
||||
final class APIService: APIServiceProtocol {
|
||||
|
||||
// MARK: - Singleton
|
||||
|
||||
static let shared = APIService()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let session: URLSession
|
||||
private let encoder: JSONEncoder
|
||||
private let decoder: JSONDecoder
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(session: URLSession = .shared) {
|
||||
self.session = session
|
||||
|
||||
self.encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
encoder.dateEncodingStrategy = .iso8601
|
||||
|
||||
self.decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
decoder.dateDecodingStrategy = .iso8601
|
||||
}
|
||||
|
||||
// MARK: - Request
|
||||
|
||||
/// Perform network request
|
||||
/// Thực hiện request network
|
||||
func request<T: Decodable>(
|
||||
endpoint: String,
|
||||
method: HTTPMethod = .get,
|
||||
body: Encodable? = nil,
|
||||
headers: [String: String]? = nil
|
||||
) async throws -> T {
|
||||
|
||||
// Build URL
|
||||
guard let url = URL(string: APIConfig.baseURL + APIConfig.apiVersion + endpoint) else {
|
||||
throw APIError.invalidURL
|
||||
}
|
||||
|
||||
// Create request
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = method.rawValue
|
||||
request.timeoutInterval = APIConfig.timeout
|
||||
|
||||
// Set headers
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||
|
||||
// Add auth token
|
||||
if let token = await AuthManager.shared.accessToken {
|
||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
|
||||
// Add custom headers
|
||||
headers?.forEach { key, value in
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
|
||||
// Set body
|
||||
if let body = body {
|
||||
request.httpBody = try encoder.encode(body)
|
||||
}
|
||||
|
||||
// Perform request
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
// Handle response
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw APIError.unknown
|
||||
}
|
||||
|
||||
// Check status code
|
||||
switch httpResponse.statusCode {
|
||||
case 200...299:
|
||||
do {
|
||||
return try decoder.decode(T.self, from: data)
|
||||
} catch {
|
||||
throw APIError.decodingError(error)
|
||||
}
|
||||
case 401:
|
||||
await AuthManager.shared.handleUnauthorized()
|
||||
throw APIError.unauthorized
|
||||
case 403:
|
||||
throw APIError.forbidden
|
||||
case 404:
|
||||
throw APIError.notFound
|
||||
case 429:
|
||||
throw APIError.rateLimited
|
||||
default:
|
||||
let message = String(data: data, encoding: .utf8)
|
||||
throw APIError.serverError(statusCode: httpResponse.statusCode, message: message)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience Methods
|
||||
|
||||
/// GET request
|
||||
func get<T: Decodable>(endpoint: String) async throws -> T {
|
||||
try await request(endpoint: endpoint, method: .get, body: nil as String?, headers: nil)
|
||||
}
|
||||
|
||||
/// POST request
|
||||
func post<T: Decodable, B: Encodable>(endpoint: String, body: B) async throws -> T {
|
||||
try await request(endpoint: endpoint, method: .post, body: body, headers: nil)
|
||||
}
|
||||
|
||||
/// PUT request
|
||||
func put<T: Decodable, B: Encodable>(endpoint: String, body: B) async throws -> T {
|
||||
try await request(endpoint: endpoint, method: .put, body: body, headers: nil)
|
||||
}
|
||||
|
||||
/// DELETE request
|
||||
func delete<T: Decodable>(endpoint: String) async throws -> T {
|
||||
try await request(endpoint: endpoint, method: .delete, body: nil as String?, headers: nil)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes / Lỗi Thường Gặp
|
||||
|
||||
| Mistake | Problem | Solution |
|
||||
|---------|---------|----------|
|
||||
| No error handling | Crashes | Use `do-catch` with custom errors |
|
||||
| Force unwrap URLs | Crashes | Guard with `APIError.invalidURL` |
|
||||
| Blocking main thread | Frozen UI | Use `async/await` |
|
||||
| No timeout | Hanging requests | Set `timeoutInterval` |
|
||||
| Hardcoded URLs | Hard to maintain | Use `APIConfig` enum |
|
||||
|
||||
## Quick Reference / Tham Chiếu Nhanh
|
||||
|
||||
| Category | Standard |
|
||||
|----------|----------|
|
||||
| HTTP Client | URLSession with async/await |
|
||||
| Errors | Custom `APIError` enum |
|
||||
| Encoding | snake_case JSON |
|
||||
| Auth | Bearer token injection |
|
||||
| Timeout | 30 seconds default |
|
||||
|
||||
## Resources / Tài Nguyên
|
||||
|
||||
- [Swift Enterprise Architect](../swift-enterprise-architect/SKILL.md) - Architecture
|
||||
- [Swift Security](../swift-security/SKILL.md) - Token management
|
||||
- [Apple URLSession](https://developer.apple.com/documentation/foundation/urlsession)
|
||||
Reference in New Issue
Block a user