Skip to content

Latest commit

 

History

History
765 lines (597 loc) · 22.4 KB

File metadata and controls

765 lines (597 loc) · 22.4 KB

Migration Guide

Upgrading from 0.45.x to 0.46.0

Registry construction is now explicit: add shared schemas first, then call prepare() to build a reusable registry. Validators no longer take ownership of that registry; pass it by reference with with_registry(&registry). ValidationOptions::with_resource and ValidationOptions::with_resources were removed in favor of building a Registry first. For cases with multiple shared schemas, extend([...]) is the batch form of add(...).

// Old (0.45.x)
use jsonschema::{Registry, Resource};

// Inline shared schema
let validator = jsonschema::options()
    .with_resource(
        "https://example.com/schema",
        Resource::from_contents(shared_schema),
    )
    .build(&schema)?;

// Multiple shared schemas
let validator = jsonschema::options()
    .with_resources([
        (
            "https://example.com/schema-1",
            Resource::from_contents(schema_1),
        ),
        (
            "https://example.com/schema-2",
            Resource::from_contents(schema_2),
        ),
    ].into_iter())
    .build(&schema)?;

// Prebuilt registry
let registry = Registry::try_from_resources([
    (
        "https://example.com/schema",
        Resource::from_contents(shared_schema),
    ),
])?;
let validator = jsonschema::options()
    .with_registry(registry)
    .build(&schema)?;

// New (0.46.0)
use jsonschema::Registry;

// Shared registry + borrowed validator build
let registry = Registry::new()
    .add("https://example.com/schema", shared_schema)?
    .prepare()?;
let validator = jsonschema::options()
    .with_registry(&registry)
    .build(&schema)?;

// Multiple shared schemas
let registry = Registry::new()
    .extend([
        ("https://example.com/schema-1", schema_1),
        ("https://example.com/schema-2", schema_2),
    ])?
    .prepare()?;
let validator = jsonschema::options()
    .with_registry(&registry)
    .build(&schema)?;

Upgrading from 0.38.x to 0.39.0

Custom keyword API simplified

The Keyword::validate signature has been simplified. Path information (instance_path and schema_path) is now filled in automatically, so you only need to provide the error message.

Keyword::validate signature:

// Old (0.38.x)
fn validate<'i>(
    &self,
    instance: &'i Value,
    location: &LazyLocation,
) -> Result<(), ValidationError<'i>>;

// New (0.39.0)
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>>;

Creating errors:

// Old (0.38.x)
ValidationError::custom(schema_path, instance_path, instance, message)

// New (0.39.0) - just the message, paths are filled in automatically
ValidationError::custom("expected a string")

// For factory errors (invalid schema values)
ValidationError::schema("expected true")

Updated implementation example:

use jsonschema::{Keyword, ValidationError};
use serde_json::Value;

struct MyValidator;

impl Keyword for MyValidator {
    fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
        if !instance.is_string() {
            return Err(ValidationError::custom("expected a string"));
        }
        Ok(())
    }

    fn is_valid(&self, instance: &Value) -> bool {
        instance.is_string()
    }
}

Upgrading from 0.37.x to 0.38.0

WASM: Validator is now Send + Sync

The Validator type is now Send + Sync on WASM targets, restoring the expected behavior that was inadvertently broken in 0.34.0. This allows Validator to be used in static variables and shared across async contexts on WASM.

If you implemented custom Keyword or Format validators on WASM that rely on non-thread-safe types (like Rc or RefCell), you'll need to update them to use thread-safe alternatives (Arc, Mutex, etc.):

use std::sync::Arc;

// Old (0.37.x on WASM) - non-thread-safe types were allowed
use std::rc::Rc;
struct MyKeyword {
    data: Rc<SomeData>,
}

// New (0.38.x) - must be Send + Sync on all platforms
struct MyKeyword {
    data: Arc<SomeData>,
}

Note: The Retrieve and AsyncRetrieve traits still have relaxed Send + Sync bounds on WASM, so retrievers can continue to use non-thread-safe types.

Upgrading from 0.36.x to 0.37.0

ValidationError is now opaque

All ValidationError fields are private. Replace error.instance, error.instance_path, error.schema_path, and error.kind with the corresponding accessor calls:

