feat: domain purchase (v2 register) + domain get#70
Conversation
…and saved contacts
Extends the spec-generated domains-client and adds five domain commands:
- purchase: registers a domain via v2 POST /v2/customers/{customerId}/domains/register
(unlike v1, v2 authorizes card payments for OAuth users; customerId comes from
the token's `customer:` sub). Guards with --agree (consent) and --confirm
(charge), runs a per-TLD schema preflight, fetches legal agreements + price for
the v2 consent, and sources contacts from ~/.config/gddy/contacts.toml.
- contacts init: scaffolds a starter contacts.toml (all roles commented out).
- agreements / schema: read the legal agreements and per-TLD requirements.
- get: GET /v1/domains/{domain}, full details (all fields by default).
API failures now surface the response body plus a 402 payment-method hint, and
the global --debug flag appends the x-request-id (and the v2 register path).
The trim script (now a standalone trim-spec.py) adds the agreements, purchase,
purchase-schema, v2 register, and get operations; schema/get responses are taken
free-form to tolerate sparse/loosely-typed payloads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds end-to-end domain registration (via v2 register) plus single-domain lookup to the Rust CLI, along with supporting legal-agreement/schema helpers, local contact defaults, and the required spec/client updates.
Changes:
- Add
gddy domain purchase,domain get,domain agreements,domain schema, anddomain contacts initcommands (including consent gates, preflight checks, and improved API error formatting). - Introduce
contacts.tomlsupport for optional per-role default contacts used during purchase. - Extend the generated
domains-clientsurface area (spec trim + OpenAPI) and add client tests for agreements/purchase/schema/register/get.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| rust/src/main.rs | Wires in the new contacts module. |
| rust/src/domain/mod.rs | Implements new domain commands, consent/preflight helpers, and improved API error formatting. |
| rust/src/domain/guides/domain-purchase.md | Adds the long-form purchase guide referenced by command help/errors. |
| rust/src/contacts/mod.rs | Adds contacts.toml parsing + mapping to API request types and a starter template. |
| rust/domains-client/src/lib.rs | Adds client tests for newly included endpoints and request/response shapes. |
| rust/domains-client/scripts/trim-spec.py | New standalone spec-trimming script including get/agreements/purchase/schema/v2 register. |
| rust/domains-client/scripts/regenerate-spec.sh | Switches to the standalone trim script and updates regeneration flow comments/output. |
| rust/domains-client/openapi/domains.oas3.json | Updates the trimmed OpenAPI spec to include new endpoints and schemas. |
| .gitignore | Broadens build artifact ignore patterns to include Python __pycache__. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ion, doc comments - Use the documented output_schema type strings (`bool`/`[]string`, not `boolean`/`array`) for the `domain get` result so --help/--schema stay consistent. - Reject empty label/TLD in `registrable_tld` (`.com`, `example.`) up front, with a test, so purchase fails clearly instead of doing an invalid TLD lookup. - Correct the stale `Address` STRICT_DEFS comment and refresh the regenerate-spec.sh header + progress message to list the get/schema/v2-register operations the trim now keeps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, comment - `domain contacts init` now reports `created` vs `overwritten` based on whether the file already existed, not on the `--force` flag (--force on a missing file is a create). - In `domain purchase`, resolve/validate the credential (OAuth-only + customerId) before loading contacts.toml, so sso-key environments get the clear "requires OAuth" error instead of a misleading contacts parse/read error. - Clarify the country-uppercasing test comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`domain agreements` still formatted send failures via the generated client's Display, which drops the response body for UnexpectedResponse. Route it through `api_error` like the other new domain commands so it surfaces the API body (and the request id under --debug). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The price fetch from /v1/domains/available also reports availability, which we ignored — a taken domain (available=false, but a price present) would proceed to a guaranteed-failing paid v2 register. Bail with a clear "not available" message before building consent / registering. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Country codes are ASCII two-letter ISO codes; `to_ascii_uppercase()` avoids full-Unicode expansions (e.g. ß → SS) before `AddressCountry::try_from`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, single cred resolve - check_tld_requirements now recognizes required contact roles under both the v1 flat naming (contactRegistrant) and v2 nested naming (contacts.registrant / bare registrant), and SENDABLE_PURCHASE_FIELDS covers the v2 body fields (contacts, metadata). So the preflight stays correct now that purchase uses v2 register, regardless of which requirement-naming convention the schema uses. - Keep blocking on a missing required contact: omitting one does not reliably fall back to account defaults (e.g. .fun rejects a missing registrant). - Register a DomainSchemaResult output schema for `domain schema` so --schema/help document the projected `required` field. - `domain purchase` reuses its already-resolved credential via make_client_with_cred instead of resolving again inside make_client (single resolution, same token). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… fall back) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…p example - Contact::to_api now reports `encoding: UTF-8` when any contact field is non-ASCII (accented names/addresses) instead of always claiming ASCII, with a test; all-ASCII contacts still report ASCII. - Use the fully qualified `gddy domain agreements` in the --agree help text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| /// command needs — and the v2 path wants the bare uuid, so the `customer:` | ||
| /// prefix is stripped. A subject that isn't `customer:`-typed (or an empty one) | ||
| /// isn't a customer identity, so it's rejected with a clear error. | ||
| fn customer_id(cred: &Credential) -> Result<String> { |
There was a problem hiding this comment.
Nit: this is odd to see in the domain scope here, I think it'd be reasonable to put it in auth.rs instead? Though, perhaps domains is the only API surface that will require extracting and passing in the customer id.
There was a problem hiding this comment.
Not a publicly declared function, same for others like iso_datetime where reuse could become a possibility, but I think we move them if reuse becomes a need
What
Adds domain registration and single-domain lookup to the CLI, extending the spec-generated
domains-client:gddy domain purchase <domain>— registers a domain via v2POST /v2/customers/{customerId}/domains/register. v2 (unlike v1) authorizes credit-card payments for OAuth users;customerIdcomes from the token'scustomer:-prefixedsub. Guards:--agree(legal consent) and--confirm(the charge). Fails fast if the domain isn't available, runs a per-TLD schema preflight (blocks before paying when a required contact/field is missing), fetches legal agreements + price for the v2 consent, and sources contacts from~/.config/gddy/contacts.toml. Async: reportsstatus: submitted.gddy domain get <domain>—GET /v1/domains/{domain}; full details, all fields by default (listkeeps its abbreviated view).gddy domain agreements/domain schema— read a TLD's legal agreements and registration requirements.gddy domain contacts init— scaffolds a startercontacts.toml(all roles commented out, inert until edited).Notable details
gddy payments addhint. The global--debugflag appends thex-request-id(and, for register, the customer-scoped path) for support hand-offs.trim-spec.py; it adds the get / agreements / purchase / purchase-schema / v2-register operations.schemaandgetresponses are taken free-form to tolerate the upstream's loosely-typed / sparse (privacy-masked) payloads. Request-body types stay strict.purchasedeclares bothdomains.domain:read(the preflight/availability/agreements reads) anddomains.domain:create(register). No new scope beyonddomains.domain:create.contacts.toml; a required contact that's missing hard-blocks (omitting one does not reliably fall back to account defaults — e.g..fun). Contactencodingis reported asUTF-8when fields are non-ASCII, elseASCII.Refined during review
The TLD preflight recognizes required-contact naming under both the v1 flat (
contactRegistrant) and v2 nested (contacts.registrant) conventions, so it stays correct against the v2 register body;domain purchaseresolves its credential once and reuses it for the request;domain schemaregisters an output schema; and the availability/contact-encoding behaviors above were added.Testing
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings, andcargo test --workspaceall green (204 tests). Newhttpmockclient tests for agreements/purchase/schema/register/get; unit tests for the consent gates, TLD preflight (incl. v1/v2 naming),customer_idextraction, registrable-TLD validation,iso-datetimeformatting, the error formatter, and contact mapping/encoding. Verified end-to-end against thetestenvironment (a real.funregistration succeeded).🤖 Generated with Claude Code