Skip to content

chore: modernize, optimize, and document the RDAP library#49

Merged
wolveix merged 13 commits into
masterfrom
chore/modernize-and-optimize
Jun 23, 2026
Merged

chore: modernize, optimize, and document the RDAP library#49
wolveix merged 13 commits into
masterfrom
chore/modernize-and-optimize

Conversation

@wolveix

@wolveix wolveix commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

Modernizes the codebase, optimizes the decoder hot paths, and adds documentation across exported APIs.

  • Performance: cache decoder field plans, slim DecodeData, and optimize string hot paths
  • Docs: document exported and printer functions, fix gofmt drift
  • Tooling: replace go.yml with a build.yml workflow, expand .gitignore for editor/IDE/OS artifacts
  • Maintenance: update dependencies and run go fix for modernization

Changes

  • New benchmark_test.go covering decode paths
  • Reworked decoder.go with cached field plans (largest change)
  • Expanded print.go and request.go
  • Dependency bumps in go.mod / go.sum

Test plan

  • go test ./...
  • go vet ./...
  • Review benchmark results

Summary by CodeRabbit

  • New Features

    • Added comprehensive GitHub Actions CI/CD workflows for automated testing and code quality checks.
  • Bug Fixes

    • Improved error handling and standardized error messages across the codebase.
    • Enhanced file permission security with stricter access controls.
  • Documentation

    • Added API documentation comments for exported functions and methods.
    • Created test fixtures for CLI validation.
  • Tests

    • Added end-to-end CLI test suite with golden file validation.
    • Added performance benchmarks for critical operations.
  • Chores

    • Updated Go version requirement to 1.25.0 and modernized code patterns.
    • Configured golangci-lint for consistent code quality standards.

wolveix added 5 commits June 5, 2026 17:27
- Bump go directive to 1.25 and update module dependencies
  (kingpin v2.4.0, httpmock v1.4.1, x/crypto v0.52.0)
- Replace interface{} with any throughout
- Use reflect.Pointer over the deprecated reflect.Ptr
- Adopt maps.Copy and slices.Contains from the standard library
- Use strings.Builder in DecodeData.String()
- Replace the Go CI workflow with a dedicated build workflow
… paths

Resolve each struct type's decode plan (where every RDAP field lives, and
where the DecodeData field lives) once per Go type and cache it, instead of
re-running reflection and struct-tag parsing on every struct decode.
decodeStruct binds the cached plan to the instance and resolves only the
fields present in the JSON.

Slim down DecodeData, which is allocated for every decoded struct:
- values now references the parsed source map directly instead of copying it.
- isKnown shares the plan's read-only set of field names instead of being
  rebuilt per instance.
- notes and overrideKnownValue are allocated lazily (most decodes need
  neither) rather than eagerly.

Together these cut decoding of a representative nested domain response by
~53% in time, ~63% in allocated bytes, and ~49% in allocation count. What
remains is dominated by encoding/json parsing into map[string]any and the
reflection that fills the structs. The original BUG: invariant checks are
preserved; they now run once when a type's plan is built.

Also optimize three string hot paths:
- escapePath: fast-path the common no-escape case (returns the input with
  zero allocation) and pre-size the buffer otherwise.
- Printer.cleanString: skip the rune-by-rune strings.Map scan when the
  input contains no control runes.
- VCard.Tel/Fax: call VCardProperty.Values once per property instead of
  twice; Fax now uses slices.Contains for consistency with Tel.

Add benchmark_test.go covering the decode path and string hot paths as
regression guards.
Add doc comments to every previously-undocumented exported function across
the package (Client.Do, the Error()/String() methods, the bootstrap
sort.Interface sorters, Response.ToWhoisStyleResponse, and the sandbox/test
helpers), and complete the comments for the Printer's print* routines.

Also fix pre-existing gofmt drift in bootstrap/cache/memory_cache.go,
bootstrap/question.go, and test/http_test.go.
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a142e20e-90a6-4fe1-ab7d-e2dca5240c8b

📥 Commits

Reviewing files that changed from the base of the PR and between b6c7332 and cbf2bd8.

