405 lines
9.2 KiB
Swift
405 lines
9.2 KiB
Swift
//
|
|
// APIUsageExamples.swift
|
|
// AppClientBaseSwift
|
|
//
|
|
// Examples of using APIResponse wrapper
|
|
// Ví dụ sử dụng APIResponse wrapper
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/*
|
|
|
|
HƯỚNG DẪN SỬ DỤNG API RESPONSE WRAPPER
|
|
======================================
|
|
|
|
Server của bạn trả về response với format:
|
|
{
|
|
"success": true,
|
|
"data": { ... } hoặc [ ... ],
|
|
"error": null,
|
|
"pagination": null
|
|
}
|
|
|
|
Tất cả API calls phải sử dụng wrapper tương ứng.
|
|
|
|
*/
|
|
|
|
// MARK: - Example 1: Single Object Response
|
|
// Ví dụ 1: Response trả về 1 object
|
|
|
|
/*
|
|
GET /api/v1/users/me
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": "...",
|
|
"email": "...",
|
|
"name": "..."
|
|
},
|
|
"error": null,
|
|
"pagination": null
|
|
}
|
|
*/
|
|
|
|
func exampleFetchUser() async throws -> User {
|
|
// Cách 1: Dùng unwrap() helper (recommended)
|
|
let response: APIResponse<User> = try await APIService.shared.get(
|
|
endpoint: "/users/me"
|
|
)
|
|
let user = try response.unwrap()
|
|
return user
|
|
|
|
// Cách 2: Manual check
|
|
// let response: APIResponse<User> = try await APIService.shared.get(endpoint: "/users/me")
|
|
// guard response.success, let user = response.data else {
|
|
// throw APIError.serverError(statusCode: 400, message: response.error ?? "Failed")
|
|
// }
|
|
// return user
|
|
}
|
|
|
|
// MARK: - Example 2: List/Array Response
|
|
// Ví dụ 2: Response trả về danh sách
|
|
|
|
/*
|
|
GET /api/v1/users?page=1
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{ "id": "1", "email": "...", "name": "..." },
|
|
{ "id": "2", "email": "...", "name": "..." }
|
|
],
|
|
"error": null,
|
|
"pagination": {
|
|
"currentPage": 1,
|
|
"pageSize": 20,
|
|
"totalPages": 5,
|
|
"totalItems": 100
|
|
}
|
|
}
|
|
*/
|
|
|
|
func exampleFetchUsers() async throws -> [User] {
|
|
// Dùng ListResponse wrapper
|
|
let response: ListResponse<User> = try await APIService.shared.get(
|
|
endpoint: "/users?page=1"
|
|
)
|
|
|
|
// Unwrap để lấy array
|
|
let users = try response.unwrap()
|
|
|
|
// Có thể access pagination info
|
|
if let pagination = response.pagination {
|
|
print("Page \(pagination.currentPage ?? 0) of \(pagination.totalPages ?? 0)")
|
|
print("Total items: \(pagination.totalItems ?? 0)")
|
|
}
|
|
|
|
return users
|
|
}
|
|
|
|
// MARK: - Example 3: POST Request with Response Data
|
|
// Ví dụ 3: POST request có response data
|
|
|
|
/*
|
|
POST /api/v1/posts
|
|
Body: { "title": "...", "content": "..." }
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": "post_123",
|
|
"title": "...",
|
|
"content": "...",
|
|
"createdAt": "..."
|
|
},
|
|
"error": null,
|
|
"pagination": null
|
|
}
|
|
*/
|
|
|
|
struct Post: Codable {
|
|
let id: String
|
|
let title: String
|
|
let content: String
|
|
let createdAt: Date?
|
|
}
|
|
|
|
struct CreatePostRequest: Encodable {
|
|
let title: String
|
|
let content: String
|
|
}
|
|
|
|
func exampleCreatePost(title: String, content: String) async throws -> Post {
|
|
let request = CreatePostRequest(title: title, content: content)
|
|
|
|
let response: APIResponse<Post> = try await APIService.shared.post(
|
|
endpoint: "/posts",
|
|
body: request
|
|
)
|
|
|
|
return try response.unwrap()
|
|
}
|
|
|
|
// MARK: - Example 4: DELETE/UPDATE Requests
|
|
// Ví dụ 4: DELETE/UPDATE requests
|
|
|
|
/*
|
|
DELETE /api/v1/posts/123
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": null,
|
|
"error": null,
|
|
"pagination": null
|
|
}
|
|
*/
|
|
|
|
func exampleDeletePost(id: String) async throws {
|
|
// Dùng SimpleResponse khi không cần data
|
|
let response: SimpleResponse = try await APIService.shared.delete(
|
|
endpoint: "/posts/\(id)"
|
|
)
|
|
|
|
if !response.success {
|
|
throw APIError.serverError(
|
|
statusCode: 400,
|
|
message: response.error ?? response.message ?? "Delete failed"
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Example 5: PUT/PATCH Request
|
|
// Ví dụ 5: PUT/PATCH request
|
|
|
|
/*
|
|
PUT /api/v1/users/profile
|
|
Body: { "name": "New Name", "phoneNumber": "..." }
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": "...",
|
|
"email": "...",
|
|
"name": "New Name",
|
|
...
|
|
},
|
|
"error": null,
|
|
"pagination": null
|
|
}
|
|
*/
|
|
|
|
struct UpdateProfileRequest: Encodable {
|
|
let name: String?
|
|
let phoneNumber: String?
|
|
}
|
|
|
|
func exampleUpdateProfile(name: String?, phoneNumber: String?) async throws -> User {
|
|
let request = UpdateProfileRequest(name: name, phoneNumber: phoneNumber)
|
|
|
|
let response: APIResponse<User> = try await APIService.shared.put(
|
|
endpoint: "/users/profile",
|
|
body: request
|
|
)
|
|
|
|
return try response.unwrap()
|
|
}
|
|
|
|
// MARK: - Example 6: Error Handling
|
|
// Ví dụ 6: Xử lý lỗi
|
|
|
|
func exampleWithErrorHandling() async {
|
|
do {
|
|
let response: APIResponse<User> = try await APIService.shared.get(
|
|
endpoint: "/users/me"
|
|
)
|
|
let user = try response.unwrap()
|
|
print("Success: \(user.email)")
|
|
|
|
} catch let error as APIError {
|
|
switch error {
|
|
case .decodingError(let decodingError):
|
|
print("❌ Decoding error: \(decodingError)")
|
|
// Server response structure doesn't match expected model
|
|
|
|
case .unauthorized:
|
|
print("❌ Unauthorized - token invalid or expired")
|
|
// Need to login again
|
|
|
|
case .networkError(let networkError):
|
|
print("❌ Network error: \(networkError)")
|
|
// No internet connection or server unreachable
|
|
|
|
case .serverError(let statusCode, let message):
|
|
print("❌ Server error \(statusCode): \(message ?? "Unknown")")
|
|
// Server returned error response
|
|
|
|
case .noData:
|
|
print("❌ No data received")
|
|
|
|
default:
|
|
print("❌ Unknown error: \(error)")
|
|
}
|
|
|
|
} catch {
|
|
print("❌ Unexpected error: \(error)")
|
|
}
|
|
}
|
|
|
|
// MARK: - Example 7: Nested Objects
|
|
// Ví dụ 7: Objects lồng nhau
|
|
|
|
/*
|
|
GET /api/v1/posts/123/comments
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"id": "comment_1",
|
|
"content": "Great post!",
|
|
"author": {
|
|
"id": "user_1",
|
|
"name": "John Doe",
|
|
"avatarUrl": "..."
|
|
},
|
|
"createdAt": "..."
|
|
}
|
|
],
|
|
"error": null,
|
|
"pagination": { ... }
|
|
}
|
|
*/
|
|
|
|
struct Comment: Codable {
|
|
let id: String
|
|
let content: String
|
|
let author: CommentAuthor
|
|
let createdAt: Date?
|
|
}
|
|
|
|
struct CommentAuthor: Codable {
|
|
let id: String
|
|
let name: String
|
|
let avatarUrl: String?
|
|
}
|
|
|
|
func exampleFetchComments(postId: String) async throws -> [Comment] {
|
|
let response: ListResponse<Comment> = try await APIService.shared.get(
|
|
endpoint: "/posts/\(postId)/comments"
|
|
)
|
|
return try response.unwrap()
|
|
}
|
|
|
|
// MARK: - Example 8: Search/Filter with Query Parameters
|
|
// Ví dụ 8: Search/Filter với query parameters
|
|
|
|
/*
|
|
GET /api/v1/posts?search=swift&category=tutorial&page=1&pageSize=20
|
|
*/
|
|
|
|
func exampleSearchPosts(
|
|
search: String?,
|
|
category: String?,
|
|
page: Int = 1,
|
|
pageSize: Int = 20
|
|
) async throws -> [Post] {
|
|
var queryItems: [String] = []
|
|
|
|
if let search = search {
|
|
queryItems.append("search=\(search)")
|
|
}
|
|
if let category = category {
|
|
queryItems.append("category=\(category)")
|
|
}
|
|
queryItems.append("page=\(page)")
|
|
queryItems.append("pageSize=\(pageSize)")
|
|
|
|
let queryString = queryItems.joined(separator: "&")
|
|
let endpoint = "/posts?\(queryString)"
|
|
|
|
let response: ListResponse<Post> = try await APIService.shared.get(
|
|
endpoint: endpoint
|
|
)
|
|
|
|
return try response.unwrap()
|
|
}
|
|
|
|
// MARK: - Example 9: Upload File (if needed)
|
|
// Ví dụ 9: Upload file (nếu cần)
|
|
|
|
/*
|
|
POST /api/v1/users/avatar
|
|
Content-Type: multipart/form-data
|
|
|
|
Note: Cần implement multipart form data nếu cần upload file
|
|
Hiện tại APIService chỉ hỗ trợ JSON
|
|
*/
|
|
|
|
// MARK: - Example 10: Custom Headers
|
|
// Ví dụ 10: Custom headers
|
|
|
|
func exampleWithCustomHeaders() async throws -> User {
|
|
// Nếu cần thêm headers đặc biệt
|
|
let response: APIResponse<User> = try await APIService.shared.request(
|
|
endpoint: "/users/me",
|
|
method: .get,
|
|
body: nil as String?,
|
|
headers: [
|
|
"X-Custom-Header": "custom-value",
|
|
"X-Request-ID": UUID().uuidString
|
|
]
|
|
)
|
|
|
|
return try response.unwrap()
|
|
}
|
|
|
|
// MARK: - Best Practices
|
|
// Các best practices
|
|
|
|
/*
|
|
|
|
✅ DO - Nên làm:
|
|
|
|
1. Luôn dùng APIResponse<T> hoặc ListResponse<T>
|
|
2. Dùng unwrap() helper để code ngắn gọn
|
|
3. Handle errors properly với do-catch
|
|
4. Log errors trong development
|
|
5. Validate request data trước khi gọi API
|
|
6. Cache data khi cần (như User data)
|
|
|
|
❌ DON'T - Không nên làm:
|
|
|
|
1. Không decode trực tiếp thành Model mà không wrapper
|
|
2. Không ignore errors
|
|
3. Không hardcode URLs, dùng Constants
|
|
4. Không lưu sensitive data vào UserDefaults (dùng Keychain)
|
|
5. Không block main thread với synchronous calls
|
|
|
|
*/
|
|
|
|
// MARK: - Testing Tips
|
|
// Tips test API
|
|
|
|
/*
|
|
|
|
Test APIs trong development:
|
|
|
|
1. Dùng APITestView để test từng endpoint
|
|
2. Check Console logs với DebugLogger
|
|
3. Verify JSON response structure
|
|
4. Test error cases (401, 404, 500, etc.)
|
|
5. Test với slow network / offline
|
|
|
|
Mock data cho UI testing:
|
|
|
|
#if DEBUG
|
|
func mockFetchUser() async -> User {
|
|
return User.sample
|
|
}
|
|
#endif
|
|
|
|
*/
|
|
|