Skip to content

Optional schema numeric bound parsing - #59

Merged
christianhelle merged 4 commits into
mainfrom
optional-float-parsing
Jul 3, 2026
Merged

Optional schema numeric bound parsing#59
christianhelle merged 4 commits into
mainfrom
optional-float-parsing

Conversation

@christianhelle

@christianhelle christianhelle commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Closes #52

Summary by CodeRabbit

  • Bug Fixes

    • Improved parsing of schema numeric limits (multipleOf, maximum, minimum) to correctly accept integers, decimals, and numeric-string inputs.
    • Unsupported or missing numeric bound values are now handled gracefully (without errors), while exclusiveMaximum/exclusiveMinimum boolean flags continue to be preserved.
  • Tests

    • Added/expanded schema bound parsing test coverage across supported schema versions, including positive, negative, missing, invalid, and overflowing-number scenarios.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared optionalFloat helper for schema numeric bounds, updates v3.0/v3.1/v3.2 parsers to use it for multipleOf, maximum, and minimum, and adds tests covering integer, float, string, missing, negative, and overflowing inputs.

Changes

Numeric bounds parsing

Layer / File(s) Summary
Shared float parsing helper
src/models/json_helpers.zig
Adds optionalFloat for optional JSON values, accepting integers, floats, and numeric strings.
v3.0 schema parsing
src/models/v3.0/schema.zig
Imports the shared helper and uses it for multipleOf, maximum, and minimum parsing.
v3.1 schema refactor
src/models/v3.1/schema.zig
Replaces the local float helper with the shared helper and updates the numeric-bound assignments.
v3.2 schema parsing
src/models/v3.2/schema.zig
Imports the shared helper and uses it for multipleOf, maximum, and minimum parsing.
Test registration and bounds coverage
src/tests.zig, src/tests/schema_bounds_parsing_tests.zig
Registers the new test module and adds coverage for v3.0/v3.2 numeric-bound parsing behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • christianhelle/openapi2zig#40: Both PRs change Schema.parseFromJson handling for multipleOf, maximum, and minimum in src/models/v3.2/schema.zig.

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: optional parsing of schema numeric bounds.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch optional-float-parsing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/tests/schema_bounds_parsing_tests.zig (1)

94-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

.number_string branch of optionalFloat is untested.

std.json.Value.number_string is only produced for integer-formatted numeric tokens that overflow i64 (not for quoted JSON strings). "not-a-number" parses as .string, so this test actually exercises optionalFloat's else => null fallback, not the .number_string => parseFloat(...) catch null path added by this PR. Consider adding a test with an overflowing integer literal (e.g. an unquoted number wider than i64) to actually cover that branch.

🧪 Suggested additional test
test "v3.0 schema parses overflowing integer multipleOf via number_string" {
    var gpa = test_utils.createTestAllocator();
    const allocator = gpa.allocator();
    const source = "{\"multipleOf\": 99999999999999999999999999999999}";
    var parsed = try parseJsonValue(allocator, source);
    defer parsed.deinit();
    var schema = try models.v3.Schema.parseFromJson(allocator, parsed.value);
    defer schema.deinit(allocator);
    try std.testing.expect(schema.multipleOf != null);
}

