Skip to content

feat(cube-cli): reintroduce cube validate as a Cloud data-model check - #11595

Open
paveltiunov wants to merge 3 commits into
masterfrom
claude/cube-validate-cli-command-pax4k7
Open

feat(cube-cli): reintroduce cube validate as a Cloud data-model check#11595
paveltiunov wants to merge 3 commits into
masterfrom
claude/cube-validate-cli-command-pax4k7

Conversation

@paveltiunov

Copy link
Copy Markdown
Member

Check List

  • Tests have been run in packages where changes have been made if available
  • Linter has been run for changed code
  • Tests for the changes have been added if not covered yet
  • Docs have been added / updated if required

Issue Reference this PR resolves

Linear: CUB-3782
Server side: cubedevinc/cubejs-enterprise#14215 — the endpoint this command calls. Merge that first; until it ships this command 404s.

Description of Changes Made

Brings validate back to the CLI, as a Cloud check rather than the local compile the old cubejs validate did.

cube validate 42                     # the deploy branch (production)
cube validate 42 --branch my-branch  # a specific branch
cube validate 42 --dev-mode          # the active dev-mode working copy

The compile happens where the model runs: the command calls GET /build/api/v1/deployments/{id}/data-model/validate, which asks the branch's own Cube runtime for /meta — the same call the console makes in dev mode. That checks the model against the deployment's real environment variables, drivers and dependencies, which a local compile cannot, and it's what makes --branch / --dev-mode meaningful: each is served by its own runtime. --dev-mode validates your uncommitted working copy, before you commit it.

It exits non-zero with the compiler's errors, one per line and prefixed with the file the compiler blamed, so it works as a CI gate:

✗ Data model on dev-pavel-my-branch failed to compile:
  model/cubes/orders.yml: Orders cube: unknown measure type not_a_measure_type
error: data model on dev-pavel-my-branch has 1 compilation error(s)

Errors go to stderr and --json prints the full report on stdout (valid, errors[], cubesCount), so the exit code gates the same way in both modes.

The legacy cubejs validate in packages/cubejs-cli is untouched — this is the new cube binary only.

docs-mintlify/reference/cli.mdx gains a "Validate the data model" section and a command-reference row.

cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test all pass (20 tests, including new coverage for the per-file error rendering).


Generated by Claude Code

The old `cubejs validate` compiled the model locally, which meant it could
only ever check what a local checkout plus a local install could express —
not the environment variables, drivers or dependencies the model actually
runs against.

`cube validate <deployment>` instead asks the branch's own Cube runtime for
`GET /cubejs-api/v1/meta`, the same call the console makes in dev mode, via
the new `GET /build/api/v1/deployments/{id}/data-model/validate` endpoint. So
the verdict is by construction the one that branch's API would give, and
naming the branch is what picks the runtime:

  cube validate 42                     # the deploy branch (production)
  cube validate 42 --branch my-branch  # a specific branch
  cube validate 42 --dev-mode          # the active dev-mode working copy

It exits non-zero with the compiler's errors, per file, so it works as a CI
gate; `--json` gives the same report machine-readably.

Ref CUB-3782
@paveltiunov
paveltiunov requested a review from keydunov as a code owner August 19, 2026 03:01
@mintlify

mintlify Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
cubed3 🟢 Ready View Preview Aug 19, 2026, 3:02 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions github-actions Bot added the rust Pull requests that update Rust code label Aug 19, 2026
@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @paveltiunov's task in 2m 59s —— View job


Review: cube validate — looks solid; 3 low/medium robustness nits on response-shape drift (0 blocking)

Full review — click to expand
  • Read PR diff (git diff origin/master...HEAD)
  • Compare against sibling commands and shared helpers (output, util::push, client, main.rs dispatch)
  • Review docs changes (docs-mintlify/reference/cli.mdx)
  • Check test coverage
  • Post inline comments

What's good

  • Wiring is exactly the house style: Args doc comment as long help (same as deploy.rs / logs.rs), util::push for the optional query param, output::field for stringifying, and all three dispatch sites in main.rs (Command, name(), run()) updated — the name() arm is easy to forget and drops the command out of telemetry, so good catch.
  • #[arg(long, conflicts_with = "dev_mode")] on --branch correctly uses clap 4's field-name arg id, so --branch x --dev-mode fails at parse time rather than sending an ambiguous request.
  • stdout/stderr split is right: --json keeps stdout a clean single JSON document while the human-readable failure list goes to stderr, and the bail! runs in both modes so the exit code gates identically. main.rs:304-307 turns it into exit(1).
  • Security: deployment is typed i64, so the path segment can't be injected; branch goes through the query-param vector and is encoded by reqwest. No auth logic bypassed — it goes through ctx.api()? like every other command. Nothing to flag.
  • Docs section in cli.mdx is accurate and appropriately short, and the command-reference row is in place.

