Skip to content

Commit f1eb1a2

Browse files
authored
V2 (#42)
1 parent 593428b commit f1eb1a2

126 files changed

Lines changed: 7131 additions & 904 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/devcontainer.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
{
22
"name": "Swift",
3-
"image": "swift:5.10",
3+
"image": "swift:6.1",
44
"features": {
55
"ghcr.io/devcontainers/features/common-utils:2": {
66
"installZsh": "false",
77
"username": "vscode",
8-
"userUid": "1000",
9-
"userGid": "1000",
8+
"userUid": "1001",
9+
"userGid": "1001",
1010
"upgradePackages": "false"
1111
},
1212
"ghcr.io/devcontainers/features/git:1": {
1313
"version": "os-provided",
1414
"ppa": "false"
15-
}
15+
},
16+
"ghcr.io/devcontainers/features/node:1": { }
1617
},
1718
"runArgs": [
1819
"--cap-add=SYS_PTRACE",
@@ -29,15 +30,15 @@
2930
},
3031
// Add the IDs of extensions you want installed when the container is created.
3132
"extensions": [
32-
"sswg.swift-lang"
33+
"swiftlang.swift-vscode"
3334
]
3435
}
3536
},
3637
// Use 'forwardPorts' to make a list of ports inside the container available locally.
3738
// "forwardPorts": [],
3839

3940
// Use 'postCreateCommand' to run commands after the container is created.
40-
"postCreateCommand": "swift --version",
41+
"postCreateCommand": "./scripts/post_create_container.sh",
4142

4243
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
4344
"remoteUser": "vscode"

.github/workflows/swift-ubuntu.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
name: Swift Ubuntu
55

6-
on:
6+
on:
77
push:
88
branches:
99
- main
@@ -25,11 +25,21 @@ jobs:
2525
- name: Checkout
2626
uses: actions/checkout@v4
2727

28-
- name: Setup Swift
28+
- name: Install Dependencies
29+
run: sudo apt-get -y install libcurl4-openssl-dev pkg-config python3-lldb-13
30+
31+
- name: Install Swiftly
32+
run: |
33+
curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \
34+
tar zxf swiftly-$(uname -m).tar.gz && \
35+
./swiftly init --skip-install --assume-yes
36+
37+
. ~/.local/share/swiftly/env.sh
38+
echo "PATH=$PATH" >> $GITHUB_ENV
39+
40+
- name: Install Swift
2941
run: |
30-
curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash -s -- --disable-confirmation
3142
swiftly install ${{ matrix.swift }}
32-
swiftly use ${{ matrix.swift }}
3343
3444
- name: Get Swift version
3545
run: swift --version

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ DerivedData/
99
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
1010
.netrc
1111
.vscode
12+
.claude*

.mise.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[tools]
2+
swiftlint = "latest"
3+
claude = "latest"
4+
5+
[settings]
6+
experimental = true

.swiftlint.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
included:
2+
- Sources
3+
- Package.swift
4+
5+
excluded:
6+
- .build
7+
- Submodules
8+
9+
opt_in_rules:
10+
- array_init
11+
- attributes
12+
- closure_end_indentation
13+
- closure_spacing
14+
- empty_count
15+
- explicit_init
16+
- extension_access_modifier
17+
- fatal_error_message
18+
- first_where
19+
- let_var_whitespace
20+
- literal_expression_end_indentation
21+
- nimble_operator
22+
- operator_usage_whitespace
23+
- overridden_super_call
24+
- pattern_matching_keywords
25+
- private_outlet
26+
- prohibited_super_call
27+
- redundant_nil_coalescing
28+
- unneeded_parentheses_in_closure_argument
29+
- vertical_parameter_alignment_on_call
30+
31+
disabled_rules:
32+
- attributes
33+
- multiple_closures_with_trailing_closure
34+
- trailing_comma
35+
- vertical_parameter_alignment_on_call
36+
- void_return
37+
38+
custom_rules:
39+
disable_print:
40+
name: "print usage"
41+
regex: "((\\bprint)|(Swift\\.print))\\s*\\("
42+
message: "User app.logger or request.logger instead of print"
43+
severity: warning
44+
45+
# Default Rule Configuration
46+
type_name:
47+
min_length: 2
48+
49+
identifier_name:
50+
min_length: 2
51+
allowed_symbols: "_"
52+
excluded:
53+
- x
54+
- f
55+
- i
56+
57+
file_length:
58+
warning: 500
59+
error: 1000
60+
61+
function_body_length: 50
62+
63+
line_length: 200
64+
65+
cyclomatic_complexity:
66+
ignores_case_statements: true
67+
68+
large_tuple:
69+
warning: 6
70+
error: 10
71+
72+
nesting:
73+
type_level:
74+
warning: 2
75+
function_level:
76+
warning: 10

CLAUDE.md

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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

Comments
 (0)