Please confirm this understanding of std.json.Value's number classification for the Zig version this project targets.
JSON numbers encoded as floats may lose precision when being parsed into std.json.Value.float, which supports that .number_string is reserved for the integer-overflow case rather than arbitrary strings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/schema_bounds_parsing_tests.zig` around lines 94 - 103, The current
test in schema_bounds_parsing_tests.zig is using a quoted string, so it does not
exercise the optionalFloat .number_string branch in
models.v3.Schema.parseFromJson. Update or add a test that feeds an unquoted
overflowing integer literal through parseJsonValue and verifies
Schema.multipleOf is parsed from the .number_string path, while keeping the
existing null-fallback coverage separate if needed.
src/models/v3.0/schema.zig (1)

6-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate optionalFloat helper across v3.0 and v3.2.

The identical helper is introduced verbatim in src/models/v3.2/schema.zig (Lines 6-15). Consider extracting it into a shared module (e.g. a small json_helpers.zig) imported by both version-specific schema files to avoid future drift between the two copies.

♻️ Suggested consolidation
// src/models/json_helpers.zig
const json = `@import`("std").json;

pub 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| `@import`("std").fmt.parseFloat(f64, s) catch null,
        else => null,
    };
}

Then import it in both src/models/v3.0/schema.zig and src/models/v3.2/schema.zig.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/v3.0/schema.zig` around lines 6 - 15, The optionalFloat helper is
duplicated in both versioned schema files, so consolidate it into a shared
utility module and import it from each schema. Create a shared helper (for
example json_helpers.zig) containing optionalFloat, then update the v3.0 and
v3.2 schema implementations to reference that shared function instead of keeping
separate copies. Use the existing optionalFloat symbol as the extraction target
so both schema files stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/models/v3.0/schema.zig`:
- Around line 6-15: The optionalFloat helper is duplicated in both versioned
schema files, so consolidate it into a shared utility module and import it from
each schema. Create a shared helper (for example json_helpers.zig) containing
optionalFloat, then update the v3.0 and v3.2 schema implementations to reference
that shared function instead of keeping separate copies. Use the existing
optionalFloat symbol as the extraction target so both schema files stay in sync.

In `@src/tests/schema_bounds_parsing_tests.zig`:
- Around line 94-103: The current test in schema_bounds_parsing_tests.zig is
using a quoted string, so it does not exercise the optionalFloat .number_string
branch in models.v3.Schema.parseFromJson. Update or add a test that feeds an
unquoted overflowing integer literal through parseJsonValue and verifies
Schema.multipleOf is parsed from the .number_string path, while keeping the
existing null-fallback coverage separate if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5c6300a-4a5d-4264-84df-195eb0050567

📥 Commits

Reviewing files that changed from the base of the PR and between eb97a74 and a035cc1.

📒 Files selected for processing (4)
  • src/models/v3.0/schema.zig
  • src/models/v3.2/schema.zig
  • src/tests.zig
  • src/tests/schema_bounds_parsing_tests.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/tests/schema_bounds_parsing_tests.zig (2)

9-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Heavy duplication between v3.0 and v3.2 test blocks.

The v3.0 (lines 9-130) and v3.2 (lines 132-253) test blocks are near-identical copies differing only by models.v3.Schema vs models.v32.Schema, and the negative-bounds test is duplicated again at lines 255-266 and 334-345. A comptime-parametrized helper (looping over schema types) would cut this duplication significantly and reduce future maintenance drift between versions.

Also applies to: 132-266, 334-345

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/schema_bounds_parsing_tests.zig` around lines 9 - 130, The schema
bounds tests are duplicated across v3.0 and v3.2, with the same cases repeated
for only the schema type changing. Refactor the repeated blocks in
schema_bounds_parsing_tests.zig into a comptime-driven helper or shared test
runner that accepts the schema type (for example, models.v3.Schema and
models.v32.Schema) and reuses the same assertions. Also fold the duplicated
negative-bounds cases into the shared helper so the v3 and v32 coverage stays
aligned without maintaining parallel test copies.

268-333: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Tighten the number_string cases. These assertions only prove the bounds fields deserialize to a numeric value accepted by optionalFloat; they don’t distinguish the overflow-specific path implied by the test names. Assert the parsed variant, or rename the tests if the intent is just to cover oversized numeric input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/schema_bounds_parsing_tests.zig` around lines 268 - 333, The schema
bounds tests in the `schema_bounds_parsing_tests.zig` file are too weak because
they only check that `multipleOf`, `maximum`, and `minimum` are non-null, which
does not verify the overflow-specific `number_string` parsing path. Update the
tests that call `models.v3.Schema.parseFromJson` and
`models.v32.Schema.parseFromJson` so they assert the parsed value came through
the `number_string` variant or equivalent overflow handling, not just any
numeric value. If that level of detail is not intended, rename the affected test
cases to match the actual behavior being covered.
src/models/v3.0/schema.zig (1)

3-3: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Good fix for the multipleOf/maximum/minimum union-access panic; same risk remains for integer bound fields.

Using json_helpers.optionalFloat correctly avoids crashing on .integer vs .float tag mismatches (previously val.float would panic if the JSON value was actually an integer). However, maxLength, minLength, maxItems, minItems, maxProperties, and minProperties (Lines 191-192, 194-195, 197-198) still access val.integer directly, which has the identical panic risk for malformed schema documents (e.g., a float or string value in place of an integer). src/models/v3.1/schema.zig already guards this via a local optionalInteger helper — consider extracting it into json_helpers.zig (alongside optionalFloat) and reusing it here and in v3.2 for consistency and defensive parsing.

Also applies to: 186-189

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/v3.0/schema.zig` at line 3, The integer-bound schema fields in the
v3.0 parser still read union members directly and can panic on malformed JSON.
Add and use a shared optionalInteger helper in json_helpers.zig, similar to
optionalFloat, then update the schema parsing in schema.zig (the
maxLength/minLength/maxItems/minItems/maxProperties/minProperties handling in
the schema loader) to call that helper instead of accessing val.integer
directly; also reuse the same helper in v3.2 for consistency.
src/models/json_helpers.zig (1)

