Skip to content

feat: domain purchase (v2 register) + domain get#70

Open
jpage-godaddy wants to merge 9 commits into
rust-portfrom
domain-purchase
Open

feat: domain purchase (v2 register) + domain get#70
jpage-godaddy wants to merge 9 commits into
rust-portfrom
domain-purchase

Conversation

@jpage-godaddy

@jpage-godaddy jpage-godaddy commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

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 v2 POST /v2/customers/{customerId}/domains/register. v2 (unlike v1) authorizes credit-card payments for OAuth users; customerId comes from the token's customer:-prefixed sub. 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: reports status: submitted.
  • gddy domain get <domain>GET /v1/domains/{domain}; full details, all fields by default (list keeps its abbreviated view).
  • gddy domain agreements / domain schema — read a TLD's legal agreements and registration requirements.
  • gddy domain contacts init — scaffolds a starter contacts.toml (all roles commented out, inert until edited).

Notable details

  • Error UX: API failures surface the response body (not just status), plus a 402 → gddy payments add hint. The global --debug flag appends the x-request-id (and, for register, the customer-scoped path) for support hand-offs.
  • Spec trim: the inline trim logic moved to a standalone trim-spec.py; it adds the get / agreements / purchase / purchase-schema / v2-register operations. schema and get responses are taken free-form to tolerate the upstream's loosely-typed / sparse (privacy-masked) payloads. Request-body types stay strict.
  • Scopes: purchase declares both domains.domain:read (the preflight/availability/agreements reads) and domains.domain:create (register). No new scope beyond domains.domain:create.
  • Contacts: per-role defaults in contacts.toml; a required contact that's missing hard-blocks (omitting one does not reliably fall back to account defaults — e.g. .fun). Contact encoding is reported as UTF-8 when fields are non-ASCII, else ASCII.

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 purchase resolves its credential once and reuses it for the request; domain schema registers an output schema; and the availability/contact-encoding behaviors above were added.

Testing

cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace all green (204 tests). New httpmock client tests for agreements/purchase/schema/register/get; unit tests for the consent gates, TLD preflight (incl. v1/v2 naming), customer_id extraction, registrable-TLD validation, iso-datetime formatting, the error formatter, and contact mapping/encoding. Verified end-to-end against the test environment (a real .fun registration succeeded).

Base is rust-port (the Rust-port integration branch), not main.

🤖 Generated with Claude Code

…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>

Copilot AI 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.

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, and domain contacts init commands (including consent gates, preflight checks, and improved API error formatting).
  • Introduce contacts.toml support for optional per-role default contacts used during purchase.
  • Extend the generated domains-client surface 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.

Comment thread rust/src/domain/mod.rs
Comment thread rust/src/domain/mod.rs
Comment thread rust/src/domain/mod.rs
Comment thread rust/domains-client/scripts/trim-spec.py Outdated
Comment thread rust/domains-client/scripts/regenerate-spec.sh Outdated
Comment thread rust/domains-client/scripts/regenerate-spec.sh Outdated
…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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Comment thread rust/src/domain/mod.rs
Comment thread rust/src/contacts/mod.rs Outdated
Comment thread rust/src/domain/mod.rs Outdated
…, 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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Comment thread rust/src/domain/mod.rs Outdated
`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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.

Comment thread rust/src/domain/mod.rs
Comment thread rust/domains-client/openapi/domains.oas3.json
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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Comment thread rust/src/contacts/mod.rs Outdated
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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Comment thread rust/src/domain/mod.rs
Comment thread rust/src/domain/mod.rs
Comment thread rust/src/domain/mod.rs
Comment thread rust/src/domain/mod.rs Outdated
…, 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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Comment thread rust/src/domain/mod.rs Outdated
… fall back)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jpage-godaddy jpage-godaddy requested a review from Copilot June 18, 2026 20:42

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.

Comment thread rust/src/contacts/mod.rs
Comment thread rust/src/domain/mod.rs
…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>

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.

Comment thread rust/src/domain/mod.rs
/// 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> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

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.

4 participants