Skip to content

cargo.json: DetailedDependency checks that one property is present, but cargo requires one source #6394

Closed

Description

@simin-yuan

Summary

In src/schemas/json/cargo.json, definitions.DetailedDependency asserts minProperties: 1 and nothing else. Cargo requires a dependency to carry a source — one of version, path, git or workspace. A dependency that has properties but no source satisfies the schema and is rejected by cargo.

Minimal reproduction

Given Cargo.toml:

[package]
name = "probe"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { features = ["derive"] }

Against the published schema:

$ python -m check_jsonschema --schemafile cargo.json Cargo.toml
ok -- validation done

Against cargo (1.97.1):

$ cargo metadata --no-deps --format-version 1
error: failed to parse manifest at `.../Cargo.toml`

Caused by:
  dependency (serde) specified without providing a local path, Git repository, version, or workspace dependency to use

Where the constraint stops one step short

minProperties: 1 is satisfied by any single property, and features, optional, default-features, branch, tag, rev, package and the rest are all properties. So {features = ["derive"]} validates.

The four things cargo accepts as a source, taken from its own error message above, each work on their own:

declared schema cargo
{version = "1"} ok ok
{path = "./sub"} ok ok
{git = "https://github.com/a/b"} ok ok
{features = ["derive"]} ok rejected
{branch = "main"} ok rejected
{optional = true} ok rejected
{default-features = false} ok rejected

I checked workspace = true, registry = "custom" and registry-index = "..." on their own as well; all three are rejected by cargo in a manifest with no [workspace], and workspace is named as a valid source in the error text, so I did not treat those three rows as evidence either way.

Why this is worth fixing

Forgetting the version while keeping something else is a common edit — you add features, lose version, and the editor stays quiet until cargo build. That is the case the schema is best placed to catch, since the editor is where the mistake is made.

Suggested fix

minProperties: 1 becomes redundant once the sources are expressed:

"anyOf": [
  {"required": ["version"]},
  {"required": ["path"]},
  {"required": ["git"]},
  {"required": ["workspace"]}
]

anyOf and required are both fine in draft-07, which this file declares.

How it came up

I wrote a small mutation tester for validators — it mutates inputs a checker accepts and reports which mutations still get through. Run against this schema, it deleted the source from a dependency that validated and the schema still said ok. The tool is https://github.com/simin-yuan/greencheck (MIT, no dependencies); the reproduction above stands on its own without it.

Activity

  1. added theissue type on Sep 23, 2026
  2. simin-yuan commented on Sep 25, 2026

    @simin-yuan
    ContributorAuthor

    Follow-up: the report above quoted cargo output. I have now re-run the whole reproduction against the real toolchain on this machine (cargo is installed here but not on PATH, which is why an earlier check wrongly concluded it was unavailable). Doing it for real changed one thing I suggested, so I am correcting that below.

    Environment: cargo 1.97.1 (c980f4866 2026-06-30), rustc 1.97.1 (8bab26f4f 2026-07-14); check-jsonschema 0.38.0; schema as published at https://www.schemastore.org/cargo.json, which declares draft-07.

    Each row is one package with a src/lib.rs and a single [dependencies] entry, run through cargo metadata --no-deps --format-version 1 and through check-jsonschema --schemafile cargo.json Cargo.toml.

    dependency entry cargo schema
    serde = { version = "1" } ok ok
    serde = { version = "1", features = ["derive"] } ok ok
    regex = { git = "https://github.com/rust-lang/regex.git" } ok ok
    regex = { git = "...", version = "1" } ok ok
    regex = { path = "../regex", version = "1" } ok ok
    serde = { features = ["derive"] } rejected ok
    serde = { branch = "main" } rejected ok
    serde = { optional = true } rejected ok
    serde = { default-features = false } rejected ok
    serde = { registry = "custom" } rejected ok
    serde = { registry-index = "https://example.com/index" } rejected ok
    regex = { git = "...", path = "../regex" } rejected, different message ok

    For the first rejected group cargo says exactly what the report quotes:

    dependency (serde) specified without providing a local path, Git repository, version, or workspace dependency to use

    The last row is refused for a different reason:

    dependency (regex) specification is ambiguous. Only one of git or path is allowed.

    Correction. The anyOf I proposed still accepts { git = "...", path = "../regex" }, which cargo refuses, so it closes only half the gap. Both halves are needed: at least one source, and at most one of git / path:

    "allOf": [
      { "anyOf": [ {"required": ["version"]}, {"required": ["path"]}, {"required": ["git"]}, {"required": ["workspace"]} ] },
      { "not": { "required": ["git", "path"] } }
    ]

    With those in place minProperties: 1 has nothing left to do. I have not tested this against the repository test files, and I did not look at the other places DetailedDependency is referenced, so treat it as a starting point rather than a ready patch.

    One measurement I would rather state than leave implied: inside a real [workspace] whose root declares workspace.dependencies.serde, the entry serde = { workspace = true } parses, and so do variants that add version, features, optional or default-features. The earlier row calling workspace = true rejected was only ever about a manifest with no workspace, which the report flagged at the time. I am reporting what I measured, not claiming which combinations are normative.

    Also worth noting: registry and registry-index on their own are not sources. Cargo rejects them with the same message as features, and that message names only path, git, version and workspace.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions