diff --git a/src/models/v3.0/schema.zig b/src/models/v3.0/schema.zig index eb8a273..a7b05dc 100644 --- a/src/models/v3.0/schema.zig +++ b/src/models/v3.0/schema.zig @@ -3,6 +3,16 @@ const json = std.json; const Reference = @import("reference.zig").Reference; const ExternalDocumentation = @import("externaldocs.zig").ExternalDocumentation; +fn optionalFloat(value: ?json.Value) ?f64 { + const val = value orelse return null; + return switch (val) { + .integer => |i| @as(f64, @floatFromInt(i)), + .float => |f| f, + .number_string => |s| std.fmt.parseFloat(f64, s) catch null, + else => null, + }; +} + pub const XML = struct { name: ?[]const u8 = null, namespace: ?[]const u8 = null, @@ -182,10 +192,10 @@ pub const Schema = struct { } return Schema{ .title = if (obj.get("title")) |val| try allocator.dupe(u8, val.string) else null, - .multipleOf = if (obj.get("multipleOf")) |val| val.float else null, - .maximum = if (obj.get("maximum")) |val| val.float else null, + .multipleOf = optionalFloat(obj.get("multipleOf")), + .maximum = optionalFloat(obj.get("maximum")), .exclusiveMaximum = if (obj.get("exclusiveMaximum")) |val| val.bool else null, - .minimum = if (obj.get("minimum")) |val| val.float else null, + .minimum = optionalFloat(obj.get("minimum")), .exclusiveMinimum = if (obj.get("exclusiveMinimum")) |val| val.bool else null, .maxLength = if (obj.get("maxLength")) |val| val.integer else null, .minLength = if (obj.get("minLength")) |val| val.integer else null, diff --git a/src/models/v3.2/schema.zig b/src/models/v3.2/schema.zig index 2cde132..ab65381 100644 --- a/src/models/v3.2/schema.zig +++ b/src/models/v3.2/schema.zig @@ -3,6 +3,16 @@ const json = std.json; const Reference = @import("reference.zig").Reference; const ExternalDocumentation = @import("externaldocs.zig").ExternalDocumentation; +fn optionalFloat(value: ?json.Value) ?f64 { + const val = value orelse return null; + return switch (val) { + .integer => |i| @as(f64, @floatFromInt(i)), + .float => |f| f, + .number_string => |s| std.fmt.parseFloat(f64, s) catch null, + else => null, + }; +} + pub const XML = struct { name: ?[]const u8 = null, namespace: ?[]const u8 = null, @@ -206,10 +216,10 @@ pub const Schema = struct { return Schema{ .title = if (obj.get("title")) |val| try allocator.dupe(u8, val.string) else null, - .multipleOf = if (obj.get("multipleOf")) |val| val.float else null, - .maximum = if (obj.get("maximum")) |val| val.float else null, + .multipleOf = optionalFloat(obj.get("multipleOf")), + .maximum = optionalFloat(obj.get("maximum")), .exclusiveMaximum = if (obj.get("exclusiveMaximum")) |val| val.bool else null, - .minimum = if (obj.get("minimum")) |val| val.float else null, + .minimum = optionalFloat(obj.get("minimum")), .exclusiveMinimum = if (obj.get("exclusiveMinimum")) |val| val.bool else null, .maxLength = if (obj.get("maxLength")) |val| val.integer else null, .minLength = if (obj.get("minLength")) |val| val.integer else null, diff --git a/src/tests/openapi_v32_tests.zig b/src/tests/openapi_v32_tests.zig index 6603853..7dff393 100644 --- a/src/tests/openapi_v32_tests.zig +++ b/src/tests/openapi_v32_tests.zig @@ -1,6 +1,7 @@ const OpenApi32Converter = @import("../generators/converters/openapi32_converter.zig").OpenApi32Converter; const models = @import("../models.zig"); const std = @import("std"); +const json = std.json; const test_utils = @import("test_utils.zig"); fn loadOpenApi32Document(allocator: std.mem.Allocator, file_path: []const u8) !models.OpenApi32Document { @@ -26,6 +27,22 @@ test "can deserialize petstore into OpenApi32Document" { try std.testing.expectEqualStrings("Swagger Petstore", parsed.info.title); } +test "schema numeric bounds accept integer and number_string values for v3.2" { + var gpa = test_utils.createTestAllocator(); + const allocator = gpa.allocator(); + var object = json.ObjectMap.init(allocator); + defer object.deinit(); + try object.put("type", .{ .string = "number" }); + try object.put("multipleOf", .{ .integer = 2 }); + try object.put("maximum", .{ .integer = 10 }); + try object.put("minimum", .{ .number_string = "1.5" }); + var schema = try models.v32.Schema.parseFromJson(allocator, .{ .object = object }); + defer schema.deinit(allocator); + try std.testing.expectEqual(@as(f64, 2.0), schema.multipleOf.?); + try std.testing.expectEqual(@as(f64, 10.0), schema.maximum.?); + try std.testing.expectEqual(@as(f64, 1.5), schema.minimum.?); +} + test "can parse petstore info summary field" { var gpa = test_utils.createTestAllocator(); const allocator = gpa.allocator(); diff --git a/src/tests/openapi_v3_tests.zig b/src/tests/openapi_v3_tests.zig index e07818d..5d7ab47 100644 --- a/src/tests/openapi_v3_tests.zig +++ b/src/tests/openapi_v3_tests.zig @@ -2,6 +2,7 @@ const ModelCodeGenerator = @import("../generators/v3.0/modelgenerator.zig").Mode const OpenApiConverter = @import("../generators/converters/openapi_converter.zig").OpenApiConverter; const models = @import("../models.zig"); const std = @import("std"); +const json = std.json; const test_utils = @import("test_utils.zig"); fn loadOpenApiDocument(allocator: std.mem.Allocator, file_path: []const u8) !models.OpenApiDocument { @@ -27,6 +28,22 @@ test "can deserialize petstore into OpenApiDocument" { try std.testing.expectEqualStrings("Swagger Petstore", parsed.info.title); } +test "schema numeric bounds accept integer and number_string values" { + var gpa = test_utils.createTestAllocator(); + const allocator = gpa.allocator(); + var object = json.ObjectMap.init(allocator); + defer object.deinit(); + try object.put("type", .{ .string = "number" }); + try object.put("multipleOf", .{ .integer = 2 }); + try object.put("maximum", .{ .integer = 10 }); + try object.put("minimum", .{ .number_string = "1.5" }); + var schema = try models.v3.Schema.parseFromJson(allocator, .{ .object = object }); + defer schema.deinit(allocator); + try std.testing.expectEqual(@as(f64, 2.0), schema.multipleOf.?); + try std.testing.expectEqual(@as(f64, 10.0), schema.maximum.?); + try std.testing.expectEqual(@as(f64, 1.5), schema.minimum.?); +} + test "can generate data structures from petstore OpenAPI specification" { var gpa = test_utils.createTestAllocator(); const allocator = gpa.allocator();