📒 Files selected for processing (5)
  • .golangci.yml
  • decoder_test.go
  • print_test.go
  • request_test.go
  • vcard_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • .golangci.yml
  • print_test.go

📝 Walkthrough

Walkthrough

PR migrates interface{} to any across the decoder, vcard, response, and client packages. The JSON decoder replaces per-decode reflective field discovery with a sync.Map-cached struct plan storing field index paths and shared known-field sets; DecodeData methods move to pointer receivers. escapePath gains a no-alloc fast path; cleanString short-circuits on strings without control characters; printUnknowns produces deterministic output via sorted keys. Bootstrap registry error strings adopt %w wrapping and lowercase casing; disk cache permissions tighten to 0o600/0o750. Three GitHub Actions workflows (build, lint, tests) replace the old go.yml; a .golangci.yml v2 config is added. CLI golden tests and decode/escape/vcard benchmarks are introduced.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately describes the core changes: modernization (Go 1.25+, interface{} to any), optimization (cached decoder plans, fast-path string handling), and documentation (GoDoc comments added across multiple files).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/modernize-and-optimize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 204ab3fa-e565-4545-abcf-826605294d79

📥 Commits

Reviewing files that changed from the base of the PR and between 8fa7e50 and a63633d.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • .github/workflows/build.yml
  • .github/workflows/go.yml
  • .gitignore
  • benchmark_test.go
  • bootstrap/answer.go
  • bootstrap/asn_registry.go
  • bootstrap/cache/memory_cache.go
  • bootstrap/cache/registry_cache.go
  • bootstrap/client.go
  • bootstrap/net_registry.go
  • bootstrap/question.go
  • client.go
  • client_error.go
  • decode_data.go
  • decoder.go
  • decoder_test.go
  • go.mod
  • print.go
  • request.go
  • response.go
  • sandbox/file.go
  • test/file.go
  • test/http.go
  • test/http_test.go
  • vcard.go
  • vcard_test.go
💤 Files with no reviewable changes (1)
  • .github/workflows/go.yml

Comment thread .github/workflows/build.yml
Comment thread .github/workflows/build.yml
Comment thread bootstrap/answer.go Outdated
Comment thread go.mod
wolveix added 3 commits June 20, 2026 22:50
Add golangci-lint config (.golangci.yml) tuned for this library, plus
Lint and Tests GitHub Actions workflows with tightened token scopes.

Resolve all linter findings, including genuine fixes:
- propagate context via http.NewRequestWithContext (noctx)
- fix &*r.Server aliasing that mutated the shared Server URL (SA4001)
- lowercase error strings and drop redundant "Error" prefixes (ST1005)
- add explicit json tags to the bootstrap unmarshal struct (musttag)
- replace test init() with explicit var initialization
- tighten cache dir/file permissions to 0750/0600
- drop always-nil error returns from the decodeX helpers

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 28535d5a-c898-4bf3-ae53-24019730f0b1

📥 Commits

Reviewing files that changed from the base of the PR and between 226d6e9 and 272ecf4.

📒 Files selected for processing (37)
  • .github/workflows/lint.yml
  • .github/workflows/tests.yml
  • .golangci.yml
  • benchmark_test.go
  • bootstrap/asn_registry.go
  • bootstrap/asn_registry_test.go
  • bootstrap/cache/disk_cache.go
  • bootstrap/cache/disk_cache_test.go
  • bootstrap/cache/memory_cache.go
  • bootstrap/cache/memory_cache_test.go
  • bootstrap/client.go
  • bootstrap/client_test.go
  • bootstrap/dns_registry.go
  • bootstrap/dns_registry_test.go
  • bootstrap/file.go
  • bootstrap/file_test.go
  • bootstrap/net_registry.go
  • bootstrap/net_registry_test.go
  • bootstrap/service_provider_registry.go
  • bootstrap/service_provider_registry_test.go
  • cli.go
  • client.go
  • client_error.go
  • client_test.go
  • decoder.go
  • decoder_test.go
  • example_test.go
  • print.go
  • print_test.go
  • request.go
  • request_test.go
  • sandbox/file.go
  • test/file.go
  • test/http.go
  • test/http_test.go
  • vcard.go
  • vcard_test.go
