jsonschema: represent integer keywords as ints

Some JSON Schema keywords, like minLength, are logically integers.
The JSON Schema spec allows them to be written as floating-point
values with zero fractional part, which is a reasonable choice, since
JSON doesn't distinguish the two.

We could simply validate these fields as integers in the normal course
of events (namely, validating the schema against its meta-schema),
but this CL does a little better: it uses *int struct fields for
these keywords, preventing them from ever being non-integers.
The UnmarshalJSON method still accepts integral floating-point values.
Marshaling is unaffected, and the results don't change: encoding/json
has always marshaled integral floats without the decimal part.

Other choices:
- Leave them as float64: you could then get away with
    Schema{MinLength: Ptr(1.5)}
  and though you couldn't get far with that, it's still irksome.

- json.Number: same problem as float64, plus it's even harder to write
  as a literal.

- int64: now `Ptr(1)` doesn't work (it's a *int, not a *int64).
  Anyway, these keywords all compare against len(x) in Go, which
  is of type int.

Change-Id: Ie3f08bd8a5deaf2a21344d6ab873551c011b12d5
Reviewed-on: https://go-review.googlesource.com/c/tools/+/668315
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Jonathan Amsterdam 2025-04-25 15:20:37 -04:00
commit 40185dd013

View file

@ -145,12 +145,12 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
str := instance.String()
n := utf8.RuneCountInString(str)
if schema.MinLength != nil {
if m := int(*schema.MinLength); n < m {
if m := *schema.MinLength; n < m {
return fmt.Errorf("minLength: %q contains %d Unicode code points, fewer than %d", str, n, m)
}
}
if schema.MaxLength != nil {
if m := int(*schema.MaxLength); n > m {
if m := *schema.MaxLength; n > m {
return fmt.Errorf("maxLength: %q contains %d Unicode code points, more than %d", str, n, m)
}
}
@ -268,7 +268,7 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
anns.noteIndex(i)
}
}
if nContains == 0 && (schema.MinContains == nil || int(*schema.MinContains) > 0) {
if nContains == 0 && (schema.MinContains == nil || *schema.MinContains > 0) {
return fmt.Errorf("contains: %s does not have an item matching %s",
instance, schema.Contains)
}
@ -277,23 +277,23 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.4
// TODO(jba): check that these next four keywords' values are integers.
if schema.MinContains != nil && schema.Contains != nil {
if m := int(*schema.MinContains); nContains < m {
if m := *schema.MinContains; nContains < m {
return fmt.Errorf("minContains: contains validated %d items, less than %d", nContains, m)
}
}
if schema.MaxContains != nil && schema.Contains != nil {
if m := int(*schema.MaxContains); nContains > m {
if m := *schema.MaxContains; nContains > m {
return fmt.Errorf("maxContains: contains validated %d items, greater than %d", nContains, m)
}
}
if schema.MinItems != nil {
if m := int(*schema.MinItems); instance.Len() < m {
if m := *schema.MinItems; instance.Len() < m {
return fmt.Errorf("minItems: array length %d is less than %d", instance.Len(), m)
}
}
if schema.MaxItems != nil {
if m := int(*schema.MaxItems); instance.Len() > m {
return fmt.Errorf("minItems: array length %d is greater than %d", instance.Len(), m)
if m := *schema.MaxItems; instance.Len() > m {
return fmt.Errorf("maxItems: array length %d is greater than %d", instance.Len(), m)
}
}
if schema.UniqueItems {
@ -385,12 +385,12 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.5
if schema.MinProperties != nil {
if n, m := instance.Len(), int(*schema.MinProperties); n < m {
if n, m := instance.Len(), *schema.MinProperties; n < m {
return fmt.Errorf("minProperties: object has %d properties, less than %d", n, m)
}
}
if schema.MaxProperties != nil {
if n, m := instance.Len(), int(*schema.MaxProperties); n > m {
if n, m := instance.Len(), *schema.MaxProperties; n > m {
return fmt.Errorf("maxProperties: object has %d properties, greater than %d", n, m)
}
}