package contract import ( "encoding/json" "errors" "fmt" "io" "reflect" "strings" ) // Documentation is part of the contract compiler, so CLI and runtime agree. func applyDocumentation(f *field, tags map[string]string) error { s := f.value.schema if f.maxBytes > 0 { v := f.maxBytes s.MaxBytes = &v } if f.source == "file" && f.value.typ.Kind() == reflect.Slice { v := f.maxFiles if v == 0 { v = 16 } s.MaxItems = &v } s.Description = tags["description"] if raw, ok := tags["enumDescriptions"]; ok { if err := json.Unmarshal([]byte(raw), &s.EnumDescriptions); err != nil { return errors.New("enumDescriptions must be a JSON string array") } count := len(s.Enum) if s.Nullable && count > 0 && s.Enum[count-1] == nil { count-- } if count == 0 || len(s.EnumDescriptions) != count { return errors.New("enumDescriptions must describe every declared enum value") } for _, v := range s.EnumDescriptions { if strings.TrimSpace(v) == "" { return errors.New("enum description cannot be empty") } } } if raw, ok := tags["example"]; ok { var value any if base(f.value).typ.Kind() == reflect.String && base(f.value).scalar == nil { value = raw } else { decoder := json.NewDecoder(strings.NewReader(raw)) decoder.UseNumber() var err error value, err = decodeValue(decoder, 0) if err != nil { return errors.New("invalid example JSON") } if _, err = decoder.Token(); err != io.EOF { return errors.New("example requires one value") } } // A schema-only custom scalar never executes project decoders. if !documentationHasScalar(f.value) { v := reflect.New(f.value.typ).Elem() if err := setValue(f.value, v, value); err != nil { return fmt.Errorf("example: %w", err) } if err := checkField(*f, v); err != nil { return fmt.Errorf("example: %w", err) } } else if err := checkDocumentationScalarTree(f.value, value); err != nil { return err } s.Example = value } return nil } func documentationHasScalar(n *node) bool { if n.scalar != nil { return true } if n.elem != nil && documentationHasScalar(n.elem) { return true } for _, f := range n.fields { if documentationHasScalar(f.value) { return true } } return false } // Metadata never invokes application decoders, including nested scalar types. func checkDocumentationScalarTree(n *node, value any) error { if value == nil { if n.typ.Kind() == reflect.Pointer || n.typ.Kind() == reflect.Interface { return nil } return errors.New("example null requires a nullable field") } if n.typ.Kind() == reflect.Pointer { return checkDocumentationScalarTree(n.elem, value) } if n.scalar != nil { for _, kind := range strings.Split(n.scalar.JSONInput, "|") { if scalarAccepts(strings.TrimSpace(kind), value) { return nil } } return errors.New("example does not match scalar input") } if !documentationHasScalar(n) { v := reflect.New(n.typ).Elem() return setValue(n, v, value) } switch n.typ.Kind() { case reflect.Slice: values, ok := value.([]any) if !ok { return errors.New("example requires an array") } if n.schema.MinItems != nil && len(values) < *n.schema.MinItems || n.schema.MaxItems != nil && len(values) > *n.schema.MaxItems { return errors.New("example violates item limits") } for _, v := range values { if err := checkDocumentationScalarTree(n.elem, v); err != nil { return err } } case reflect.Map: values, ok := value.(map[string]any) if !ok { return errors.New("example requires an object") } for _, v := range values { if err := checkDocumentationScalarTree(n.elem, v); err != nil { return err } } case reflect.Struct: values, ok := value.(map[string]any) if !ok { return errors.New("example requires an object") } declared := map[string]bool{} for _, f := range n.fields { declared[f.name] = true v, ok := values[f.name] if !ok { if !f.optional && !f.hasDefault { return errors.New("example missing field") } continue } if err := checkDocumentationScalarTree(f.value, v); err != nil { return err } if !documentationHasScalar(f.value) { typed := reflect.New(f.value.typ).Elem() if err := setValue(f.value, typed, v); err != nil { return err } if err := checkField(f, typed); err != nil { return err } } } for name := range values { if !declared[name] { return errors.New("unknown example field") } } } return nil }