Skip to content

refactor(cli): consolidate api.rs HTTP boilerplate #12

Description

@I-am-nothing

Surfaced during /simplify on #11 — skipped from that PR to keep scope tight, tracked here for a follow-up.

Three related cleanups in src/api.rs that would naturally land together:

1. Extract bounded body read

MAX_RESPONSE_BYTES = 64 * 1024 is declared in src/api.rs:12 and again in src/auth/mod.rs. The pattern `resp.take(MAX_RESPONSE_BYTES).read_to_end(...)` is reimplemented 4 times (3 in api.rs, 1 in auth/mod.rs).

Lift the constant + helper into `src/http.rs`:
```rust
pub(crate) const MAX_RESPONSE_BYTES: u64 = 64 * 1024;
pub(crate) fn read_capped(resp: reqwest::blocking::Response) -> std::io::Result<Vec> { ... }
```

2. Consolidate response status branching

`api::me`, `api::get_module`, and `api::create_module` each spell out the same tail:

  • `is_success` → decode JSON
  • `UNAUTHORIZED`/`FORBIDDEN` → `ApiError::Unauthenticated`
  • 4xx with platform error envelope → `ApiError::Server`
  • everything else → `ApiError::Unexpected`

Worth a `decode_response<T: DeserializeOwned>(resp, allow_404: bool) -> Result<Option, ApiError>` shared helper. `get_module` is the odd one out (404 → `Ok(None)`); the `allow_404` flag captures that.

3. Reuse one `reqwest::blocking::Client` per command

Today each `api::*` builds its own client via `http::client(Duration::from_secs(15))`. A single `module init` invocation builds 3 clients (`me` + `get_module` + `create_module`). Each client construction does rustls + connection pool init.

`src/auth/mod.rs::exchange_code` already takes `&Client` from the caller — pick that pattern and have `api::*` accept `&Client` too. Then the command wires one client through the call chain.

Why now skipped

PR #11 was already covering the user-visible bugs (404 routing, missing pre-flight, plain-text UX). These three are internal cleanup that don't change behavior. They're worth doing as one focused PR after #11 merges.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions