gflights 0.3.1

Unofficial async Rust client for the Google Flights web API — search flights, price graphs, and booking offers.
Documentation
# gflights — Claude instructions

## Workspace layout

```
google-flights-rs/
├── src/                    Rust library + CLI
│   ├── bin/cli/            CLI subcommands
│   ├── parsers/            Request builders & response parsers
│   └── requests/           ApiClient, Config, retry logic
├── gflights-py/            Python bindings (pyo3 + maturin)
│   ├── src/lib.rs          Rust extension (_gflights)
│   ├── gflights/           Python package (re-exports, stubs, types)
│   └── tests/              Python test suite
├── benches/                Criterion benchmarks
└── tests/                  Rust integration / live API tests
```

---

## Git workflow

- **Never commit directly to `master`** — always use a feature or fix branch.
- Branch naming: `feat/<topic>`, `fix/<topic>`, `chore/<topic>`.
- After every feature or fix is complete, run `/verify` to confirm the change works end-to-end at the CLI / Python surface before merging. The `--profile dev` build is sufficient for verification.

---

## Before every commit — run locally first

Always verify locally before pushing. CI runs the same checks and failing there wastes time.

### Rust crate

```sh
cargo fmt                                        # format (required — CI blocks on diff)
cargo clippy --all-targets -- -D warnings        # lint (zero warnings policy)
cargo test --lib                                 # 152 unit tests
cargo test --bin gflights                        # 13 CLI tests
cargo test --doc                                 # doc tests
cargo build --benches                            # ensure benchmarks still compile
```

### Python bindings (run from `gflights-py/`)

```sh
cd gflights-py
python -m maturin develop                        # rebuild extension after Rust changes
.venv/Scripts/pytest.exe tests/test_import.py tests/test_types.py tests/test_errors.py -v
```

All offline tests must pass before pushing.

---

## Live / integration tests

These hit the real Google Flights API.  They are skipped unless the
`RUN_LIVE_TESTS` environment variable is set to a non-empty value.

### Rust
```sh
RUN_LIVE_TESTS=1 cargo test --test live_api
```

### Python
```sh
cd gflights-py
RUN_LIVE_TESTS=1 .venv/Scripts/pytest.exe tests/test_live.py -v
```

---

## Test coverage

Keep line coverage **≥ 80%** for the Rust crate.

```sh
cargo install cargo-tarpaulin          # one-time
cargo tarpaulin --out Stdout           # check coverage
```

Current baseline: **84%** overall (parsers 84–99%; `api.rs` ~26% — network code, accepted).
If coverage drops below 80%, add tests before merging.

---

## Examples parity rule

Every user-facing **action** (something a user runs to get a result) must have a
corresponding example in `examples/`. Technical / infrastructure features
(proxy, user-agent rotation, retry, rate-limiting) do **not** need an example —
they are exercised through the action examples and the live tests.

| Action | Example file |
|---|---|
| Flight search + offers + booking URL | `examples/flights.rs` |
| Price graph | `examples/graph.rs` |
| Date grid | `examples/date_grid.rs` |
| Cheapest dates (one-way + round-trip) | `examples/cheapest_dates.rs` |
| Booking offers + URL resolution | `examples/offer.rs` |
| Multi-city search | `examples/multi_city.rs` |
| Explore destinations | `examples/explore.rs` |
| Flight deals | `examples/deals.rs` |

When adding a new public **action**, add or update the relevant example. All examples guard network calls behind `RUN_LIVE=1` so `cargo test --examples` passes offline.

```sh
cargo build --examples                   # must compile clean
RUN_LIVE=1 cargo run --example <name>    # smoke-test with network
```

---

## Python bindings parity rule

Whenever `src/` (the Rust crate) changes, update `gflights-py/` to match:

| Rust change | Bindings update needed |
|---|---|
| New public method / field / type | Expose in `gflights-py/src/lib.rs`; add to `gflights/_gflights.pyi` |
| Renamed / removed API | Mirror in bindings |
| New `Config` option or filter | Add parameter to the affected Python method(s) |
| New response field | Expose on the relevant Python data class |
| Behaviour change | Update affected Python tests |

The Rust crate and Python bindings must stay in sync at all times.

---

## Building the Python extension

```sh
cd gflights-py
uv venv --python 3.11 .venv            # one-time
uv pip install maturin pytest pytest-asyncio
python -m maturin develop              # build + install into .venv
```

After any change to `gflights-py/src/lib.rs`, re-run `maturin develop` before running Python tests.

---

## Security & dependency hygiene

```sh
cargo audit                            # check for CVEs (runs in CI)
```

Zero CVE policy — fix or justify any advisory before merging.

---

## Benchmarks

```sh
cargo bench                            # must be run from the project root (test_files/ paths)
```

Benchmarks are in `benches/parse.rs`. They use fixtures from `test_files/`.
Do not move or rename fixtures without updating the benchmark.

---

## Publishing (Rust crate)

`cargo publish --dry-run` must be clean before tagging a release.
The crate is live at https://crates.io/crates/gflights.