From e80961d1a18a29bb3865e06035e524927d9a0312 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Sun, 4 Oct 2026 13:38:36 -0600 Subject: [PATCH] Validate abstract vector array instances AI disclosure: This work was prepared with assistance from OpenAI Codex. --- src/validation.jl | 6 +-- test/runtests.jl | 1 + test/vector_instances.jl | 99 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 test/vector_instances.jl diff --git a/src/validation.jl b/src/validation.jl index 3e3cb2d..2daa961 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -385,8 +385,8 @@ function _validate( end function _additional_items(x, num_items, val, path) - for i in (num_items+1):length(x) - ret = _validate_child(x[i], val, path, i) + for (i, xi) in enumerate(Iterators.drop(x, num_items)) + ret = _validate_child(xi, val, path, num_items + i) if ret !== nothing return ret end @@ -581,7 +581,7 @@ function _validate( end _is_type(::Any, ::Val) = false -_is_type(::Array, ::Val{:array}) = true +_is_type(::Union{Array,AbstractVector}, ::Val{:array}) = true _is_type(::Bool, ::Val{:boolean}) = true _is_type(::Integer, ::Val{:integer}) = true _is_type(x::AbstractFloat, ::Val{:integer}) = isinteger(x) diff --git a/test/runtests.jl b/test/runtests.jl index da335ba..0664f87 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,6 +15,7 @@ import ZipFile include("additional_patterns.jl") include("integer_floats.jl") include("string_instances.jl") +include("vector_instances.jl") const TEST_SUITE_URL = "https://github.com/json-schema-org/JSON-Schema-Test-Suite/archive/23.1.0.zip" diff --git a/test/vector_instances.jl b/test/vector_instances.jl new file mode 100644 index 0000000..cc8a44f --- /dev/null +++ b/test/vector_instances.jl @@ -0,0 +1,99 @@ +struct _OffsetVector{T} <: AbstractVector{T} + values::Vector{T} +end +Base.size(x::_OffsetVector) = size(x.values) +Base.axes(x::_OffsetVector) = (0:(length(x.values)-1),) +Base.getindex(x::_OffsetVector, i::Int) = x.values[i+1] +Base.iterate(x::_OffsetVector, state...) = iterate(x.values, state...) + +@testset "AbstractVector array instances" begin + array = JSONSchema.Schema(Dict("type" => "array")) + nullable = JSONSchema.Schema(Dict("type" => ["array", "null"])) + vectors = ([1, 2, 3], view([0, 1, 2, 3, 0], 2:4), 1:3) + for data in (vectors..., BitVector([true, false])) + @test isvalid(array, data) + @test isvalid(nullable, data) + @test isvalid(JSONSchema.schema(typeof(data)), data) + end + @test isvalid(nullable, nothing) + + for data in vectors + for (keyword, constraint, expected) in ( + ("minItems", 3, true), + ("minItems", 4, false), + ("maxItems", 3, true), + ("maxItems", 2, false), + ("uniqueItems", true, true), + ("contains", Dict("const" => 2), true), + ("contains", Dict("const" => 4), false), + ("items", Dict("type" => "integer"), true), + ("items", false, false), + ) + schema = JSONSchema.Schema( + Dict("type" => "array", keyword => constraint), + ) + @test isvalid(schema, data) == expected + end + object = JSONSchema.Schema( + Dict( + "properties" => Dict( + "values" => Dict( + "type" => "array", + "items" => Dict("type" => "integer"), + ), + ), + ), + ) + @test isvalid(object, Dict("values" => data)) + end + + integers = JSONSchema.Schema( + Dict("type" => "array", "items" => Dict("type" => "integer")), + ) + for data in ([1.0, 1.5, 2.0], view([1.0, 1.5, 2.0], 1:3), 1.0:0.5:2.0) + issue = JSONSchema.validate(integers, data) + @test issue isa JSONSchema.SingleIssue && + issue.reason == "type" && + JSONSchema.json_pointer(issue) == "#/1" + end + empty = JSONSchema.Schema(Dict("type" => "array", "maxItems" => 0)) + for data in (Int[], view(Int[], 1:0), 1:0) + @test isvalid(empty, data) + end + booleans = JSONSchema.Schema( + Dict("type" => "array", "items" => Dict("type" => "boolean")), + ) + @test isvalid(booleans, BitVector([true, false])) + @test !isvalid( + JSONSchema.Schema(Dict("type" => "array", "uniqueItems" => true)), + BitVector([true, false, true]), + ) + + for data in (1, true, "array", nothing, Dict(), (1, 2)) + @test !isvalid(array, data) + end + @test isvalid(array, [1 2; 3 4]) +end + +@testset "Offset vector additional items use JSON positions" begin + integer = Dict("type" => "integer") + raw = Dict{String,Any}("items" => [integer], "additionalItems" => integer) + for schema in ( + JSONSchema.Schema(raw), + JSONSchema.Schema(merge(raw, Dict("type" => "array"))), + ) + @test isvalid(schema, _OffsetVector([1, 2, 3])) + @test isvalid(schema, _OffsetVector(Int[])) + issue = JSONSchema.validate(schema, _OffsetVector([1.0, 2.5, 3.0])) + @test issue isa JSONSchema.SingleIssue + @test issue.x == 2.5 + @test issue.path == "[2]" + @test JSONSchema.json_pointer(issue) == "#/1" + issue = JSONSchema.validate(schema, _OffsetVector([1.5, 2.0])) + @test JSONSchema.json_pointer(issue) == "#/0" + end + @test isvalid( + JSONSchema.schema(_OffsetVector{Int}), + _OffsetVector([1, 2, 3]), + ) +end