💤 Files with no reviewable changes (2)
  • bootstrap/client_test.go
  • test/file.go
✅ Files skipped from review due to trivial changes (8)
  • bootstrap/asn_registry_test.go
  • bootstrap/dns_registry_test.go
  • bootstrap/service_provider_registry_test.go
  • bootstrap/net_registry_test.go
  • request_test.go
  • example_test.go
  • bootstrap/cache/disk_cache_test.go
  • sandbox/file.go
🚧 Files skipped from review as they are similar to previous changes (9)
  • test/http_test.go
  • vcard_test.go
  • benchmark_test.go
  • client.go
  • bootstrap/cache/memory_cache.go
  • request.go
  • print.go
  • vcard.go
  • decoder.go

Comment thread .github/workflows/lint.yml
Comment thread .github/workflows/lint.yml
Comment thread .github/workflows/tests.yml
wolveix added 5 commits June 23, 2026 13:29
Drive RunCLI for each output mode (text/json/whois/raw) plus version and
error paths, comparing exit code, stdout, and stderr against committed
golden files. Network-backed cases use an in-process fixture server via
--server and an in-memory cache, so nothing touches the network or the
real filesystem. Regenerate with `go test -run TestRunCLI -update`.

Also sort unknown-field output in the printer (printUnknowns and the
nested-map case of printUnknown) so CLI output is deterministic.

Raises RunCLI coverage from 0% to ~48%.
go.mod declares a 1.25.0 floor while CI ran only 1.26, which could mask
bugs that surface on the declared minimum. Test on both the floor and the
latest release; lint continues on the latest toolchain only.
Distribute the benchmarks from benchmark_test.go into the _test.go file of
the package code each one exercises, matching this repo's per-file test
layout (idiomatic Go; the standard library colocates benchmarks too):

  BenchmarkDecodeDomain                  -> decoder_test.go
  BenchmarkEscapePathClean/Dirty         -> request_test.go
  BenchmarkCleanStringClean/Dirty        -> print_test.go
  BenchmarkVCardValues                   -> vcard_test.go

BenchmarkDecodeDomain now loads its fixture via test.LoadFile for
consistency (outside the timed loop, so results are unaffected).
The uint->int conversion in Printer.indent for strings.Repeat cannot
realistically overflow (indent depth is bounded by RDAP object nesting).
An in-code //nolint kept getting stripped by editor tooling, so suppress
it in .golangci.yml instead, which is stable across edits.
@wolveix

wolveix commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

Benchmarks: master vs chore/modernize-and-optimize

Same benchmark suite (go test -bench . ./..., colocated in the package _test.go files) run against both
implementations via benchstat. master has no benchmarks, so the identical benchmark code was applied to a throwaway master worktree (with only its go.mod language version bumped 1.191.22 so the for range b.N syntax compiles; no implementation code touched).

Environment: goos: darwin, goarch: arm64, cpu: Apple M3 Pro, -count=10, -benchmem.

Time (sec/op)

Benchmark master branch Δ
DecodeDomain 62.62µ ± 1% 29.95µ ± 7% −52.16% (p=0.000)
EscapePathClean 33.29n ± 2% 8.99n ± 3% −72.99% (p=0.000)
EscapePathDirty 87.47n ± 2% 79.94n ± 6% −8.61% (p=0.000)
CleanStringClean 293.1n ± 2% 93.31n ± 1% −68.16% (p=0.000)
CleanStringDirty 74.70n ± 2% 79.59n ± 2% +6.54% (p=0.000)
VCardValues 12.99n ± 5% 12.99n ± 3% ~ (p=0.670)
geomean 193.1n 113.0n −41.51%

Memory (B/op)

Benchmark master branch Δ
DecodeDomain 65.01Ki ± 0% 23.94Ki ± 0% −63.18% (p=0.000)
EscapePathClean 16.00 ± 0% 0.00 ± 0% −100.00% (p=0.000)
EscapePathDirty 128.0 ± 0% 160.0 ± 0% +25.00% (p=0.000)
CleanStringClean 0 0 ~
CleanStringDirty 32.00 ± 0% 32.00 ± 0% ~
VCardValues 16.00 ± 0% 16.00 ± 0% ~