Findings (all inline)

# Severity Where Issue
1 Medium validate.rs:30-38 Non-object errors[] entries (plain strings) render as blank lines — a failed validation with zero actionable output
2 Low validate.rs:58 Missing branchName in the response yields Data model on is valid
3 Low validate.rs:74-91 valid: false with empty errors prints failed to compile: followed by nothing, then has 0 compilation error(s)

All three are the same root theme: the response contract lives in a different repo (cubedevinc/cubejs-enterprise#14215) and this command reads it optimistically. None are blocking — worst case is a confusing message, never a false pass.

Other observations (no inline comment)

  • Telemetry noise. The bail! on an invalid model flows through main.rs:293-297, which emits an Error telemetry event and success: false. A CI gate firing as designed will therefore look identical to a CLI malfunction in the metrics. If you care about that distinction, an exit-code-only path (or an error property marking it expected) would separate "the CLI broke" from "the model didn't compile."
  • rust/cube-cli/README.md:129-154 has a command table introduced by "Every endpoint of the Console Server public API is covered" with no validate row. deploy is missing there too, so this is pre-existing drift rather than something this PR introduced — but adding a row is a one-liner if you're touching it.
  • Test coverage is limited to format_error, which is reasonable given the rest of command() is I/O-bound and the crate has no HTTP test harness. If findings 1 and 3 are addressed, the string-error case and the empty-errors case are both testable as pure functions and worth a line each.
  • Not verified locally: cargo test / cargo fmt --check / cargo clippy need approval I don't have in this environment, so I'm taking the PR description's "20 tests pass" at face value. The suggested diffs above are written to compile as drop-ins but haven't been run through rustc here.
• branch [`claude/cube-validate-cli-command-pax4k7`](https://github.com/cube-js/cube/tree/claude/cube-validate-cli-command-pax4k7)

Comment thread rust/cube-cli/src/commands/validate.rs
Comment thread rust/cube-cli/src/commands/validate.rs Outdated
Comment thread rust/cube-cli/src/commands/validate.rs Outdated
Review of the paired PR. All three are the same shape: the response contract
lives in cubedevinc/cubejs-enterprise and this command ships separately, so an
entry that doesn't match what it expects still has to print as something. A
blank line is the one output `validate` must never produce — printing the
errors IS the command.

- `format_error` renders a bare-string entry as itself and a half-filled
  object as whichever half it has, falling back to the entry's own JSON rather
  than the empty string `output::field` would give for both lookups.
- A response without `branchName` no longer yields "Data model on  is valid":
  it falls back to the branch the caller asked for, or a generic label for
  `--dev-mode`, where the personal `dev-…` name only exists server-side.
- `valid: false` with no errors said "failed to compile:" and then listed
  nothing, followed by "has 0 compilation error(s)". It now says the model
  could not be validated, which points at the runtime — where the answer is.
  Still fails closed: a report this command can't read is not evidence the
  model compiles.

Adds a `validate` row to the README's command table, and unit tests for the
new pure functions.
@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @paveltiunov's task in 1m 2s —— View job


Re-review of a721ee6: all 3 prior findings fixed, no new blocking issues (0 blocking, 1 cosmetic nit)

Full review — click to expand
  • Read the new commit (a721ee6) against my earlier findings
  • Verify the fixes by tracing output::field / stringify semantics
  • Review the new tests
  • Re-check and resolve my stale review threads

What changed

a721ee6 addresses every point from the previous pass, and does it more thoroughly than the suggestions I posted:

Prior finding Status
Non-object errors[] entries render as blank lines Fixedformat_error (validate.rs:37-51) now handles a bare string, a message-only object, a file-only object, and falls back to the entry's own JSON. The 4-arm match (file.is_empty(), message.is_empty()) covers strictly more shapes than the format!("{file}{message}") I suggested, which would still have produced an empty line for {"code": 7}.
Missing branchName yields on is valid Fixed — extracted into branch_label (validate.rs:60-71) with --dev-mode getting its own label, as suggested.
!valid with empty errors prints a header promising a list, then nothing Fixed — separate else if errors.is_empty() branch (:109-116) and a distinct bail! (:130-135).

I traced the semantics rather than trusting the tests: output::field returns "" both when the key is missing and when its value is null (output.rs:38, 50), so {"fileName": null, "message": "no sql"} correctly takes the (true, false) arm, and Value::Null takes (true, true)"null". The two new test fns (validate.rs:165-179, :189-208) assert exactly those boundary shapes, and the args() helper keeps branch_label testable without an HTTP harness. Coverage is now good for everything in this file that isn't I/O.

Remaining observations (nothing inline, nothing blocking)

  • Cosmetic double-report in the empty-errors path. In human mode a valid: false / no-errors response prints ✗ Data model on X could not be validated. and then error: data model on X could not be validated (the API reported a failure without any compilation errors). The stderr line adds nothing the bail! doesn't already say, unlike the error-list case where it heads a list. Dropping it would leave the parenthetical as the single, more informative line — purely a polish call, and the current output isn't wrong.
  • Telemetry noise (carried over from the last pass, unchanged): the bail! on an invalid model flows through main.rs:293-297, so a CI gate firing as designed emits an Error event indistinguishable from a CLI malfunction. Worth a follow-up if those metrics matter; not something to hold this PR for.
  • errors non-empty with valid: true is ignored (the success branch wins). Correct given the server owns the verdict, just noting it's a deliberate asymmetry.
  • README/docs — the validate row I flagged as missing is now in rust/cube-cli/README.md:133, and cli.mdx has both the section and the command-reference row. deploy is still absent from the README table, but that's pre-existing drift.

Not verified locally

cargo test / cargo fmt --check / cargo clippy require approval this environment doesn't grant, so I reviewed by reading. I'm taking the PR description's "20 tests pass" at face value; my correctness claims above come from tracing format_error and branch_label against output.rs, not from a run.

Housekeeping

Resolved all three of my prior review threads — the code each pointed at has been rewritten and the concerns no longer apply.

• branch [`claude/cube-validate-cli-command-pax4k7`](https://github.com/cube-js/cube/tree/claude/cube-validate-cli-command-pax4k7)

The empty-errors path printed a stderr line and then a bail! saying the same
thing, unlike the error-list case where the stderr line heads a list. Keeping
only the bail! leaves one line, and it's the more informative one — it says
the API reported a failure without compilation errors, which is what points
the operator at the runtime rather than the model.
@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @paveltiunov's task in 45s —— View job


Re-review of 7584cae: the double-report nit is fixed cleanly — approving on read, 0 blocking, 0 new findings

Full review — click to expand
  • Read the new commit (7584cae)
  • Re-check the full file state (validate.rs end-to-end)
  • Trace every branch of the four-way output matrix
  • Check existing review threads (all three already resolved — nothing to do)

What changed

7584cae takes the cosmetic point from my last pass: the valid: false / empty-errors path no longer prints a stderr line that only restates what the bail! says. The else if errors.is_empty() arm is gone and the remaining arm is else if !errors.is_empty() (validate.rs:109), so the header is printed only when it actually heads a list.

The rationale in the code comment (:122-125) is the right one to leave behind: the header exists to introduce a list, and the surviving line is strictly more informative because it names the thing to go look at (the runtime, not the model).

Verifying the output matrix

I traced all four human-mode states rather than trusting the diff shape:

valid errors stdout stderr
true ✓ Data model on X is valid (N cubes)
false non-empty header + one line per error, then error: … has N compilation error(s)
false empty error: data model on X could not be validated (the API reported a failure without any compilation errors) — one line
any any, --json full report the bail! line only

No arm can now emit a header with nothing under it, and no arm emits two lines saying the same thing. Exit code is unchanged in all failure states — main.rs:304-307 still turns the bail! into exit(1), including under --json, which is what makes the CI gate identical in both modes.

Nothing else in the file moved: format_error (:37-51), branch_label (:60-71) and the 4 tests (:146-204) are byte-identical to a721ee6, which I already reviewed and traced against output::field's missing-vs-null semantics. OwoColorize is still used (:113), so removing the other .red() call didn't orphan the import.

Carried-over observations (unchanged, none blocking)

  • Telemetry noise. Still the one thing I'd consider a follow-up: the bail! on an invalid model flows through main.rs:293-297, which emits an Error event with success: false. A CI gate firing exactly as designed is indistinguishable in metrics from the CLI malfunctioning. Only worth doing if those metrics are actually read.
  • errors non-empty with valid: true is ignored — deliberate, the server owns the verdict.
  • deploy is missing from the rust/cube-cli/README.md command table — pre-existing drift, not this PR's. validate is present at :133.

Housekeeping

All three of my earlier threads were already resolved in the previous round; no threads to resolve and no new inline comments to post — this commit introduced nothing to comment on.

Not verified locally

cargo test / cargo fmt --check / cargo clippy need approval this environment doesn't grant (the call was blocked), so this is a read-based review. The claims above come from tracing the control flow and output.rs, not from a run; I'm taking the PR description's passing-test claim at face value. CI on the branch is the authority there.

• branch [`claude/cube-validate-cli-command-pax4k7`](https://github.com/cube-js/cube/tree/claude/cube-validate-cli-command-pax4k7)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants