Skip to content
Back to skills

SwiftMesh AI Skill

ASecurity

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

  • 32 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added June 11, 2026
developmentswiftapi

Works with

  • api

Security analysis

A100/100

Pro scans all 16 files and shows the line behind each finding

Scanned June 11, 2026

npx -y skills add zjinhu/SwiftMesh --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of SwiftMesh AI Skill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for SwiftMesh AI Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zjinhu-swiftmesh-ai-skill/badge)](https://www.skillsdirectory.com/skills/zjinhu-swiftmesh-ai-skill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
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

```swift
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:

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

```swift
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

```swift
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

```swift
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

```swift
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`:

```swift
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

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

### 9. Raw String Response

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

### 10. File Download

```swift
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

```swift
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)

```swift
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)

```swift
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

```swift
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

```swift
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

```swift
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`):

```swift
// 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:

```swift
// 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:

```swift
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:

```swift
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?`:

```swift
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?`:

```swift
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?`:

```swift
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?`:

```swift
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:

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

```swift
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

```swift
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

```swift
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:

```swift
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...):

```swift
// 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:

```swift
// 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.
```

Files in this skill

  • .DS_Store6 KB
  • Example/Package.swift129 B
  • Image/Package.swift127 B
  • Image/logo.png78 KB
  • Package.resolved327 B
  • Package.swift1.1 KB
  • README_ZH.md12.7 KB
  • SKILL.md15.9 KB
  • SwiftMesh.podspec736 B
  • fastlane/Fastfile1.8 KB
  • fastlane/Package.swift130 B
  • fastlane/README.md682 B
  • fastlane/fastlane_pod2.1 KB
  • fastlane/fastlane_tag801 B
  • fastlane/release_tag1.3 KB
  • fastlane/report.xml3.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.