|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +OpenWeatherKit is a Swift wrapper around the WeatherKit REST API, bringing native Swift WeatherKit functionality to platforms Apple doesn't currently support (particularly Linux). The API mirrors Apple's WeatherKit as closely as possible. |
| 8 | + |
| 9 | +## Build and Test Commands |
| 10 | + |
| 11 | +### Building |
| 12 | +```bash |
| 13 | +swift build |
| 14 | +``` |
| 15 | + |
| 16 | +### Running Tests |
| 17 | +```bash |
| 18 | +swift test |
| 19 | +``` |
| 20 | + |
| 21 | +### Running a Single Test |
| 22 | +```bash |
| 23 | +swift test --filter <TestClassName>.<testMethodName> |
| 24 | +# Example: swift test --filter OpenWeatherKitTests.testWeather |
| 25 | +``` |
| 26 | + |
| 27 | +### Platform-Specific Testing |
| 28 | +The test suite has conditional compilation for Apple platforms vs Linux: |
| 29 | +- Apple platforms: Tests include CoreLocation-based geocoding |
| 30 | +- Linux: Tests require explicit countryCode and timezone parameters |
| 31 | + |
| 32 | +## Architecture |
| 33 | + |
| 34 | +### Core Request Flow |
| 35 | + |
| 36 | +1. **WeatherService** (public API) - Entry point for all weather requests |
| 37 | + - Accepts `LocationProtocol` (latitude/longitude) |
| 38 | + - Variadic methods support 1-6 different `WeatherQuery<T>` datasets in a single call |
| 39 | + - On Apple platforms: automatically geocodes location to get country code and timezone |
| 40 | + - On Linux: requires explicit `countryCode` and `timezone` parameters |
| 41 | + |
| 42 | +2. **NetworkClient** (internal) - HTTP layer |
| 43 | + - Builds URLs via `Route` enum |
| 44 | + - Handles JWT bearer token authentication |
| 45 | + - Uses `URLSession` on Apple platforms, `AsyncHTTPClient` on Linux |
| 46 | + - Fetches data in parallel using `TaskGroup` when multiple queries are requested |
| 47 | + - Returns `WeatherProxy` which aggregates partial results |
| 48 | + |
| 49 | +3. **WeatherProxy** (internal) - Result aggregation |
| 50 | + - Container for all possible weather data types |
| 51 | + - Supports combining multiple partial proxies (for parallel fetches) |
| 52 | + - Maps from API models to public models |
| 53 | + |
| 54 | +4. **WeatherQuery<T>** (public) - Type-safe dataset requests |
| 55 | + - Generic query structure with static factory methods (`.current`, `.daily`, `.hourly`, etc.) |
| 56 | + - Contains both the query type and a closure to extract results from `WeatherProxy` |
| 57 | + - Supports updating with country codes for alerts/availability queries |
| 58 | + |
| 59 | +### Platform Differences |
| 60 | + |
| 61 | +**Apple Platforms** (`#if canImport(CoreLocation)`): |
| 62 | +- Use `URLSession` for networking |
| 63 | +- Include `Geocoder` for automatic country code/timezone resolution |
| 64 | +- Support simplified API without explicit country code |
| 65 | + |
| 66 | +**Linux** (`#if os(Linux)`): |
| 67 | +- Use `AsyncHTTPClient` from swift-server |
| 68 | +- Require explicit `countryCode` and `timezone` on all requests |
| 69 | +- `WeatherService` includes `shutdown()` method to clean up HTTP client |
| 70 | + |
| 71 | +### Data Flow |
| 72 | + |
| 73 | +``` |
| 74 | +API Response (APIWeather/APIForecastDaily/etc.) |
| 75 | + ↓ (via extension +Map.swift files) |
| 76 | +WeatherProxy (aggregates partial results) |
| 77 | + ↓ (via WeatherQuery.result closure) |
| 78 | +Public Models (CurrentWeather/Forecast<DayWeather>/etc.) |
| 79 | +``` |
| 80 | + |
| 81 | +### Internal vs Public Separation |
| 82 | + |
| 83 | +- **Internal/Models**: API response models prefixed with `API*` (e.g., `APIWeather`, `APICurrentWeather`) |
| 84 | +- **Internal/Extensions**: Mapping logic from API models to public models (`+Map.swift` files) |
| 85 | +- **Public**: User-facing types matching Apple's WeatherKit API |
| 86 | + |
| 87 | +### Key Protocols |
| 88 | + |
| 89 | +- **LocationProtocol**: Abstraction for any type with `latitude` and `longitude` |
| 90 | +- **Client**: Protocol for HTTP clients (implemented by `URLSession` wrapper and `AsyncHTTPClient` wrapper) |
| 91 | + |
| 92 | +## Testing |
| 93 | + |
| 94 | +Tests use a `MockClient` that returns predefined JSON responses from `MockData`. The mock client: |
| 95 | +- Accepts an `Include` set to control which datasets to return |
| 96 | +- Simulates API responses without real network calls |
| 97 | +- On Apple platforms, uses `Geocoder.mock` for testing geocoding |
| 98 | + |
| 99 | +## Dependencies |
| 100 | + |
| 101 | +- **Swift 5.9+** minimum |
| 102 | +- **No dependencies** on Apple platforms (uses `URLSession`) |
| 103 | +- **AsyncHTTPClient** dependency on Linux (conditionally added via `#if os(Linux)` in Package.swift) |
| 104 | + |
| 105 | +## JWT Authentication |
| 106 | + |
| 107 | +The library does NOT generate JWTs. Users must provide a closure that returns a valid JWT string when initializing `WeatherService.Configuration`. The README recommends using Vapor's `jwt-kit` for JWT generation. |
| 108 | + |
| 109 | +Required JWT claims: |
| 110 | +- `exp` (expiration) |
| 111 | +- `iat` (issued at) |
| 112 | +- `iss` (issuer - Team ID) |
| 113 | +- `sub` (subject - Service Identifier) |
| 114 | +- Must be signed with ES256 using the private key from Apple Developer Portal |
| 115 | +- Must include Key ID in header |
| 116 | + |
| 117 | +## API Endpoint Structure |
| 118 | + |
| 119 | +Base URL: `https://weatherkit.apple.com` |
| 120 | + |
| 121 | +- Weather: `/api/v2/weather/{language}/{latitude}/{longitude}?dataSets=...&timezone=...` |
| 122 | +- Availability: `/api/v2/availability/{latitude}/{longitude}?country={countryCode}` |
| 123 | + |
| 124 | +## Date Handling |
| 125 | + |
| 126 | +- JSON responses use Unix epoch timestamps (`secondsSince1970`) |
| 127 | +- JSONDecoder configured with `.dateDecodingStrategy = .secondsSince1970` |
| 128 | +- Date extensions provide utilities like `.hoursFromNow(24)` and `.daysFromNow(10)` |
| 129 | + |
| 130 | +## Coding Conventions |
| 131 | + |
| 132 | +- `@usableFromInline` on internal types/methods that are called from `@inlinable` public APIs |
| 133 | +- Extensive use of `@inlinable` on public API surface for performance |
| 134 | +- Sendable conformance throughout for Swift 6 compatibility |
| 135 | +- TextCaseCoding for snake_case/camelCase conversion between API and Swift |
| 136 | + |
| 137 | +## SwiftLint Integration |
| 138 | + |
| 139 | +### Automatic Linting Hook |
| 140 | + |
| 141 | +This repository includes a PostToolUse hook that automatically runs SwiftLint when Claude Code edits Swift files. The hook: |
| 142 | +- Runs after every Edit or Write operation on Swift files |
| 143 | +- Automatically fixes correctable violations (trailing whitespace, formatting issues, etc.) |
| 144 | +- Reports remaining violations back to Claude for resolution |
| 145 | +- Only processes Swift files in `Sources/`, `Tests/`, or `Package.swift` |
| 146 | +- Completes within 15 seconds (timeout protection) |
| 147 | + |
| 148 | +### Setup Requirements |
| 149 | + |
| 150 | +**Required:** SwiftLint 0.50.0 or later must be installed: |
| 151 | +```bash |
| 152 | +brew install swiftlint |
| 153 | +``` |
| 154 | + |
| 155 | +**Activation:** Copy the example settings to enable the hook: |
| 156 | +```bash |
| 157 | +cp .claude/settings.example.json .claude/settings.local.json |
| 158 | +``` |
| 159 | + |
| 160 | +The hook is configured in `.claude/settings.local.json` (gitignored for local flexibility). |
| 161 | + |
| 162 | +### Hook Behavior |
| 163 | + |
| 164 | +**When you edit a Swift file:** |
| 165 | +1. Hook receives the file path from Claude Code |
| 166 | +2. Validates the file is a Swift file in target directories |
| 167 | +3. Runs `swiftlint --fix` to auto-correct violations |
| 168 | +4. Runs `swiftlint lint` to check for remaining violations |
| 169 | +5. If violations remain: Reports them to Claude with file:line:column references and blocks the edit |
| 170 | +6. If no violations: Completes silently |
| 171 | + |
| 172 | +**Example violation report:** |
| 173 | +``` |
| 174 | +SwiftLint found 2 violation(s) in Sources/OpenWeatherKit/Public/Weather.swift: |
| 175 | +Sources/OpenWeatherKit/Public/Weather.swift:42:1: warning: Line should be 200 characters or less (line_length) |
| 176 | +Sources/OpenWeatherKit/Public/Weather.swift:55:10: error: Print statement must not be used (custom_rules) |
| 177 | +``` |
| 178 | + |
| 179 | +### Performance Impact |
| 180 | + |
| 181 | +- Adds approximately 0.2-0.6 seconds per Swift file edit |
| 182 | +- Runs only on Swift files (non-Swift files pass through instantly) |
| 183 | +- Timeout protection prevents indefinite blocking |
| 184 | + |
| 185 | +### Disabling the Hook |
| 186 | + |
| 187 | +To disable SwiftLint integration: |
| 188 | +```bash |
| 189 | +rm .claude/settings.local.json |
| 190 | +``` |
| 191 | + |
| 192 | +Or comment out the hook configuration in the settings file. |
| 193 | + |
| 194 | +### Missing SwiftLint |
| 195 | + |
| 196 | +If SwiftLint is not installed, the hook will: |
| 197 | +- Exit gracefully with a warning message |
| 198 | +- NOT block edits |
| 199 | +- Suggest installation with `brew install swiftlint` |
0 commit comments