let instance = error.instance();
let kind = error.kind();
let instance_path = error.instance_path();
let schema_path = error.schema_path();

ErrorIterator is now a struct

ErrorIterator used to be a type alias to Box<dyn ValidationErrorIterator<'_>> and is now a struct wrapping that iterator.

Upgrading from 0.35.x to 0.36.0

Removal of Validator::apply, Output, and BasicOutput

The legacy apply() API and its BasicOutput/OutputUnit structures have been removed in favor of the richer Validator::evaluate interface that exposes the JSON Schema Output v1 formats (flag/list/hierarchical) directly.

use serde_json::json;

// Old (0.35.x)
let output = validator.apply(&instance).basic();
match output {
    BasicOutput::Valid(units) => println!("valid: {units:?}"),
    BasicOutput::Invalid(errors) => println!("errors: {errors:?}"),
}

// New (0.36.0)
let evaluation = validator.evaluate(&instance);
if evaluation.flag().valid {
    println!("valid");
}
let list = serde_json::to_value(evaluation.list())?;
let hierarchical = serde_json::to_value(evaluation.hierarchical())?;

Because evaluate() materializes every evaluation step so it can provide the structured outputs, it always walks the full schema tree. If you only need a boolean result, continue to prefer is_valid or validate.

The serialized JSON now matches the JSON Schema Output v1 specification and its companion schema. For example, evaluating an array against a schema with prefixItems and items produces list output like:

{
  "valid": false,
  "details": [
    {"valid": false, "evaluationPath": "", "schemaLocation": "", "instanceLocation": ""},
    {
      "valid": false,
      "evaluationPath": "/items",
      "instanceLocation": "",
      "schemaLocation": "/items",
      "droppedAnnotations": true
    },
    {
      "valid": false,
      "evaluationPath": "/items",
      "instanceLocation": "/1",
      "schemaLocation": "/items"
    },
    {
      "valid": false,
      "evaluationPath": "/items/type",
      "instanceLocation": "/1",
      "schemaLocation": "/items/type",
      "errors": {"type": "\"oops\" is not of type \"integer\""}
    },
    {
      "valid": true,
      "evaluationPath": "/prefixItems",
      "instanceLocation": "",
      "schemaLocation": "/prefixItems",
      "annotations": 0
    },
    {
      "valid": true,
      "evaluationPath": "/prefixItems/0",
      "instanceLocation": "/0",
      "schemaLocation": "/prefixItems/0"
    },
    {
      "valid": true,
      "evaluationPath": "/prefixItems/0/type",
      "instanceLocation": "/0",
      "schemaLocation": "/prefixItems/0/type"
    },
    {
      "valid": true,
      "evaluationPath": "/type",
      "instanceLocation": "",
      "schemaLocation": "/type"
    }
  ]
}

If you need to inspect annotations or errors programmatically without serializing to JSON, use the new evaluation.iter_annotations() and evaluation.iter_errors() helpers.

Upgrading from 0.34.x to 0.35.0

Custom meta-schemas require explicit registration

Schemas with custom/unknown $schema URIs now require their meta-schema to be registered before building validators. Custom meta-schemas automatically inherit the draft-specific behavior of their underlying draft by walking the meta-schema chain. Validators always honor an explicitly configured draft (e.g., via ValidationOptions::with_draft), so overriding the draft is still the highest priority and bypasses auto-detection and the registry check intentionally.

// Old (0.34.x) - would fail with unclear error
let schema = json!({
    "$schema": "http://example.com/custom",
    "type": "object"
});
let validator = jsonschema::validator_for(&schema)?;

// New (0.35.x) - explicit registration required
use jsonschema::{Registry, Resource, Draft};

let meta_schema = json!({
    "$id": "http://example.com/custom",
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        "https://json-schema.org/draft/2020-12/vocab/core": true,
        "https://json-schema.org/draft/2020-12/vocab/validation": true,
    }
});

let registry = Registry::try_from_resources(
    [("http://example.com/custom", Resource::from_contents(meta_schema))]
)?;

let validator = jsonschema::options()
    .with_registry(registry)
    .build(&schema)?;