Allocations (allocs/op)

Benchmark master branch Δ
DecodeDomain 966.0 ± 0% 489.0 ± 0% −49.38% (p=0.000)
EscapePathClean 1.000 ± 0% 0.000 ± 0% −100.00% (p=0.000)
EscapePathDirty 2.000 ± 0% 2.000 ± 0% ~
CleanStringClean 0 0 ~
CleanStringDirty 1.000 ± 0% 1.000 ± 0% ~
VCardValues 1.000 ± 0% 1.000 ± 0% ~

Summary

  • Headline: decoding a realistic domain response is ~2x faster (−52% time, −63% memory, −49% allocs) thanks to the cached field-plan.
  • Common hot paths (escapePath/cleanString clean inputs) are ~3x faster and now zero-alloc.
  • Minor regressions on the uncommon "dirty" paths: EscapePathDirty uses +25% B/op (128→160) and CleanStringDirty is +6.5% slower; both the rare escaping branches, a deliberate trade for the common-case wins.
  • VCardValues is unchanged (noise).
Raw benchstat output
goos: darwin
goarch: arm64
pkg: github.com/openrdap/rdap
cpu: Apple M3 Pro
                    │ /tmp/bench_master.txt │        /tmp/bench_branch.txt        │
                    │        sec/op         │   sec/op     vs base                │
DecodeDomain-12                 62.62µ ± 1%   29.95µ ± 7%  -52.16% (p=0.000 n=10)
EscapePathClean-12             33.290n ± 2%    8.993n ± 3%  -72.99% (p=0.000 n=10)
EscapePathDirty-12              87.47n ± 2%   79.94n ± 6%   -8.61% (p=0.000 n=10)
CleanStringClean-12            293.10n ± 2%   93.31n ± 1%  -68.16% (p=0.000 n=10)
CleanStringDirty-12             74.70n ± 2%   79.59n ± 2%   +6.54% (p=0.000 n=10)
VCardValues-12                  12.99n ± 5%   12.99n ± 3%        ~ (p=0.670 n=10)
geomean                         193.1n        113.0n       -41.51%

                    │ /tmp/bench_master.txt │           /tmp/bench_branch.txt           │
                    │         B/op          │     B/op      vs base                     │
DecodeDomain-12              65.01Ki ± 0%     23.94Ki ± 0%   -63.18% (p=0.000 n=10)
EscapePathClean-12             16.00 ± 0%        0.00 ± 0%  -100.00% (p=0.000 n=10)
EscapePathDirty-12             128.0 ± 0%       160.0 ± 0%   +25.00% (p=0.000 n=10)
CleanStringClean-12            0.000 ± 0%       0.000 ± 0%         ~ (p=1.000 n=10) ¹
CleanStringDirty-12            32.00 ± 0%       32.00 ± 0%         ~ (p=1.000 n=10) ¹
VCardValues-12                 16.00 ± 0%       16.00 ± 0%         ~ (p=1.000 n=10) ¹
geomean                                   ²                 ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

                    │ /tmp/bench_master.txt │          /tmp/bench_branch.txt          │
                    │       allocs/op       │ allocs/op   vs base                     │
DecodeDomain-12                966.0 ± 0%     489.0 ± 0%   -49.38% (p=0.000 n=10)
EscapePathClean-12             1.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
EscapePathDirty-12             2.000 ± 0%     2.000 ± 0%         ~ (p=1.000 n=10) ¹
CleanStringClean-12            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
CleanStringDirty-12            1.000 ± 0%     1.000 ± 0%         ~ (p=1.000 n=10) ¹
VCardValues-12                 1.000 ± 0%     1.000 ± 0%         ~ (p=1.000 n=10) ¹
geomean                                   ²               ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

@wolveix wolveix merged commit 5313e3c into master Jun 23, 2026
5 checks passed
@wolveix wolveix deleted the chore/modernize-and-optimize branch June 23, 2026 13:33
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.

1 participant