Files
pos-system/microservices/.agent/skills/swift-networking/SKILL.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

7.5 KiB

name, description, compatibility, metadata
name description compatibility metadata
swift-networking HTTP client, API handling, Error management cho Swift Enterprise. Use for REST APIs, URLSession, async networking, hoặc khi cần structured API layer. Swift 5.9+, iOS 17+, Foundation
author version references
Velik Ho 1.0 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

// 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

/// 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

/// 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

// 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