Draft Resolution: Custom meta-schemas inherit draft-specific behavior from their underlying draft. For example, a custom meta-schema based on Draft 7 will preserve Draft 7 semantics (ignoring $ref siblings, validating formats by default). The validator walks the meta-schema chain to determine the appropriate draft. To override this behavior, use .with_draft() to explicitly set a draft version.

Removed meta::try_is_valid and meta::try_validate

The try_* variants have been removed. The behavior has changed:

  • Old (0.34.x): try_is_valid and try_validate returned Result<_, ReferencingError> for unknown $schema values
  • New (0.35.x): is_valid panics for unknown $schema values, validate returns ValidationError
// Old (0.34.x)
match jsonschema::meta::try_is_valid(&schema) {
    Ok(is_valid) => println!("Valid: {}", is_valid),
    Err(e) => println!("Unknown schema: {}", e),
}

// New (0.35.x) - For known drafts only
let result = jsonschema::meta::is_valid(&schema); // Returns bool, panics on unknown $schema

// New (0.35.x) - For custom meta-schemas
let registry = Registry::try_from_resources([
    ("http://example.com/custom", Resource::from_contents(meta_schema))
])?;
let result = jsonschema::meta::options()
    .with_registry(registry)
    .is_valid(&schema); // Returns bool

Resource::from_contents no longer returns Result

The method now always succeeds and returns Resource directly, since draft detection no longer fails for unknown $schema values.

// Old (0.34.x)
let resource = Resource::from_contents(schema)?;

// New (0.35.x)
let resource = Resource::from_contents(schema); // No ? needed

Removed primitive_type module

The primitive_type module has been removed. Use jsonschema::types instead.

// Old (0.34.x)
use jsonschema::primitive_type::{PrimitiveType, PrimitiveTypesBitMap};

// New (0.35.x)
use jsonschema::types::{JsonType, JsonTypeSet};

Upgrading from 0.33.x to 0.34.0

Removed Validator::config()

The Validator::config() method has been removed to reduce memory footprint. The validator no longer stores the configuration internally.

// Old (0.33.x)
let validator = jsonschema::validator_for(&schema)?;
let config = validator.config(); // Returns Arc<ValidationOptions>

// New (0.34.x)
// No replacement - the config is not stored after compilation
// If you need config values, keep a reference to your ValidationOptions
let options = jsonschema::options().with_draft(Draft::Draft7);
let validator = options.build(&schema)?;
// Keep `options` around if you need to access configuration later

Meta-validator statics replaced with functions

Public DRAFT*_META_VALIDATOR statics have been removed. Use the new draftX::meta::validator() helper functions instead. Dropping the Send + Sync bounds for retrievers means the old LazyLock statics can't store validators on wasm32 anymore, so the new helper borrows cached validators on native platforms and builds owned copies on WebAssembly.

// Old (0.33.x)
use jsonschema::DRAFT7_META_VALIDATOR;
DRAFT7_META_VALIDATOR.is_valid(&schema);

// Also removed:
use jsonschema::DRAFT4_META_VALIDATOR;
use jsonschema::DRAFT6_META_VALIDATOR;
use jsonschema::DRAFT201909_META_VALIDATOR;
use jsonschema::DRAFT202012_META_VALIDATOR;

// New (0.34.x)
let validator = jsonschema::draft7::meta::validator();
validator.is_valid(&schema);

// Or use the module-specific helper:
jsonschema::draft7::meta::is_valid(&schema);

Lifetime parameters removed from output types

BasicOutput and Annotations no longer have lifetime parameters. This simplifies the API and uses Arc for internal ownership.

// Old (0.33.x)
fn process_output<'a>(output: BasicOutput<'a>) -> Result<(), Error> {
    match output {
        BasicOutput::Valid(units) => {
            for unit in units {
                let annotations: &Annotations<'a> = unit.annotations();
                // ...
            }
        }
        BasicOutput::Invalid(errors) => { /* ... */ }
    }
    Ok(())
}

// New (0.34.x)
fn process_output(output: BasicOutput) -> Result<(), Error> {
    match output {
        BasicOutput::Valid(units) => {
            for unit in units {
                let annotations: &Annotations = unit.annotations();
                // ...
            }
        }
        BasicOutput::Invalid(errors) => { /* ... */ }
    }
    Ok(())
}

WASM: Relaxed Send + Sync bounds

