Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
464 lines
12 KiB
Go
464 lines
12 KiB
Go
// Copyright 2025 The JSON Schema Go Project 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"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// marshalStructWithMap marshals its first argument to JSON, treating the field named
|
|
// mapField as an embedded map. The first argument must be a pointer to
|
|
// a struct. The underlying type of mapField must be a map[string]any, and it must have
|
|
// a "-" json tag, meaning it will not be marshaled.
|
|
//
|
|
// For example, given this struct:
|
|
//
|
|
// type S struct {
|
|
// A int
|
|
// Extra map[string] any `json:"-"`
|
|
// }
|
|
//
|
|
// and this value:
|
|
//
|
|
// s := S{A: 1, Extra: map[string]any{"B": 2}}
|
|
//
|
|
// the call marshalJSONWithMap(s, "Extra") would return
|
|
//
|
|
// {"A": 1, "B": 2}
|
|
//
|
|
// It is an error if the map contains the same key as another struct field's
|
|
// JSON name.
|
|
//
|
|
// marshalStructWithMap calls json.Marshal on a value of type T, so T must not
|
|
// have a MarshalJSON method that calls this function, on pain of infinite regress.
|
|
//
|
|
// Note that there is a similar function in mcp/util.go, but they are not the same.
|
|
// Here the function requires `-` json tag, does not clear the mapField map,
|
|
// and handles embedded struct due to the implementation of jsonNames in this package.
|
|
//
|
|
// TODO: avoid this restriction on T by forcing it to marshal in a default way.
|
|
// See https://go.dev/play/p/EgXKJHxEx_R.
|
|
func marshalStructWithMap[T any](s *T, mapField string) ([]byte, error) {
|
|
// Marshal the struct and the map separately, and concatenate the bytes.
|
|
// This strategy is dramatically less complicated than
|
|
// constructing a synthetic struct or map with the combined keys.
|
|
if s == nil {
|
|
return []byte("null"), nil
|
|
}
|
|
s2 := *s
|
|
vMapField := reflect.ValueOf(&s2).Elem().FieldByName(mapField)
|
|
mapVal := vMapField.Interface().(map[string]any)
|
|
|
|
// Check for duplicates.
|
|
names := jsonNames(reflect.TypeFor[T]())
|
|
for key := range mapVal {
|
|
if names[key] {
|
|
return nil, fmt.Errorf("map key %q duplicates struct field", key)
|
|
}
|
|
}
|
|
|
|
structBytes, err := json.Marshal(s2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshalStructWithMap(%+v): %w", s, err)
|
|
}
|
|
if len(mapVal) == 0 {
|
|
return structBytes, nil
|
|
}
|
|
mapBytes, err := json.Marshal(mapVal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(structBytes) == 2 { // must be "{}"
|
|
return mapBytes, nil
|
|
}
|
|
// "{X}" + "{Y}" => "{X,Y}"
|
|
res := append(structBytes[:len(structBytes)-1], ',')
|
|
res = append(res, mapBytes[1:]...)
|
|
return res, nil
|
|
}
|
|
|
|
// unmarshalStructWithMap is the inverse of marshalStructWithMap.
|
|
// T has the same restrictions as in that function.
|
|
//
|
|
// Note that there is a similar function in mcp/util.go, but they are not the same.
|
|
// Here jsonNames also returns fields from embedded structs, hence this function
|
|
// handles embedded structs as well.
|
|
func unmarshalStructWithMap[T any](data []byte, v *T, mapField string) error {
|
|
// Unmarshal into the struct, ignoring unknown fields.
|
|
if err := json.Unmarshal(data, v); err != nil {
|
|
return err
|
|
}
|
|
// Unmarshal into the map.
|
|
m := map[string]any{}
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
return err
|
|
}
|
|
// Delete from the map the fields of the struct.
|
|
for n := range jsonNames(reflect.TypeFor[T]()) {
|
|
delete(m, n)
|
|
}
|
|
if len(m) != 0 {
|
|
reflect.ValueOf(v).Elem().FieldByName(mapField).Set(reflect.ValueOf(m))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var jsonNamesMap sync.Map // from reflect.Type to map[string]bool
|
|
|
|
// jsonNames returns the set of JSON object keys that t will marshal into,
|
|
// including fields from embedded structs in t.
|
|
// t must be a struct type.
|
|
//
|
|
// Note that there is a similar function in mcp/util.go, but they are not the same
|
|
// Here the function recurses over embedded structs and includes fields from them.
|
|
func jsonNames(t reflect.Type) map[string]bool {
|
|
// Lock not necessary: at worst we'll duplicate work.
|
|
if val, ok := jsonNamesMap.Load(t); ok {
|
|
return val.(map[string]bool)
|
|
}
|
|
m := map[string]bool{}
|
|
for i := range t.NumField() {
|
|
field := t.Field(i)
|
|
// handle embedded structs
|
|
if field.Anonymous {
|
|
fieldType := field.Type
|
|
if fieldType.Kind() == reflect.Ptr {
|
|
fieldType = fieldType.Elem()
|
|
}
|
|
for n := range jsonNames(fieldType) {
|
|
m[n] = true
|
|
}
|
|
continue
|
|
}
|
|
info := fieldJSONInfo(field)
|
|
if !info.omit {
|
|
m[info.name] = true
|
|
}
|
|
}
|
|
jsonNamesMap.Store(t, m)
|
|
return m
|
|
}
|
|
|
|
type jsonInfo struct {
|
|
omit bool // unexported or first tag element is "-"
|
|
name string // Go field name or first tag element. Empty if omit is true.
|
|
settings map[string]bool // "omitempty", "omitzero", etc.
|
|
}
|
|
|
|
// fieldJSONInfo reports information about how encoding/json
|
|
// handles the given struct field.
|
|
// If the field is unexported, jsonInfo.omit is true and no other jsonInfo field
|
|
// is populated.
|
|
// If the field is exported and has no tag, then name is the field's name and all
|
|
// other fields are false.
|
|
// Otherwise, the information is obtained from the tag.
|
|
func fieldJSONInfo(f reflect.StructField) jsonInfo {
|
|
if !f.IsExported() {
|
|
return jsonInfo{omit: true}
|
|
}
|
|
info := jsonInfo{name: f.Name}
|
|
if tag, ok := f.Tag.Lookup("json"); ok {
|
|
name, rest, found := strings.Cut(tag, ",")
|
|
// "-" means omit, but "-," means the name is "-"
|
|
if name == "-" && !found {
|
|
return jsonInfo{omit: true}
|
|
}
|
|
if name != "" {
|
|
info.name = name
|
|
}
|
|
if len(rest) > 0 {
|
|
info.settings = map[string]bool{}
|
|
for _, s := range strings.Split(rest, ",") {
|
|
info.settings[s] = true
|
|
}
|
|
}
|
|
}
|
|
return info
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|