A tool now validates its input with its InputSchema.
Change schema inference to allow explicit nulls for fields of
pointer type.
We assume the schema has no external references. For example, the
following schema cannot be handled:
{
"$ref": "https://example.com/other.json"
}
Schemas with internal references, like to a "$defs", are fine.
Change-Id: I6ee7c18c2c5cb609df0b22a66da986f7ea64bbe4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/670676
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
291 lines
7.3 KiB
Go
291 lines
7.3 KiB
Go
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
|
|
// Use of this source code is governed by an MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package jsonschema
|
|
|
|
import (
|
|
"bytes"
|
|
"cmp"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"hash/maphash"
|
|
"math"
|
|
"math/big"
|
|
"reflect"
|
|
"slices"
|
|
)
|
|
|
|
// Equal reports whether two Go values representing JSON values are equal according
|
|
// to the JSON Schema spec.
|
|
// The values must not contain cycles.
|
|
// See https://json-schema.org/draft/2020-12/json-schema-core#section-4.2.2.
|
|
// It behaves like reflect.DeepEqual, except that numbers are compared according
|
|
// to mathematical equality.
|
|
func Equal(x, y any) bool {
|
|
return equalValue(reflect.ValueOf(x), reflect.ValueOf(y))
|
|
}
|
|
|
|
func equalValue(x, y reflect.Value) bool {
|
|
// Copied from src/reflect/deepequal.go, omitting the visited check (because JSON
|
|
// values are trees).
|
|
if !x.IsValid() || !y.IsValid() {
|
|
return x.IsValid() == y.IsValid()
|
|
}
|
|
|
|
// Treat numbers specially.
|
|
rx, ok1 := jsonNumber(x)
|
|
ry, ok2 := jsonNumber(y)
|
|
if ok1 && ok2 {
|
|
return rx.Cmp(ry) == 0
|
|
}
|
|
if x.Kind() != y.Kind() {
|
|
return false
|
|
}
|
|
switch x.Kind() {
|
|
case reflect.Array:
|
|
if x.Len() != y.Len() {
|
|
return false
|
|
}
|
|
for i := range x.Len() {
|
|
if !equalValue(x.Index(i), y.Index(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Slice:
|
|
if x.IsNil() != y.IsNil() {
|
|
return false
|
|
}
|
|
if x.Len() != y.Len() {
|
|
return false
|
|
}
|
|
if x.UnsafePointer() == y.UnsafePointer() {
|
|
return true
|
|
}
|
|
// Special case for []byte, which is common.
|
|
if x.Type().Elem().Kind() == reflect.Uint8 && x.Type() == y.Type() {
|
|
return bytes.Equal(x.Bytes(), y.Bytes())
|
|
}
|
|
for i := range x.Len() {
|
|
if !equalValue(x.Index(i), y.Index(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Interface:
|
|
if x.IsNil() || y.IsNil() {
|
|
return x.IsNil() == y.IsNil()
|
|
}
|
|
return equalValue(x.Elem(), y.Elem())
|
|
case reflect.Pointer:
|
|
if x.UnsafePointer() == y.UnsafePointer() {
|
|
return true
|
|
}
|
|
return equalValue(x.Elem(), y.Elem())
|
|
case reflect.Struct:
|
|
t := x.Type()
|
|
if t != y.Type() {
|
|
return false
|
|
}
|
|
for i := range t.NumField() {
|
|
sf := t.Field(i)
|
|
if !sf.IsExported() {
|
|
continue
|
|
}
|
|
if !equalValue(x.FieldByIndex(sf.Index), y.FieldByIndex(sf.Index)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Map:
|
|
if x.IsNil() != y.IsNil() {
|
|
return false
|
|
}
|
|
if x.Len() != y.Len() {
|
|
return false
|
|
}
|
|
if x.UnsafePointer() == y.UnsafePointer() {
|
|
return true
|
|
}
|
|
iter := x.MapRange()
|
|
for iter.Next() {
|
|
vx := iter.Value()
|
|
vy := y.MapIndex(iter.Key())
|
|
if !vy.IsValid() || !equalValue(vx, vy) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Func:
|
|
if x.Type() != y.Type() {
|
|
return false
|
|
}
|
|
if x.IsNil() && y.IsNil() {
|
|
return true
|
|
}
|
|
panic("cannot compare functions")
|
|
case reflect.String:
|
|
return x.String() == y.String()
|
|
case reflect.Bool:
|
|
return x.Bool() == y.Bool()
|
|
// Ints, uints and floats handled in jsonNumber, at top of function.
|
|
default:
|
|
panic(fmt.Sprintf("unsupported kind: %s", x.Kind()))
|
|
}
|
|
}
|
|
|
|
// hashValue adds v to the data hashed by h. v must not have cycles.
|
|
// hashValue panics if the value contains functions or channels, or maps whose
|
|
// key type is not string.
|
|
// It ignores unexported fields of structs.
|
|
// Calls to hashValue with the equal values (in the sense
|
|
// of [Equal]) result in the same sequence of values written to the hash.
|
|
func hashValue(h *maphash.Hash, v reflect.Value) {
|
|
// TODO: replace writes of basic types with WriteComparable in 1.24.
|
|
|
|
writeUint := func(u uint64) {
|
|
var buf [8]byte
|
|
binary.BigEndian.PutUint64(buf[:], u)
|
|
h.Write(buf[:])
|
|
}
|
|
|
|
var write func(reflect.Value)
|
|
write = func(v reflect.Value) {
|
|
if r, ok := jsonNumber(v); ok {
|
|
// We want 1.0 and 1 to hash the same.
|
|
// big.Rats are always normalized, so they will be.
|
|
// We could do this more efficiently by handling the int and float cases
|
|
// separately, but that's premature.
|
|
writeUint(uint64(r.Sign() + 1))
|
|
h.Write(r.Num().Bytes())
|
|
h.Write(r.Denom().Bytes())
|
|
return
|
|
}
|
|
switch v.Kind() {
|
|
case reflect.Invalid:
|
|
h.WriteByte(0)
|
|
case reflect.String:
|
|
h.WriteString(v.String())
|
|
case reflect.Bool:
|
|
if v.Bool() {
|
|
h.WriteByte(1)
|
|
} else {
|
|
h.WriteByte(0)
|
|
}
|
|
case reflect.Complex64, reflect.Complex128:
|
|
c := v.Complex()
|
|
writeUint(math.Float64bits(real(c)))
|
|
writeUint(math.Float64bits(imag(c)))
|
|
case reflect.Array, reflect.Slice:
|
|
// Although we could treat []byte more efficiently,
|
|
// JSON values are unlikely to contain them.
|
|
writeUint(uint64(v.Len()))
|
|
for i := range v.Len() {
|
|
write(v.Index(i))
|
|
}
|
|
case reflect.Interface, reflect.Pointer:
|
|
write(v.Elem())
|
|
case reflect.Struct:
|
|
t := v.Type()
|
|
for i := range t.NumField() {
|
|
if sf := t.Field(i); sf.IsExported() {
|
|
write(v.FieldByIndex(sf.Index))
|
|
}
|
|
}
|
|
case reflect.Map:
|
|
if v.Type().Key().Kind() != reflect.String {
|
|
panic("map with non-string key")
|
|
}
|
|
// Sort the keys so the hash is deterministic.
|
|
keys := v.MapKeys()
|
|
// Write the length. That distinguishes between, say, two consecutive
|
|
// maps with disjoint keys from one map that has the items of both.
|
|
writeUint(uint64(len(keys)))
|
|
slices.SortFunc(keys, func(x, y reflect.Value) int { return cmp.Compare(x.String(), y.String()) })
|
|
for _, k := range keys {
|
|
write(k)
|
|
write(v.MapIndex(k))
|
|
}
|
|
// Ints, uints and floats handled in jsonNumber, at top of function.
|
|
default:
|
|
panic(fmt.Sprintf("unsupported kind: %s", v.Kind()))
|
|
}
|
|
}
|
|
|
|
write(v)
|
|
}
|
|
|
|
// jsonNumber converts a numeric value or a json.Number to a [big.Rat].
|
|
// If v is not a number, it returns nil, false.
|
|
func jsonNumber(v reflect.Value) (*big.Rat, bool) {
|
|
r := new(big.Rat)
|
|
switch {
|
|
case !v.IsValid():
|
|
return nil, false
|
|
case v.CanInt():
|
|
r.SetInt64(v.Int())
|
|
case v.CanUint():
|
|
r.SetUint64(v.Uint())
|
|
case v.CanFloat():
|
|
r.SetFloat64(v.Float())
|
|
default:
|
|
jn, ok := v.Interface().(json.Number)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
if _, ok := r.SetString(jn.String()); !ok {
|
|
// This can fail in rare cases; for example, "1e9999999".
|
|
// That is a valid JSON number, since the spec puts no limit on the size
|
|
// of the exponent.
|
|
return nil, false
|
|
}
|
|
}
|
|
return r, true
|
|
}
|
|
|
|
// jsonType returns a string describing the type of the JSON value,
|
|
// as described in the JSON Schema specification:
|
|
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.1.
|
|
// It returns "", false if the value is not valid JSON.
|
|
func jsonType(v reflect.Value) (string, bool) {
|
|
if !v.IsValid() {
|
|
// Not v.IsNil(): a nil []any is still a JSON array.
|
|
return "null", true
|
|
}
|
|
if v.CanInt() || v.CanUint() {
|
|
return "integer", true
|
|
}
|
|
if v.CanFloat() {
|
|
if _, f := math.Modf(v.Float()); f == 0 {
|
|
return "integer", true
|
|
}
|
|
return "number", true
|
|
}
|
|
switch v.Kind() {
|
|
case reflect.Bool:
|
|
return "boolean", true
|
|
case reflect.String:
|
|
return "string", true
|
|
case reflect.Slice, reflect.Array:
|
|
return "array", true
|
|
case reflect.Map, reflect.Struct:
|
|
return "object", true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func assert(cond bool, msg string) {
|
|
if !cond {
|
|
panic("assertion failed: " + msg)
|
|
}
|
|
}
|
|
|
|
// wrapf wraps *errp with the given formatted message if *errp is not nil.
|
|
func wrapf(errp *error, format string, args ...any) {
|
|
if *errp != nil {
|
|
*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
|
|
}
|
|
}
|