Retrieve / AsyncRetrieve on wasm32 no longer require Send + Sync.

use jsonschema::{Retrieve, Uri};
use serde_json::Value;
use std::error::Error;

// Old (0.33.x)
use std::sync::{Arc, Mutex};
struct BrowserRetriever(Arc<Mutex<JsFetcher>>);

impl Retrieve for BrowserRetriever {
    fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
        self.0.lock().unwrap().fetch(uri)
    }
}

// New (0.34.x)
use std::rc::Rc;
struct BrowserRetriever(Rc<JsFetcher>);

impl Retrieve for BrowserRetriever {
    fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
        self.0.fetch(uri)
    }
}

Async retrievers follow the same pattern—switch async_trait::async_trait to async_trait::async_trait(?Send) on wasm so the implementation can hold non-thread-safe types.

// Old (0.33.x)
#[async_trait::async_trait]
impl AsyncRetrieve for BrowserRetriever {
    async fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
        self.0.lock().unwrap().fetch(uri).await
    }
}

// New (0.34.x, wasm32)
#[async_trait::async_trait(?Send)]
impl AsyncRetrieve for BrowserRetriever {
    async fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
        self.0.fetch(uri).await
    }
}

Upgrading from 0.32.x to 0.33.0

In 0.33 LocationSegment::Property now holds a Cow<'_, str> and LocationSegment is no longer Copy.

If your code matches the enum and treats the property as &str, update it like this.

This change was made to support proper round-trips for JSON Pointer segments (escaped vs. unescaped forms).

// Old (0.32.x)
match segment {
    LocationSegment::Property(p) => do_something(p), // p: &str
    LocationSegment::Index(i)    => ...
}

do_something_else(segment);

// New (0.33.0)
match segment {
    LocationSegment::Property(p) => do_something(&*p), // p: Cow<'_, str>
    LocationSegment::Index(i)    => ...
}

// `LocationSegment` is no longer Copy, use `.clone()` if you need ownership
do_something_else(segment.clone());

Upgrading from 0.29.x to 0.30.0

PrimitiveType was replaced by JsonType, and PrimitiveTypesBitMap with JsonTypeSet.

// Old (0.29.x)
use jsonschema::primitive_types::PrimitiveType;
use jsonschema::primitive_types::PrimitiveTypesBitMap;

// New (0.30.0)
use jsonschema::JsonType;
use jsonschema::JsonTypeSet;

Upgrading from 0.28.x to 0.29.0

The builder methods on ValidationOptions now take ownership of self. Change your code to use method chaining instead of reusing the options instance:

// Old (0.28.x)
let mut options = jsonschema::options();
options.with_draft(Draft::Draft202012);
options.with_format("custom", |s| s.len() > 3);
let validator = options.build(&schema)?;

// New (0.29.0)
let validator = jsonschema::options()
    .with_draft(Draft::Draft202012)
    .with_format("custom", |s| s.len() > 3)
    .build(&schema)?;

If you implement the Retrieve trait, update the uri parameter type in the retrieve method:

// Old (0.28.x)
impl Retrieve for MyRetriever {
    fn retrieve(&self, uri: &Uri<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        // ...
    }
}

// New (0.29.0)
impl Retrieve for MyRetriever {
    fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        // ...
    }
}

This is a type-level change only; the behavior and available methods remain the same.

The Registry API has been simplified to use a consistent builder pattern. Replace direct creation methods with Registry::options():

// Before (0.28.x)
let registry = Registry::options()
    .draft(Draft::Draft7)
    .try_new(
        "http://example.com/schema",
        resource
    )?;

let registry = Registry::options()
    .draft(Draft::Draft7)
    .try_from_resources([
        ("http://example.com/schema", resource)
    ].into_iter())?;
    
let registry = Registry.try_with_resource_and_retriever(
    "http://example.com/schema",
    resource,
    retriever
)?;

// After (0.29.0)
let registry = Registry::options()
    .draft(Draft::Draft7)
    .build([
        ("http://example.com/schema", resource)
    ])?;

let registry = Registry::options()
    .draft(Draft::Draft7)
    .build([
        ("http://example.com/schema", resource)
    ])?;

let registry = Registry::options()
    .retriever(retriever)
    .build(resources)?;

