zjinhu

SwiftMesh AI Skill

```

zjinhu 32 11 Updated 3mo ago

Resources

13
GitHub

Install

npx skillscat add zjinhu/swiftmesh

Install via the SkillsCat registry.

SKILL.md

SwiftMesh AI Skill

A comprehensive AI reference for using SwiftMesh — an Alamofire + Codable wrapper with async/await, Combine, fluent configuration, file upload/download, JSON key path parsing, resilient Codable wrappers, and built-in logging.


Quick Reference Card

Feature Method Description
GET Request .request(of: Model.self) Decode response to Codable model
Key Path .request(of: Model.self, modelKeyPath: "data.user") Extract nested JSON
File Upload .upload(of: Result.self) Upload file/Data/stream/multipart
File Download .download() Download or resume download
Raw Data .requestData() Get raw response Data
Raw String .requestString() Get response as String
Retry Policy .setInterceptor(RetryPolicy()) Auto-retry with backoff
Logging Mesh.enableLog() Enable network logging

Core Architecture

SwiftMesh uses a builder pattern on the Mesh class. Every configuration method returns Self, enabling fluent chaining. The flow is:

Configure (Mesh + Config) → Execute (Request/Upload/Download) → Handle (Handle)

File Structure

File Purpose
Mesh.swift Core builder class with all properties
Config.swift Fluent chainable setters + global config
Request.swift async/await request execution
Handle.swift URL construction, error handling, response processing, RetryPolicy
Upload.swift File upload (file, data, stream, multipart)
Download.swift File download (standard, resumable)
KeyPath.swift JSON key path decoder for nested extraction
Codable+.swift Resilient property wrappers (@Default, @IgnoreError, @ConvertTo*)
Log.swift Network logger (cURL, status, timing, JSON)

Usage Patterns

1. Basic GET Request

let result = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/weather/city/101030100")
    .request(of: Weather.self)

2. GET with JSON Key Path Extraction

Extract only a nested portion of the JSON response without parsing the entire structure:

// JSON: { "code": 200, "data": { "yesterday": { "temp": 25, "notice": "Sunny" } } }
let yesterday = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/weather")
    .request(of: Forecast.self, modelKeyPath: "data.yesterday")

3. POST Request with Parameters

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/login")
    .setParameters(["username": "admin", "password": "123456"])
    .request(of: LoginResult.self)

4. POST with JSON Encoding

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/api/data")
    .setRequestEncoding(JSONEncoding.default)
    .setParameters(["key": "value"])
    .request(of: Response.self)

5. Request with Custom Headers

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/secure/data")
    .setHeads(["Authorization": "Bearer token123"])
    .request(of: SecureData.self)

6. Request with Retry Policy

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/unstable-api")
    .setInterceptor(RetryPolicy(maxRetryCount: 3))
    .request(of: Data.self)

7. URLRequest-based Request

When you already have a URLRequestConvertible:

let urlRequest = try URLRequest(url: URL(string: "https://api.example.com/data")!, method: .get)
let result = try await Mesh()
    .urlRequest(urlRequest, type: Response.self)

8. Raw Data Response

let data = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/raw")
    .requestData()

9. Raw String Response

let string = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/text")
    .requestString()

10. File Download

let fileURL = try await Mesh()
    .setRequestMethod(.get)
    .setUrlHost("https://example.com")
    .setUrlPath("/files/document.pdf")
    .setDestination { _, _ in
        let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("document.pdf")
        return (dest, [.removePreviousFile, .createIntermediateDirectories])
    }
    .download()

11. Resumable Download

let fileURL = try await Mesh()
    .setUrlHost("https://example.com")
    .setUrlPath("/files/large-file.zip")
    .setDownloadType(.resume)
    .setResumeData(savedResumeData)  // from previous interrupted download
    .download()

12. Single File Upload (URL)

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload")
    .setUploadType(.file)
    .setFileURL(fileURL)
    .upload(of: UploadResult.self)

13. Single File Upload (Data)

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload")
    .setUploadType(.data)
    .setFileData(imageData)
    .upload(of: UploadResult.self)

14. Multipart Form Upload

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload/multi")
    .setUploadType(.multipart)
    .setAddformData(name: "file",
                    fileName: "photo.jpg",
                    fileData: imageData,
                    mimeType: "image/jpeg")
    .setAddformData(name: "description",
                    fileData: "My photo".data(using: .utf8))
    .upload(of: UploadResult.self)

15. Multipart with Pre-built UploadDatas

let uploads = [
    MultipleUpload.formData(name: "file1", fileName: "a.jpg", fileData: data1, mimeType: "image/jpeg"),
    MultipleUpload.formData(name: "file2", fileName: "b.pdf", fileURL: fileURL)
]

let result = try await Mesh()
    .setRequestMethod(.post)
    .setUrlHost("https://api.example.com")
    .setUrlPath("/upload/batch")
    .setUploadType(.multipart)
    .setUploadDatas(uploads)
    .setParameters(["userId": "123"])  // additional form fields
    .upload(of: BatchResult.self)

16. Custom Timeout

let result = try await Mesh()
    .setUrlHost("https://api.example.com")
    .setUrlPath("/slow-api")
    .setTimeout(60)  // 60 seconds
    .request(of: Response.self)

Global Configuration

Set these once at app launch (e.g., in AppDelegate):

// Enable network logging
Mesh.enableLog(.log)  // or .print

// Set global default headers
Mesh.setHeaders(["Authorization": "Bearer token", "App-Version": "1.0"])

// Set global default parameters
Mesh.setParameters(["platform": "ios", "sdk_version": "2.0"])

// Set global URL host
Mesh.setUrlHost("https://api.example.com")

Then per-request configuration only needs the path:

// Uses global headers, parameters, and urlHost
let result = try await Mesh()
    .setUrlPath("/weather")
    .request(of: Weather.self)

Resilient Codable Property Wrappers

Handle inconsistent API responses gracefully without decoding failures.

@Default Wrappers

Provide fallback values when fields are missing or invalid:

struct Response: Codable {
    @Default.True var isEnabled: Bool              // Missing → true
    @Default.False var isDeleted: Bool             // Missing → false
    @Default.EmptyString var name: String          // Missing → ""
    @Default.EmptyInt var count: Int               // Missing → 0
    @Default.EmptyArray var tags: [String]         // Missing → []
    @Default.EmptyDictionary var meta: [String: Int] // Missing → [:]
    @Default.Now var createdAt: Date               // Missing → Date()
}

@IgnoreError

Returns nil instead of throwing when a field fails to decode:

struct Response: Codable {
    @IgnoreError var description: String?   // Invalid type → nil (no crash)
    @IgnoreError var nested: NestedModel?   // Missing/malformed → nil
}

@ConvertToString

Accepts String, Int, or Double from JSON → converts to String?:

struct Response: Codable {
    @ConvertToString var version: String?  // "1.0", 1, or 1.0 → "1.0", "1", "1.0"
}

@ConvertToInt

Accepts Int, String, or Double from JSON → converts to Int?:

struct Response: Codable {
    @ConvertToInt var count: Int?  // 42, "42", or 42.9 → 42, 42, 42
}

@ConvertToDouble

Accepts Double, Int, Float, or String from JSON → converts to Double?:

struct Response: Codable {
    @ConvertToDouble var price: Double?  // 9.99, 10, "9.99" → 9.99, 10.0, 9.99
}

@ConvertToFloat

Accepts Float, Int, Double, or String from JSON → converts to Float?:

struct Response: Codable {
    @ConvertToFloat var rating: Float?  // 4.5, 5, "4.5" → 4.5, 5.0, 4.5
}

JSON Key Path Decoder

Extract nested JSON values without parsing the entire response:

// JSON: { "code": 200, "data": { "list": [{ "id": 1 }, { "id": 2 }] } }

// Extract a single nested object
let firstItem = try await Mesh()
    .setRequestMethod(.get)
    .setUrlPath("/items")
    .request(of: Item.self, modelKeyPath: "data.list.0")

// The KeyPath decoder also works directly:
let decoder = JSONDecoder.default
let item = try decoder.decode(Item.self, from: jsonData, keyPath: "data.list.0")
let items = try decoder.decodeArray([Item].self, from: jsonData, keyPath: "data.list")

The default decoder auto-configures:

  • keyDecodingStrategy = .convertFromSnakeCase (snake_case → camelCase)
  • dateDecodingStrategy = .iso8601

Combine + SwiftUI Integration

ObservableObject Pattern

class RequestModel: ObservableObject {
    @MainActor @Published var weather: Weather?
    @MainActor @Published var errorMessage: String?
    
    func fetchWeather() {
        Task {
            do {
                let result = try await Mesh()
                    .setRequestMethod(.get)
                    .setUrlHost("https://api.example.com")
                    .setUrlPath("/weather")
                    .request(of: Weather.self)
                
                await MainActor.run { self.weather = result }
            } catch {
                await MainActor.run { self.errorMessage = error.localizedDescription }
            }
        }
    }
}

SwiftUI Usage

struct WeatherView: View {
    @StateObject private var model = RequestModel()
    
    var body: some View {
        VStack {
            if let weather = model.weather {
                Text("\(weather.temperature)°C")
            }
            if let error = model.errorMessage {
                Text(error).foregroundColor(.red)
            }
        }
        .onAppear { model.fetchWeather() }
    }
}

UIKit + Combine Usage

class ViewController: UIViewController {
    private var model = RequestModel()
    private var cancellables: Set<AnyCancellable> = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        model.$weather
            .receive(on: RunLoop.main)
            .sink { weather in
                print("Weather: \(String(describing: weather))")
            }
            .store(in: &cancellables)
        
        model.fetchWeather()
    }
}

Error Handling

SwiftMesh normalizes common network errors into user-friendly NSError:

Error Code Description
NSURLErrorNotConnectedToInternet No internet connection
NSURLErrorTimedOut Request timeout
NSURLErrorCannotFindHost Host not found
NSURLErrorCannotConnectToHost Cannot connect to host
NSURLErrorNetworkConnectionLost Connection lost during request

All normalized errors return the message: "Unable to connect to the server"

Catch errors in your async code:

do {
    let result = try await Mesh()
        .setUrlPath("/api")
        .request(of: Response.self)
} catch let error as NSError {
    print("Error \(error.code): \(error.localizedDescription)")
}

RetryPolicy

Built-in retry with linear backoff (1s, 2s, 3s...):

// Default: 3 retries
let policy = RetryPolicy()

// Custom: 5 retries
let policy = RetryPolicy(maxRetryCount: 5)

// Use with request
let result = try await Mesh()
    .setUrlPath("/unstable-api")
    .setInterceptor(policy)
    .request(of: Response.self)

Logging

Enable at app launch:

// Using Apple's unified Logger (os.log)
Mesh.enableLog(.log)

// Using Swift print()
Mesh.enableLog(.print)

Output includes:

  • cURL command (for easy reproduction)
  • HTTP status code
  • Elapsed time
  • Pretty-printed JSON response

Configuration Method Reference

All methods are chainable (return Self):

Basic Configuration

Method Type Description
.setTimeout(_:) TimeInterval Request timeout in seconds (default: 15)
.setInterceptor(_:) RequestInterceptor? Retry policy / credential handler
.setRequestMethod(_:) HTTPMethod GET, POST, PUT, DELETE, etc.
.setHeads(_:) [String: String] Per-request headers
.setRequestEncoding(_:) ParameterEncoding URLEncoding, JSONEncoding, etc.
.setUrlHost(_:) String? Per-request URL host
.setUrlPath(_:) String? URL path
.setParameters(_:) [String: Any]? Request parameters

Download Configuration

Method Type Description
.setDownloadType(_:) DownloadType .download or .resume
.setDestination(_:) DownloadRequest.Destination File save location
.setResumeData(_:) Data? Resume data from interrupted download

Upload Configuration

Method Type Description
.setUploadType(_:) UploadType .file, .data, .stream, .multipart
.setFileURL(_:) URL? File URL for upload
.setFileData(_:) Data? File data for upload
.setStream(_:) InputStream? Input stream for upload
.setUploadDatas(_:) [MultipleUpload] Multipart form entries
.setAddformData(name:fileName:fileData:fileURL:mimeType:) Multiple Quick add form field

Global Static Methods

Method Description
Mesh.enableLog(_:) Enable network logging
Mesh.setHeaders(_:) Set global default headers
Mesh.setParameters(_:) Set global default parameters
Mesh.setUrlHost(_:) Set global default URL host

Enums Reference

DownloadType

Case Description
.download Standard download
.resume Resumable download (requires resumeData)

UploadType

Case Description
.file Upload from file URL
.data Upload from Data object
.stream Upload from InputStream
.multipart Multipart form upload

LogType

Case Description
.print Output via Swift print()
.log Output via Apple os.log

AI Prompt Templates

When using an AI coding assistant, use these templates:

"Make a GET request"

Use SwiftMesh to make a GET request to {URL} and decode the response into a {Model} struct. Use key path "{keyPath}" to extract nested data.

"Upload a file"

Use SwiftMesh to upload a {file/data/stream} to {URL} with multipart form field name "{name}" and MIME type "{mimeType}". Decode the response as {Model}.

"Download a file"

Use SwiftMesh to download a file from {URL} and save it to {destination}. Use resumable download if needed.

"Handle inconsistent API types"

Create a Codable struct for this JSON: {json}. Use @Default, @IgnoreError, and @ConvertTo* wrappers to handle missing or inconsistent types.