from std/data/json/schema import *;
from test/more import *;

ok( valid( { type: "object" }, { name: "Ada" } ), "object validates" );
ok(
	not valid( { type: "object" }, [ "Ada" ] ),
	"boolean mode reports invalid",
);

let type_result := validate( { type: "string" }, 42 );
ok( not type_result.valid(), "structured result invalid" );
ok( type_result.errors()[0] instanceof TypeMismatchError, "type error leaf class" );
ok( type_result.errors()[0] instanceof WrongValueError, "type error broad class" );
is( type_result.errors()[0].keywordLocation(), "/type", "keyword location" );
is( type_result.errors()[0].instanceLocation(), "", "instance location" );
is( type_result.to_Dict(){valid}, false, "Basic dict valid flag" );

let required_result := validate(
	{
		type: "object",
		required: [ "name" ],
	},
	{},
);
ok(
	required_result.errors()[0] instanceof RequiredPropertyError,
	"required property has explicit class",
);
ok(
	required_result.errors()[0] instanceof MissingValueError,
	"required property is missing value error",
);

let schema := {
	type: "object",
	properties: {
		age: { type: "integer", minimum: 18 },
	},
};
let nested := validate( schema, { age: 16 } );
ok( not nested.valid(), "nested property invalid" );
ok(
	nested.errors().any( fn e -> e instanceof MinimumError ),
	"nested error has specific class",
);
let nested_minimum := nested.errors().first( fn e -> e instanceof MinimumError );
ok(
	nested.errors().any( fn e -> e instanceof SubschemaError ),
	"subschema error is emitted",
);
let subschema := nested.errors().first( fn e -> e instanceof SubschemaError );
is( subschema.error(), "A subschema had errors.", "subschema message" );
ok(
	subschema.suberrors().any( fn e -> e ≡ nested_minimum ),
	"weak child link works",
);

let format_options := {
	format_assert: true,
	unknown_format: "fail",
};

let fmt := validate(
	{ type: "string", format: "email" },
	"not email",
	format_options,
);
ok( fmt.errors()[0] instanceof FormatError, "format error class" );

let unknown_fmt := validate(
	{ type: "string", format: "made-up" },
	"x",
	format_options,
);
ok( unknown_fmt.errors()[0] instanceof UnknownFormatError, "unknown format class" );

let additional := validate(
	{
		type: "object",
		properties: { known: true },
		additionalProperties: false,
	},
	{ known: 1, other: 2 },
);
ok(
	additional.errors().any( fn e -> e instanceof AdditionalPropertiesError ),
	"additionalProperties has explicit class",
);

let registry := new SchemaRegistry();
registry.register( { type: "string" }, "https://example.test/string" );
let registry_options := { registry: registry };
let ref_schema := { "$ref": "https://example.test/string" };
ok(
	valid(
		ref_schema,
		"ok",
		registry_options,
	),
	"registry resolves absolute ref",
);
let reference_invalid := valid( ref_schema, 1, registry_options );
is( reference_invalid, false, "ref target validates instance" );

ok(
	new RelativeJSONPointer( path: "1/foo/0" ) instanceof RelativeJSONPointer,
	"relative JSON pointer parses",
);
let rel_doc := {
	foo: [ "bar", "baz" ],
	highly: {
		nested: {
			objects: true,
		},
	},
};
is(
	new RelativeJSONPointer( path: "0" ).first( rel_doc, "/foo/1" ),
	"baz",
	"relative pointer 0 returns current value",
);
is(
	new RelativeJSONPointer( path: "1/0" ).first( rel_doc, "/foo/1" ),
	"bar",
	"relative pointer can climb then apply JSON Pointer",
);
is(
	new RelativeJSONPointer( path: "0-1" ).first( rel_doc, "/foo/1" ),
	"bar",
	"relative pointer index manipulation works",
);
is(
	new RelativeJSONPointer( path: "2/highly/nested/objects" )
		.first( rel_doc, "/foo/1" ),
	true,
	"relative pointer can resolve from root",
);
is(
	new RelativeJSONPointer( path: "0#" ).first( rel_doc, "/foo/1" ),
	"1",
	"relative pointer key request returns current key",
);

let unordered := <<< 1, 2, 3 >>>;
ok( valid( { type: "array", minItems: 2 }, unordered ), "Bag minItems works" );
ok(
	not valid( { prefixItems: [ { type: "number" } ] }, unordered ),
	"Bag rejects positional prefixItems",
);

done_testing();
