Files
oikos/vendor/github.com/getkin/kin-openapi/openapi3/origin.go
dtoro febc153b7f
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
fix: add involves edge from task to agent:nomos at creation
Plus sync vendor directory for Docker build compatibility.
2026-08-11 22:03:12 +02:00

256 lines
6.6 KiB
Go

package openapi3
import (
"reflect"
"strings"
"github.com/oasdiff/yaml"
)
const originKey = "__origin__"
var originPtrType = reflect.TypeFor[*Origin]()
// Origin contains the origin of a collection.
// Key is the location of the collection itself.
// Fields is a map of the location of each scalar field in the collection.
// Sequences is a map of the location of each item in sequence-valued fields.
type Origin struct {
Key *Location `json:"key,omitempty" yaml:"key,omitempty"`
Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
Sequences map[string][]Location `json:"sequences,omitempty" yaml:"sequences,omitempty"`
}
// Location is a struct that contains the location of a field.
type Location struct {
File string `json:"file,omitempty" yaml:"file,omitempty"`
Line int `json:"line,omitempty" yaml:"line,omitempty"`
Column int `json:"column,omitempty" yaml:"column,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
}
// originFromSeq parses the compact []any sequence produced by yaml3's addOrigin.
//
// Format: [file, key_name, key_line, key_col, nf, f1_name, f1_delta, f1_col, ..., ns, s1_name, s1_count, s1_l0_delta, s1_c0, ...]
func originFromSeq(s []any) *Origin {
// Need at least: file, key_name, key_line, key_col, nf, ns
if len(s) < 6 {
return nil
}
file, _ := s[0].(string)
keyName, _ := s[1].(string)
keyLine := toInt(s[2])
keyCol := toInt(s[3])
o := &Origin{
Key: &Location{
File: file,
Line: keyLine,
Column: keyCol,
Name: keyName,
},
}
idx := 4
nf := toInt(s[idx])
idx++
if nf > 0 && idx+nf*3 <= len(s) {
o.Fields = make(map[string]Location, nf)
for range nf {
fname, _ := s[idx].(string)
delta := toInt(s[idx+1])
col := toInt(s[idx+2])
o.Fields[fname] = Location{
File: file,
Line: keyLine + delta,
Column: col,
Name: fname,
}
idx += 3
}
}
if idx >= len(s) {
return o
}
ns := toInt(s[idx])
idx++
if ns > 0 {
o.Sequences = make(map[string][]Location, ns)
for range ns {
if idx >= len(s) {
break
}
sname, _ := s[idx].(string)
idx++
if idx >= len(s) {
break
}
count := toInt(s[idx])
idx++
locs := make([]Location, count)
for j := 0; j < count && idx+2 < len(s); j++ {
name, _ := s[idx].(string)
delta := toInt(s[idx+1])
col := toInt(s[idx+2])
locs[j] = Location{File: file, Line: keyLine + delta, Column: col, Name: name}
idx += 3
}
o.Sequences[sname] = locs
}
}
return o
}
// toInt converts numeric types to int. Handles int/uint64 from YAML decoding.
func toInt(v any) int {
switch n := v.(type) {
case int:
return n
case uint64:
return int(n)
}
return 0
}
// applyOrigins walks a Go struct tree and a parallel OriginTree, setting
// Origin fields on each struct from the extracted origin data.
func applyOrigins(v any, tree *yaml.OriginTree) {
if tree == nil {
return
}
applyOriginsToValue(reflect.ValueOf(v), tree)
}
func applyOriginsToValue(val reflect.Value, tree *yaml.OriginTree) {
// Keep track of the last pointer so we can pass it to struct handlers
// (needed for calling methods like Map() on maplike types).
var ptr reflect.Value
for val.Kind() == reflect.Pointer || val.Kind() == reflect.Interface {
if val.IsNil() {
return
}
if val.Kind() == reflect.Pointer {
ptr = val
}
val = val.Elem()
}
switch val.Kind() {
case reflect.Struct:
applyOriginsToStruct(val, ptr, tree)
case reflect.Map:
applyOriginsToMap(val, tree)
case reflect.Slice:
applyOriginsToSlice(val, tree)
}
}
func applyOriginsToStruct(val reflect.Value, ptr reflect.Value, tree *yaml.OriginTree) {
typ := val.Type()
// Set Origin field for structs whose Origin field has a "-" json tag.
if tree.Origin != nil {
if sf, ok := typ.FieldByName("Origin"); ok && sf.Type == originPtrType {
tag := sf.Tag.Get("json")
if tag == "-" {
if s, ok := tree.Origin.([]any); ok {
val.FieldByName("Origin").Set(reflect.ValueOf(originFromSeq(s)))
}
}
}
}
// Recurse into exported struct fields using json tags
for i := range typ.NumField() {
sf := typ.Field(i)
if !sf.IsExported() {
continue
}
tag := jsonTagName(sf)
if tag == "" || tag == "-" {
continue
}
childTree := tree.Fields[tag]
if childTree != nil {
applyOriginsToValue(val.Field(i), childTree)
}
}
// Handle wrapper types whose inner struct has no json tag:
// - *Ref types (e.g. SchemaRef, ResponseRef) have a "Value" field
// - BoolSchema (AdditionalProperties, UnevaluatedProperties, UnevaluatedItems) has a "Schema" field
// The origin tree data applies to the inner struct, not a sub-key.
for _, fieldName := range []string{"Value", "Schema"} {
vf := val.FieldByName(fieldName)
if !vf.IsValid() || vf.Kind() != reflect.Pointer || vf.IsNil() {
continue
}
sf, _ := typ.FieldByName(fieldName)
if sf.Tag.Get("json") == "" {
applyOriginsToValue(vf, tree)
}
}
// Handle "maplike" types (Paths, Responses, Callback) whose items are
// stored in an unexported map accessible via a Map() method.
// Use the original pointer (if available) since dereferenced values
// are not addressable.
receiver := val
if ptr.IsValid() {
receiver = ptr
} else if val.CanAddr() {
receiver = val.Addr()
}
if receiver.Kind() == reflect.Pointer {
if mapMethod := receiver.MethodByName("Map"); mapMethod.IsValid() {
results := mapMethod.Call(nil)
if len(results) == 1 {
applyOriginsToMap(results[0], tree)
}
}
}
}
func applyOriginsToMap(val reflect.Value, tree *yaml.OriginTree) {
if tree.Fields == nil {
return
}
for _, key := range val.MapKeys() {
childTree := tree.Fields[key.String()]
if childTree == nil {
continue
}
elem := val.MapIndex(key)
// Map values are not addressable. For pointer-typed values we can
// recurse directly. For value types we must copy, apply, and set back.
if elem.Kind() == reflect.Pointer || elem.Kind() == reflect.Interface {
applyOriginsToValue(elem, childTree)
} else if elem.Kind() == reflect.Struct {
// Copy to a settable value
cp := reflect.New(elem.Type()).Elem()
cp.Set(elem)
applyOriginsToStruct(cp, reflect.Value{}, childTree)
val.SetMapIndex(key, cp)
}
}
}
func applyOriginsToSlice(val reflect.Value, tree *yaml.OriginTree) {
for i := 0; i < val.Len() && i < len(tree.Items); i++ {
if tree.Items[i] != nil {
applyOriginsToValue(val.Index(i), tree.Items[i])
}
}
}
// jsonTagName returns the JSON field name from a struct field's json tag.
func jsonTagName(f reflect.StructField) string {
tag := f.Tag.Get("json")
if tag == "" {
return ""
}
name, _, _ := strings.Cut(tag, ",")
return name
}