4-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Swallowed parse errors bypass the catch |err| guideline.

std.fmt.parseFloat(...) catch null discards the underlying error entirely instead of using the catch |err| pattern with meaningful context. This matches the intended fallback behavior (invalid strings become null, as covered by the schema bounds tests), but per coding guidelines the error should still be captured/logged for diagnosability.

As per coding guidelines, **/*.zig: "Use Zig error sets and catch |err| pattern for error handling with meaningful context messages".

♻️ Proposed fix
-        .number_string => |s| std.fmt.parseFloat(f64, s) catch null,
+        .number_string => |s| std.fmt.parseFloat(f64, s) catch |err| {
+            std.log.debug("optionalFloat: failed to parse number_string \"{s}\": {}", .{ s, err });
+            return null;
+        },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/json_helpers.zig` around lines 4 - 12, The optionalFloat helper in
json_helpers.zig swallows parseFloat errors by using a bare catch null, which
bypasses the required catch |err| pattern. Update optionalFloat to handle the
.number_string branch with a named caught error, preserve the null fallback for
invalid numbers, and add meaningful context using the err value so parse
failures are diagnosable. Keep the behavior of returning null for bad input, but
route the failure through std.fmt.parseFloat in a way that follows the project’s
Zig error-handling guideline.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/models/json_helpers.zig`:
- Around line 4-12: The optionalFloat helper in json_helpers.zig swallows
parseFloat errors by using a bare catch null, which bypasses the required catch
|err| pattern. Update optionalFloat to handle the .number_string branch with a
named caught error, preserve the null fallback for invalid numbers, and add
meaningful context using the err value so parse failures are diagnosable. Keep
the behavior of returning null for bad input, but route the failure through
std.fmt.parseFloat in a way that follows the project’s Zig error-handling
guideline.

In `@src/models/v3.0/schema.zig`:
- Line 3: The integer-bound schema fields in the v3.0 parser still read union
members directly and can panic on malformed JSON. Add and use a shared
optionalInteger helper in json_helpers.zig, similar to optionalFloat, then
update the schema parsing in schema.zig (the
maxLength/minLength/maxItems/minItems/maxProperties/minProperties handling in
the schema loader) to call that helper instead of accessing val.integer
directly; also reuse the same helper in v3.2 for consistency.

In `@src/tests/schema_bounds_parsing_tests.zig`:
- Around line 9-130: The schema bounds tests are duplicated across v3.0 and
v3.2, with the same cases repeated for only the schema type changing. Refactor
the repeated blocks in schema_bounds_parsing_tests.zig into a comptime-driven
helper or shared test runner that accepts the schema type (for example,
models.v3.Schema and models.v32.Schema) and reuses the same assertions. Also
fold the duplicated negative-bounds cases into the shared helper so the v3 and
v32 coverage stays aligned without maintaining parallel test copies.
- Around line 268-333: The schema bounds tests in the
`schema_bounds_parsing_tests.zig` file are too weak because they only check that
`multipleOf`, `maximum`, and `minimum` are non-null, which does not verify the
overflow-specific `number_string` parsing path. Update the tests that call
`models.v3.Schema.parseFromJson` and `models.v32.Schema.parseFromJson` so they
assert the parsed value came through the `number_string` variant or equivalent
overflow handling, not just any numeric value. If that level of detail is not
intended, rename the affected test cases to match the actual behavior being
covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 949c2b51-247b-4df5-bcce-6a0fa20bdf4f

📥 Commits

Reviewing files that changed from the base of the PR and between a035cc1 and a6d29a6.

📒 Files selected for processing (5)
  • src/models/json_helpers.zig
  • src/models/v3.0/schema.zig
  • src/models/v3.1/schema.zig
  • src/models/v3.2/schema.zig
  • src/tests/schema_bounds_parsing_tests.zig

@christianhelle christianhelle self-assigned this Jul 3, 2026
@christianhelle christianhelle added the enhancement New feature or request label Jul 3, 2026
@christianhelle
christianhelle merged commit d812245 into main Jul 3, 2026
17 checks passed
@christianhelle
christianhelle deleted the optional-float-parsing branch July 3, 2026 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema parsing assume float when integer is active in JSON object union

1 participant