Skip to content

Commit

Permalink
Added type validation
Browse files Browse the repository at this point in the history
  • Loading branch information
DrDeano committed Nov 26, 2022
1 parent 7b003ca commit 32a72e8
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ A Zig implementation of the JSON schema validator.
- [ ] ref
- [ ] refRemote
- [ ] required
- [ ] type
- [x] type
- [ ] uniqueItems
- [ ] unknownKeyword

Expand Down
4 changes: 2 additions & 2 deletions src/c_jsonschema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export fn zjs_compile(schema: ?[*:0]const u8) ?*zjs_type {
/// Whether the data is valid against the schema. Currently, if there was an error parsing
/// the data or invalid arguments, this will return false.
///
export fn zjs_validate(zjs: ?*zjs_type, data: ?[*:0]const u8) bool {
export fn zjs_validate(zjs: ?*align(@alignOf(jsonschema.Schema)) zjs_type, data: ?[*:0]const u8) bool {
if (zjs) |schema_ptr| {
const schema_comp = @ptrCast(*jsonschema.Schema, schema_ptr);
var data_tree = parseJsonTree(data) catch return false;
Expand Down Expand Up @@ -119,7 +119,7 @@ export fn zjs_compile_and_validate(schema: ?[*:0]const u8, data: ?[*:0]const u8)
/// Arguments:
/// IN zjs: ?*zjs_type - The opaque compiled JSON schema from zjs_compile().
///
export fn zjs_deinit(zjs: ?*zjs_type) void {
export fn zjs_deinit(zjs: ?*align(@alignOf(jsonschema.Schema)) zjs_type) void {
if (zjs) |schema_ptr| {
const schema_comp = @ptrCast(*jsonschema.Schema, schema_ptr);
allocator.destroy(schema_comp);
Expand Down
102 changes: 99 additions & 3 deletions src/jsonschema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,87 @@ const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;

const Type = enum {
Object,
Array,
String,
Number,
Integer,
Bool,
Null,
};

const Types = struct {
// This is a enum set as a number can be an int or float
// and a int can be either a int or a float if the float can be represented as a int without rounding
types: std.EnumSet(Type) = std.EnumSet(Type){},

const Self = @This();

fn str_to_schema_enum(str: []const u8) Schema.CompileError!std.EnumSet(Type) {
var set = std.EnumSet(Type){};
if (std.mem.eql(u8, str, "integer")) {
set.insert(.Integer);
} else if (std.mem.eql(u8, str, "number")) {
set.insert(.Number);
} else if (std.mem.eql(u8, str, "string")) {
set.insert(.String);
} else if (std.mem.eql(u8, str, "object")) {
set.insert(.Object);
} else if (std.mem.eql(u8, str, "array")) {
set.insert(.Array);
} else if (std.mem.eql(u8, str, "boolean")) {
set.insert(.Bool);
} else if (std.mem.eql(u8, str, "null")) {
set.insert(.Null);
} else {
return error.InvalidType;
}
return set;
}

pub fn compile(schema: std.json.Value) Schema.CompileError!Self {
switch (schema) {
.String => |val| return .{ .types = try Types.str_to_schema_enum(val) },
.Array => |array| {
var comp_types_schema = std.EnumSet(Type){};
for (array.items) |string| {
comp_types_schema.setUnion(try Types.str_to_schema_enum(string.String));
}
return .{ .types = comp_types_schema };
},
else => return error.InvalidType,
}
}

pub fn validate(self: Self, data: std.json.Value) Schema.ValidateError!bool {
return switch (data) {
.Object => self.types.contains(.Object),
.Array => self.types.contains(.Array),
.String => self.types.contains(.String),
.Integer => self.types.contains(.Integer) or self.types.contains(.Number),
.Float => |val| self.types.contains(.Number) or (self.types.contains(.Integer) and (@floor(val) == val and @ceil(val) == val)),
.NumberString => error.TODOTopLevel,
.Bool => self.types.contains(.Bool),
.Null => self.types.contains(.Null),
};
}
};

/// The root compiled schema object
pub const Schema = union(enum) {
Schemas: []Schema,
Bool: bool,
Types: Types,

const Self = @This();

/// Error relating to the compilation of the schema
pub const CompileError = error{
/// TODO top level compiler
TODOTopLevel,
};
InvalidType,
} || Allocator.Error;

/// Error relating to the validation of JSON data against the schema
pub const ValidateError = error{
Expand All @@ -35,13 +105,30 @@ pub const Schema = union(enum) {
/// TODOTopLevel - TODO top level validator
///
pub fn compile(allocator: Allocator, schema: std.json.Value) CompileError!Self {
_ = allocator;
return switch (schema) {
.Bool => |b| return Schema{ .Bool = b },
.Object => |object| brk: {
var schema_list = std.ArrayList(Schema).init(allocator);
errdefer schema_list.deinit();

if (object.get("type")) |type_schema| {
const sub_schema = Schema{ .Types = try Types.compile(type_schema) };
try schema_list.append(sub_schema);
}

break :brk Schema{ .Schemas = schema_list.toOwnedSlice() };
},
else => CompileError.TODOTopLevel,
};
}

pub fn deinit(self: Self, allocator: Allocator) void {
switch (self) {
.Schemas => |schemas| allocator.free(schemas),
else => {},
}
}

///
/// Validate JSON data against a compiled schema.
///
Expand All @@ -56,9 +143,17 @@ pub const Schema = union(enum) {
/// TODOTopLevel - TODO top level compiler
///
pub fn validate(self: Self, data: std.json.Value) ValidateError!bool {
_ = data;
return switch (self) {
.Bool => |b| b,
.Types => |types| types.validate(data),
.Schemas => |schemas| {
for (schemas) |schema| {
if (!try schema.validate(data)) {
return false;
}
}
return true;
},
};
}
};
Expand All @@ -80,5 +175,6 @@ pub const Schema = union(enum) {
///
pub fn validate(allocator: Allocator, schema: std.json.Value, data: std.json.Value) (Schema.CompileError || Schema.ValidateError)!bool {
const js_cmp = try Schema.compile(allocator, schema);
defer js_cmp.deinit(allocator);
return js_cmp.validate(data);
}
30 changes: 22 additions & 8 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,24 @@ test "basic" {
var data_tree = try data_parser.parse("{}");
defer data_tree.deinit();

const t_schema = jsonschema.Schema{ .Bool = true };
const f_schema = jsonschema.Schema{ .Bool = false };
{
try expect(try jsonschema.validate(std.testing.allocator, schema_tree.root, data_tree.root));
}

{
const comp = try jsonschema.Schema.compile(std.testing.allocator, schema_tree.root);
defer comp.deinit(std.testing.allocator);

try expect(try comp.validate(data_tree.root));
}

{
const t_schema = jsonschema.Schema{ .Bool = true };
const f_schema = jsonschema.Schema{ .Bool = false };

try expectError(jsonschema.Schema.CompileError.TODOTopLevel, jsonschema.validate(std.testing.allocator, schema_tree.root, data_tree.root));
try expectError(jsonschema.Schema.CompileError.TODOTopLevel, jsonschema.Schema.compile(std.testing.allocator, schema_tree.root));
try expect(!try jsonschema.Schema.validate(f_schema, data_tree.root));
try expect(try jsonschema.Schema.validate(t_schema, data_tree.root));
try expect(!try jsonschema.Schema.validate(f_schema, data_tree.root));
try expect(try jsonschema.Schema.validate(t_schema, data_tree.root));
}
}

test "c API" {
Expand All @@ -34,7 +45,8 @@ test "c API" {
const zjs_deinit = @extern(*const fn (zjs: ?*zjs_type) void, .{ .name = "zjs_deinit", .linkage = .Strong });

try expectEqual(zjs_compile(null), null);
try expectEqual(zjs_compile("{}"), null);
const ob1 = zjs_compile("{}");
defer zjs_deinit(ob1);
try expectEqual(zjs_compile("8tyol8fcu"), null);

try expect(!zjs_validate(null, null));
Expand All @@ -43,7 +55,7 @@ test "c API" {
try expect(!zjs_compile_and_validate(null, null));
try expect(!zjs_compile_and_validate(null, "{}"));
try expect(!zjs_compile_and_validate("{}", null));
try expect(!zjs_compile_and_validate("{}", "{}"));
try expect(zjs_compile_and_validate("{}", "{}"));

zjs_deinit(null);
}
Expand All @@ -52,6 +64,7 @@ test "JSON Schema Test Suite" {
const test_files_dir = "JSON-Schema-Test-Suite/tests/draft7/";
const test_files = .{
test_files_dir ++ "boolean_schema.json",
test_files_dir ++ "type.json",
};

inline for (test_files) |test_file| {
Expand Down Expand Up @@ -84,6 +97,7 @@ test "JSON Schema Test Suite" {
std.log.err("TODO: {s}", .{schema_stream.getWritten()});
return e;
};
defer compiled_schema.deinit(std.testing.allocator);

var i: usize = 0;
while (i < 1) : (i += 1) {
Expand Down

0 comments on commit 32a72e8

Please sign in to comment.