Skip to content

Fix issues found during the connection-reuse review - #5

Merged
jonathan-kosgei merged 3 commits into
mainfrom
fix-noticed-issues
Nov 3, 2026
Merged

jonathan-kosgei merged 3 commits into
mainfrom
fix-noticed-issues

Conversation

@jonathan-kosgei

@jonathan-kosgei jonathan-kosgei commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #4, fixing the items it listed under "Noticed, not changed" that are defects or gaps: an explicit JSON null failed decoding in every response type except TimeZone, the client repeated its request and decoding code four times, and the publish workflow skipped the formatting and lint gates that ci.yml applies. Additive only: no public symbol renamed or removed, no field type changed, no version change.

Changes

  • An explicit null decodes to the field's default in every response type. IpInfo, Asn, Company, Carrier, Language, Currency, Threat, ThreatScores and Blocklist now use the null_as_default deserializer that TimeZone already had, on every field that is not an Option. A key sent as null reads "", false, 0, 0.0 or an empty list instead of failing the whole lookup with a decode error; Option fields already read None. Field types are unchanged and values of the wrong type ("latitude": "north", "asn": 15169) are still rejected. Documented in the crate docs and the README "Response Types" section. Tests: five new decoding tests in tests/types.rs (every model from an all-null document; Threat flags, lists and scores; IpInfo scalars, lists and nested objects; nulls scattered through the anycast fixture with the other values intact; wrong types still rejected) and a wiremock test in tests/client.rs where /carrier, /asn and /time_zone answer with nulls.
  • One GET helper in the client. lookup_field no longer carries its own copy of the request, status check and decoding. get is generic over the decoded type and serves the full lookups, the raw single-field lookup and the typed sub-resources; the status check and JSON decoding moved to decode_response, which bulk uses too. No behaviour change: same query parameters, same Error::Api mapping. A new test pins lookup_field's error mapping (a 403 with a JSON message becomes Error::Api { status: 403, .. }).
  • publish.yml runs the same gates as ci.yml. The toolchain step installs the rustfmt and clippy components, and cargo fmt --check and cargo clippy --all-targets -- -D warnings run before cargo test and cargo publish, so a release cannot ship code that CI would reject.

Not changed

  • #[non_exhaustive] on the response structs: it would break every downstream exhaustive struct literal and pattern, a breaking change that does not belong in a patch release.
  • Client-side validation of fields= names: the API validates them, and a local list would reject fields added server-side before the crate learns about them.
  • reqwest's system-proxy feature stays off: hyper-util's matcher reads HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY without it, and the feature only adds macOS and Windows system settings.

Verification

Local, rustc/cargo 1.97.0, run before the push:

  • cargo fmt --check: clean.
  • cargo clippy --all-targets -- -D warnings: clean (this also compiles examples/lookup.rs).
  • cargo test: tests/client.rs 30 passed (28 before), tests/types.rs 13 passed (8 before), doc-tests 3 passed, 0 failed.
  • The new null-decoding tests fail against the previous src/types.rs (invalid type: null, expected a string) and pass with this change; the wrong-type test passes on both.
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps: clean, the new intra-doc links resolve.
  • CI (ci.yml) on this pull request: run 41639251359, job test: cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test all succeeded on the first attempt (conclusion success).

🤖 Generated with Claude Code

Only TimeZone tolerated a key sent as null. Every other non-Option field
of IpInfo, Asn, Company, Carrier, Language, Currency, Threat,
ThreatScores and Blocklist failed the whole response with a decode
error, because #[serde(default)] only covers a missing key. Apply the
existing null_as_default deserializer to all of them, so a null reads
as "", false, 0, 0.0 or an empty list. Field types are unchanged and
values of the wrong type are still rejected.

Document the rule in the crate docs and the README, and add decoding
tests for every model plus a client-level sub-resource test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
lookup_field carried its own copy of the request, status check and body
decoding that get_typed already had, and get and bulk repeated the same
status check and decoding a third and fourth time. Make get generic over
the decoded type and use it for the full lookups, the raw single-field
lookup and the typed sub-resources; move the status check and JSON
decoding into decode_response, which bulk uses as well. Behaviour is
unchanged: same query parameters, same Error::Api mapping. A test pins
lookup_field's error mapping.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
publish.yml ran cargo test and cargo publish on a release without the
formatting and lint gates that ci.yml applies to pull requests and
pushes to main, so a release could ship code that CI would reject. Add
the rustfmt and clippy components to the toolchain step and run
cargo fmt --check and cargo clippy --all-targets -- -D warnings before
cargo test, mirroring ci.yml.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jonathan-kosgei
jonathan-kosgei merged commit 0981d0e into main Nov 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants