diff --git a/docs/src/migration.md b/docs/src/migration.md index 9683809..f6aa0fb 100644 --- a/docs/src/migration.md +++ b/docs/src/migration.md @@ -33,9 +33,8 @@ schema = Schema(spec; parent_dir="./schemas") ### 2. `SingleIssue` Type Replaced by `ValidationResult` -The `SingleIssue` type from v1.x has been replaced by `ValidationResult`. For -backwards compatibility, `SingleIssue` is aliased to `ValidationResult`, so -existing `isa` checks will continue to work. +The `SingleIssue` type from v1.x has been removed and replaced by +`ValidationResult`. **v1.x:** ```julia @@ -56,6 +55,31 @@ if result !== nothing end ``` +### 3. `diagnose` Function + +The previously deprecated `diagnose` function has been removed. Use +`validate(schema, data)` instead. + +### 4. Inverse Argument Order + +The `validate` and `isvalid` functions where `schema` is the second argument +have been removed. `schema` must be the first argument. +```julia +validate(data, schema) # old +validate(schema, data) # new + +isvalid(data, schema) # old +isvalid(schema, data) # new +``` + +### 5. `required` Without `properties` + +v1.x supported non-standard schemas with `required` field and no `properties`. +In v2.0, you must specify `properties` if `required` is present. +```julia +schema = Schema(Dict("type" => "object", "required" => ["foo"])) # Not allowed +``` + ## API Compatibility The following v1.x patterns are fully supported in v2.0: @@ -86,13 +110,6 @@ using JSONSchema isvalid(schema, data) # Returns true or false ``` -### `schema.data` Field Access - -```julia -schema = Schema(Dict("type" => "object")) -schema.data["type"] # Works - maps to schema.spec -``` - ### Boolean Schemas ```julia @@ -100,28 +117,6 @@ Schema(true) # Accepts everything Schema(false) # Rejects everything ``` -### Inverse Argument Order - -```julia -validate(data, schema) # Works - swaps to validate(schema, data) -isvalid(data, schema) # Works - swaps to isvalid(schema, data) -``` - -### `required` Without `properties` - -```julia -schema = Schema(Dict("type" => "object", "required" => ["foo"])) -isvalid(schema, Dict("bar" => 1)) # Returns false (v1.x behavior) -``` - -### `diagnose` Function (Deprecated) - -```julia -diagnose(data, schema) # Works but emits deprecation warning -``` - -Use `validate(schema, data)` instead. - ## New Features in v2.0 ### Schema Generation from Types diff --git a/src/JSONSchema.jl b/src/JSONSchema.jl index 348580d..d719352 100644 --- a/src/JSONSchema.jl +++ b/src/JSONSchema.jl @@ -7,10 +7,7 @@ import URIs using JSON: JSONWriteStyle, Object export Schema, SchemaContext, ValidationResult, schema, validate -# Backwards compatibility exports (v1.5.0) -export diagnose, SingleIssue include("schema.jl") -include("compat.jl") end diff --git a/src/compat.jl b/src/compat.jl deleted file mode 100644 index 8d058bc..0000000 --- a/src/compat.jl +++ /dev/null @@ -1,88 +0,0 @@ -# Backwards compatibility layer for JSONSchema v1.5.0 API -# This file provides compatibility shims for code written against the v1.5.0 API - -# ============= 1. Support schema.data field access (v1.5.0 pattern) ============= -# v1.5.0 used schema.data to access the spec, new API uses schema.spec -function Base.getproperty(s::Schema, name::Symbol) - if name === :data - return getfield(s, :spec) # Map .data -> .spec - else - return getfield(s, name) - end -end - -# ============= 2. Support inverse argument order ============= -# v1.5.0 supported both validate(schema, x) and validate(x, schema) -function validate(instance, schema::Schema; resolver=nothing) - return validate(schema, instance; resolver=resolver) -end - -# ============= 3. Support boolean schemas ============= -# v1.5.0 supported Schema(true) and Schema(false) -# true = accept everything, false = reject everything -function Schema(b::Bool) - if b - # true schema accepts everything - empty schema - return Schema{Any}(Any, Object{String, Any}(), nothing) - else - # false schema rejects everything - use "not: {}" pattern - return Schema{Any}(Any, Object{String, Any}("not" => Object{String, Any}()), nothing) - end -end - -# ============= 4. Fix required validation for Dicts without properties ============= -# v1.5.0 validated "required" even when "properties" was not defined -# This is handled by adding a check in _validate_value for AbstractDict -# See _validate_required_for_dict below, called from _validate_value - -""" - _validate_required_for_dict(schema, value::AbstractDict, path, errors) - -Validate required fields for Dict values, even when properties is not defined. -This restores v1.5.0 behavior where required was checked independently. -""" -function _validate_required_for_dict(schema, value::AbstractDict, path::String, errors::Vector{String}) - if !haskey(schema, "required") - return - end - - required = schema["required"] - if !(required isa AbstractVector) - return - end - - for req_prop in required - req_str = string(req_prop) - if !haskey(value, req_str) && !haskey(value, Symbol(req_str)) - push!(errors, "$path: required property '$req_str' is missing") - end - end -end - -# ============= 5. Provide deprecated diagnose function ============= -# diagnose was deprecated in v1.5.0 but still present -""" - diagnose(x, schema) - -!!! warning "Deprecated" - `diagnose(x, schema)` is deprecated. Use `validate(schema, x)` instead. - -Validate `x` against `schema` and return a string description of the first error, -or `nothing` if valid. -""" -function diagnose(x, schema) - Base.depwarn( - "`diagnose(x, schema)` is deprecated. Use `validate(schema, x)` instead.", - :diagnose, - ) - result = validate(schema, x) - if result !== nothing && !isempty(result.errors) - return join(result.errors, "\n") - end - return nothing -end - -# ============= Type alias for SingleIssue ============= -# v1.5.0 had SingleIssue type for validation errors -# Provide an alias so code checking `result isa SingleIssue` doesn't error -const SingleIssue = ValidationResult diff --git a/src/schema.jl b/src/schema.jl index 5f2763d..d553857 100644 --- a/src/schema.jl +++ b/src/schema.jl @@ -74,6 +74,17 @@ end Schema(spec::AbstractString) = Schema(JSON.parse(spec)) Schema(spec::AbstractVector{UInt8}) = Schema(JSON.parse(spec)) +# Boolean schemas are part of the draft6 specification. +function Schema(b::Bool) + if b + # true schema accepts everything - empty schema + return Schema{Any}(Any, Object{String, Any}(), nothing) + else + # false schema rejects everything - use "not: {}" pattern + return Schema{Any}(Any, Object{String, Any}("not" => Object{String, Any}()), nothing) + end +end + # Helper functions for $ref support """ @@ -797,11 +808,6 @@ end # Also support JSON.Schema (which is an alias for JSONSchema.Schema) # and inverse argument order for v1.5.0 compatibility function validate(schema, instance; resolver=nothing) - # Handle inverse argument order (v1.5.0 compat): validate(data, schema) - if instance isa Schema - return validate(instance, schema; resolver=resolver) - end - # Handle JSON.Schema (which is aliased to JSONSchema.Schema) if typeof(schema).name.module === JSON && hasfield(typeof(schema), :type) && hasfield(typeof(schema), :spec) return validate(Schema{typeof(schema).parameters[1]}(schema.type, schema.spec, nothing), instance; resolver=resolver) @@ -883,11 +889,6 @@ function Base.isvalid(schema::Schema{T}, instance::T; verbose::Bool=false) where return is_valid end -# Also support inverse argument order for v1.5.0 compatibility -function Base.isvalid(instance, schema::Schema; verbose::Bool=false) - return Base.isvalid(schema, instance; verbose=verbose) -end - # Internal: Validate an instance against a schema function _validate_instance(schema_obj, instance, ::Type{T}, path::String, errors::Vector{String}, verbose::Bool, root::Object{String, Any}) where {T} # Handle $ref - resolve and validate against resolved schema @@ -906,30 +907,30 @@ function _validate_instance(schema_obj, instance, ::Type{T}, path::String, error if isstructtype(T) && isconcretetype(T) && haskey(schema_obj, "properties") properties = schema_obj["properties"] required = get(schema_obj, "required", String[]) - + style = StructUtils.DefaultStyle() all_field_tags = StructUtils.fieldtags(style, T) - + for i in 1:fieldcount(T) fname = fieldname(T, i) ftype = fieldtype(T, i) fvalue = getfield(instance, fname) - + # Get field tags field_tags = haskey(all_field_tags, fname) ? all_field_tags[fname] : nothing tags = field_tags isa NamedTuple && haskey(field_tags, :json) ? field_tags.json : nothing - + # Skip ignored fields if tags isa NamedTuple && get(tags, :ignore, false) continue end - + # Get JSON name (may be renamed) json_name = string(fname) if tags isa NamedTuple && haskey(tags, :name) json_name = string(tags.name) end - + # Check if field is in schema if haskey(properties, json_name) field_schema = properties[json_name] @@ -1176,13 +1177,6 @@ function _validate_value(schema, value, ::Type{T}, tags, path::String, errors::V # Dict/Object validation (properties, patternProperties, propertyNames for Dicts) if value isa AbstractDict - # Validate required fields even without properties (v1.5.0 compat) - # This is called from compat.jl and handles the case where "required" - # is specified without "properties" - if !haskey(schema, "properties") && haskey(schema, "required") - _validate_required_for_dict(schema, value, path, errors) - end - # Validate properties for Dict if haskey(schema, "properties") properties = schema["properties"] @@ -1349,13 +1343,13 @@ function _validate_string(schema, tags, value::String, path::String, errors::Vec if min_len !== nothing && length(value) < min_len push!(errors, "$path: string length $(length(value)) is less than minimum $min_len") end - + # Check maxLength max_len = get(schema, "maxLength", nothing) if max_len !== nothing && length(value) > max_len push!(errors, "$path: string length $(length(value)) exceeds maximum $max_len") end - + # Check pattern pattern = get(schema, "pattern", nothing) if pattern !== nothing @@ -1368,7 +1362,7 @@ function _validate_string(schema, tags, value::String, path::String, errors::Vec # Invalid regex pattern - skip validation end end - + # Format validation (basic checks) format = get(schema, "format", nothing) if format !== nothing @@ -1417,7 +1411,7 @@ function _validate_number(schema, tags, value::Number, path::String, errors::Vec push!(errors, "$path: value $value is less than minimum $min_val") end end - + # Check maximum max_val = get(schema, "maximum", nothing) exclusive_max = get(schema, "exclusiveMaximum", false) @@ -1428,7 +1422,7 @@ function _validate_number(schema, tags, value::Number, path::String, errors::Vec push!(errors, "$path: value $value exceeds maximum $max_val") end end - + # Check multipleOf multiple = get(schema, "multipleOf", nothing) if multiple !== nothing