Skip to content

RefConstraint silently passes validation when $ref cannot be resolved #916

@DannyvdSluijs

Description

@DannyvdSluijs

Description

In all three draft-specific RefConstraint implementations (Draft06, Draft07, Draft2019), an unresolvable $ref causes validation to silently pass rather than reporting an error.

Affected Files

  • src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.php
  • src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php
  • src/JsonSchema/Constraints/Drafts/Draft2019/RefConstraint.php

Current Behaviour

All three files share the same pattern:

try {
    $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema);
} catch (\Exception $e) {
    return; // silently returns — validation passes as if $ref wasn't there
}

When $ref points to something that cannot be resolved — a typo in the pointer, a missing $defs entry, an unreachable remote schema — the exception is caught and discarded. check() returns without adding any validation error, so the value is considered valid.

Expected Behaviour

An unresolvable $ref should result in a validation error, not a silent pass. The JSON Schema specification does not treat an unresolvable $ref as a no-op.

Considerations

Before fixing this, a decision is needed on the failure mode:

  1. Add a validation error — catch the exception and call $this->addError(...) with a suitable ConstraintError (e.g. MISSING_ERROR or a new UNRESOLVABLE_REF error), so validation fails with a clear message.
  2. Re-throw the exception — let it propagate, which would surface as an uncaught exception unless the caller wraps in a try/catch. This is more disruptive but makes broken schemas impossible to ignore.
  3. Configurable behaviour — respect CHECK_MODE_EXCEPTIONS to decide between the two above, matching how other parts of the library handle the exceptions flag.

There is also a legitimate use case to consider: offline environments where remote schemas may genuinely be unreachable. Option 1 (validation error) is likely the most compatible approach as it preserves the current exception-free API surface while correctly flagging broken refs.

Steps to Reproduce

$validator = new \JsonSchema\Validator();
$data = new \stdClass();
$schema = json_decode('{"$schema": "http://json-schema.org/draft-07/schema#", "$ref": "#/$defs/nonExistent"}');
$validator->validate($data, $schema, \JsonSchema\Constraints\Constraint::CHECK_MODE_STRICT);
var_dump($validator->isValid()); // currently dumps bool(true) — should be bool(false)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions