---
alwaysApply: false
---
# API Authoring Guidelines (VibeviewerAPI)

## Goals
- Unify API naming, directories, abstractions, dependency injection, and decoding patterns
- Keep all APIs in a single module `VibeviewerAPI` to enforce isolation and modularity
- Standardize `DecodableTargetType` and the `HttpClient.decodableRequest(_:)` usage (async/await only) on top of Moya/Alamofire

## Hard rules
- API targets must be declared with `struct` (no `enum`/case-style targets)
- Use async/await-only decoding; callback-based styles are forbidden
- Separate API declarations from model declarations:
  - API Targets/Services → `VibeviewerAPI`
  - Data models/aggregations → `VibeviewerModel`
- Views/upper layers must use `Service` protocols via dependency injection, and must not call API targets or `HttpClient` directly
- The API module only exposes `Service` protocols and default implementations; API targets, networking details, and common header configuration remain internal

## Dependencies & imports
- API module imports only:
  - `Foundation`
  - `Moya`
  - `Alamofire` (used via `HttpClient`)
  - `VibeviewerModel`
- Never import UI frameworks in the API module (`SwiftUI`/`AppKit`/`UIKit`)

## Naming conventions
- Targets: Feature name + `API`, e.g., `YourFeatureAPI`
- Protocols: `YourFeatureService`
- Default implementations: `DefaultYourFeatureService`
- Models: `YourFeatureResponse`, `YourFeatureDetail`, etc.

## Directory structure (VibeviewerAPI)
```text
VibeviewerAPI/
  Sources/VibeviewerAPI/
    Mapping/
      ... DTOs & Mappers
    Plugins/
      RequestHeaderConfigurationPlugin.swift
      RequestErrorHandlingPlugin.swift
      SimpleNetworkLoggerPlugin.swift
    Service/
      MoyaProvider+DecodableRequest.swift
      HttpClient.swift                  # Unified Moya provider & session wrapper
      HttpClientError.swift
    Targets/
      CursorGetMeAPI.swift              # internal target
      CursorUsageAPI.swift              # internal target
      CursorTeamSpendAPI.swift          # internal target
    CursorService.swift                 # public protocol + default implementation (service only)
```

## Target and decoding conventions
- Targets conform to `DecodableTargetType`:
  - `associatedtype ResultType: Decodable`
  - `var decodeAtKeyPath: String? { get }` (default `nil`)
  - Implement `baseURL`, `path`, `method`, `task`, `headers`, `sampleData`
  - Avoid overriding `validationType` unless necessary

Example:
```swift
import Foundation
import Moya
import VibeviewerModel

struct UserProfileDetailAPI: DecodableTargetType {
    typealias ResultType = UserProfileResponse

    let userId: String

    var baseURL: URL { APIConfig.baseURL }
    var path: String { "/users/\(userId)" }
    var method: Moya.Method { .get }
    var task: Task { .requestPlain }
    var headers: [String: String]? { APIHeadersBuilder.basicHeaders(cookieHeader: nil) }
    var sampleData: Data { Data("{\"id\":\"1\",\"name\":\"foo\"}".utf8) }
}
```

## Service abstraction & dependency injection
- Expose protocol + default implementation (expose services only; hide networking details)
- The default `public init(decoding:)` must not leak internal protocol types; provide `internal init(network:decoding:)` for test injection

```swift
import Foundation
import Moya
import VibeviewerModel

public protocol UserProfileService {
    func fetchDetail(userId: String) async throws -> UserProfileResponse
}

public struct DefaultUserProfileService: UserProfileService {
    private let network: NetworkClient
    private let decoding: JSONDecoder.KeyDecodingStrategy

    // Business-facing: do not expose internal NetworkClient abstraction
    public init(decoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) {
        self.network = DefaultNetworkClient()
        self.decoding = decoding
    }

    // Test injection: available within the API module (same package or @testable)
    init(network: any NetworkClient, decoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) {
        self.network = network
        self.decoding = decoding
    }

    public func fetchDetail(userId: String) async throws -> UserProfileResponse {
        try await network.decodableRequest(
            UserProfileDetailAPI(userId: userId),
            decodingStrategy: decoding
        )
    }
}
```

> Note: `DefaultNetworkClient`, the `NetworkClient` protocol, and the concrete `HttpClient` implementation details remain `internal` and are not exposed.

## View usage (dependency injection)
Views must not call API targets or `HttpClient` directly. Use injected services instead:
```swift
import VibeviewerAPI
import VibeviewerModel

let service: UserProfileService = DefaultUserProfileService()
let model = try await service.fetchDetail(userId: "1")
```

## Error handling & logging
- Enable `SimpleNetworkLoggerPlugin` by default to log requests/responses
- Enable `RequestErrorHandlingPlugin` by default:
  - Timeouts/offline → unified handling
  - Customizable via strategy protocols

## Testing & mock conventions
- Within the `VibeviewerAPI` module, inject a `FakeNetworkClient` via `internal init(network:decoding:)` to replace real networking
- Provide `sampleData` for each target; prefer minimal realistic JSON to ensure robust decoding
- Use `@testable import VibeviewerAPI` to access internal symbols when external tests are required

## Alignment with modular architecture (architecture.mdc)
- Do not import UI frameworks in the API module
- Expose only `Service` protocols and default implementations; hide targets and networking details
- Dependency direction: `VibeviewerModel` ← `VibeviewerAPI` ← `VibeviewerFeature`
- Strict “one file, one type/responsibility”; clear feature aggregation; one-way dependencies


