package dyn import ( "errors" "fmt" "slices" ) // This error is returned if the path indicates that a map or sequence is expected, but the value is nil. type cannotTraverseNilError struct { p Path } func (e cannotTraverseNilError) Error() string { component := e.p[len(e.p)-1] switch { case component.isKey(): return fmt.Sprintf("expected a map to index %q, found nil", e.p) case component.isIndex(): return fmt.Sprintf("expected a sequence to index %q, found nil", e.p) default: panic("invalid component") } } func IsCannotTraverseNilError(err error) bool { var target cannotTraverseNilError return errors.As(err, &target) } type noSuchKeyError struct { p Path } func (e noSuchKeyError) Error() string { return fmt.Sprintf("key not found at %q", e.p) } func IsNoSuchKeyError(err error) bool { var target noSuchKeyError return errors.As(err, &target) } type indexOutOfBoundsError struct { p Path } func (e indexOutOfBoundsError) Error() string { return fmt.Sprintf("index out of bounds at %q", e.p) } func IsIndexOutOfBoundsError(err error) bool { var target indexOutOfBoundsError return errors.As(err, &target) } type visitOptions struct { // The function to apply to the value once found. // // If this function returns the same value as it receives as argument, // the original visit function call returns the original value unmodified. // // If this function returns a new value, the original visit function call // returns a value with all the intermediate values updated. // // If this function returns an error, the original visit function call // returns this error and the value is left unmodified. fn func(Path, Value) (Value, error) } func visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) { if len(suffix) == 0 { return opts.fn(slices.Clone(prefix), v) } // Initialize prefix if it is empty. // It is pre-allocated to its maximum size to avoid additional allocations. if len(prefix) == 0 { prefix = make(Path, 0, len(suffix)) } component := suffix[0] suffix = suffix[1:] // Visit the value with the current component. return component.visit(v, prefix, suffix, opts) } func (component pathComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) { path := append(prefix, component) switch { case component.isKey(): // Expect a map to be set if this is a key. switch v.Kind() { case KindMap: // OK case KindNil: return InvalidValue, cannotTraverseNilError{path} default: return InvalidValue, fmt.Errorf("expected a map to index %q, found %s", path, v.Kind()) } m := v.MustMap() // Lookup current value in the map. ev, ok := m.GetByString(component.key) if !ok { return InvalidValue, noSuchKeyError{path} } // Recursively transform the value. nv, err := visit(ev, path, suffix, opts) if err != nil { return InvalidValue, err } // Return the original value if the value hasn't changed. if nv.eq(ev) { return v, nil } // Return an updated map value. m = m.Clone() m.Set(V(component.key), nv) return Value{ v: m, k: KindMap, l: v.l, }, nil case component.isIndex(): // Expect a sequence to be set if this is an index. switch v.Kind() { case KindSequence: // OK case KindNil: return InvalidValue, cannotTraverseNilError{path} default: return InvalidValue, fmt.Errorf("expected a sequence to index %q, found %s", path, v.Kind()) } s := v.MustSequence() // Lookup current value in the sequence. if component.index < 0 || component.index >= len(s) { return InvalidValue, indexOutOfBoundsError{path} } // Recursively transform the value. ev := s[component.index] nv, err := visit(ev, path, suffix, opts) if err != nil { return InvalidValue, err } // Return the original value if the value hasn't changed. if nv.eq(ev) { return v, nil } // Return an updated sequence value. s = slices.Clone(s) s[component.index] = nv return Value{ v: s, k: KindSequence, l: v.l, }, nil default: panic("invalid component") } }