Upgrading from 0.25.x to 0.26.0

The Validator::validate method now returns Result<(), ValidationError<'i>> instead of an error iterator. If you need to iterate over all validation errors, use the new Validator::iter_errors method.

Example:

// Old (0.25.x)
let validator = jsonschema::validator_for(&schema)?;

if let Err(errors) = validator.validate(&instance) {
    for error in errors {
        println!("Error: {error}");
    }
}

// New (0.26.0)
let validator = jsonschema::validator_for(&schema)?;

// To get the first error only
match validator.validate(&instance) {
    Ok(()) => println!("Valid!"),
    Err(error) => println!("Error: {error}"),
}

// To iterate over all errors
for error in validator.iter_errors(&instance) {
    println!("Error: {error}");
}

Upgrading from 0.22.x to 0.23.0

Replace:

  • JsonPointer to Location
  • PathChunkRef to LocationSegment
  • JsonPointerNode to LazyLocation

Upgrading from 0.21.x to 0.22.0

Replace UriRef<&str> with Uri<&str> in your custom retriever implementation.

Example:

// Old (0.21.x)
use jsonschema::{UriRef, Retrieve};

struct MyCustomRetriever;

impl Retrieve for MyCustomRetriever {
    fn retrieve(&self, uri: &UriRef<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        // ...
    }
}

// New (0.21.0)
use jsonschema::{Uri, Retrieve};

struct MyCustomRetriever;
impl Retrieve for MyCustomRetriever {
    fn retrieve(&self, uri: &Uri<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        // ...
    }
}

Upgrading from 0.20.x to 0.21.0

  1. Replace SchemaResolver with Retrieve:
    • Implement Retrieve trait instead of SchemaResolver
    • Use Box<dyn std::error::Error> for error handling
    • Update ValidationOptions to use with_retriever instead of with_resolver

Example:

// Old (0.20.x)
struct MyCustomResolver;

impl SchemaResolver for MyCustomResolver {
    fn resolve(&self, root_schema: &Value, url: &Url, _original_reference: &str) -> Result<Arc<Value>, SchemaResolverError> {
        match url.scheme() {
            "http" | "https" => {
                Ok(Arc::new(json!({ "description": "an external schema" })))
            }
            _ => Err(anyhow!("scheme is not supported"))
        }
    }
}

let options = jsonschema::options().with_resolver(MyCustomResolver);

// New (0.21.0)
use jsonschema::{UriRef, Retrieve};

struct MyCustomRetriever;

impl Retrieve for MyCustomRetriever {
    fn retrieve(&self, uri: &UriRef<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        match uri.scheme().map(|scheme| scheme.as_str()) {
            Some("http" | "https") => {
                Ok(json!({ "description": "an external schema" }))
            }
            _ => Err("scheme is not supported".into())
        }
    }
}

let options = jsonschema::options().with_retriever(MyCustomRetriever);
  1. Update document handling:
    • Replace with_document with with_resource

Example:

// Old (0.20.x)
let options = jsonschema::options()
    .with_document("schema_id", schema_json);

// New (0.21.0)
use jsonschema::Resource;

let options = jsonschema::options()
    .with_resource("urn:schema_id", Resource::from_contents(schema_json)?);

Upgrading from 0.19.x to 0.20.0

Draft-specific modules are now available:

// Old (0.19.x)
let validator = jsonschema::JSONSchema::options()
    .with_draft(jsonschema::Draft2012)
    .compile(&schema)
    .expect("Invalid schema");

// New (0.20.0)
let validator = jsonschema::draft202012::new(&schema)
    .expect("Invalid schema");

Available modules: draft4, draft6, draft7, draft201909, draft202012

Use the new options() function for easier customization:

// Old (0.19.x)
let options = jsonschema::JSONSchema::options();

// New (0.20.0)
let options = jsonschema::options();

The following items have been renamed. While the old names are still supported in 0.20.0 for backward compatibility, it's recommended to update to the new names:

Old Name (0.19.x) New Name (0.20.0)
CompilationOptions ValidationOptions
JSONSchema Validator
JSONPointer JsonPointer
jsonschema::compile jsonschema::validator_for
CompilationOptions::compile ValidationOptions::build