go.mod: bump github.com/getkin/kin-openapi to v0.131.0

As deepmap/oapi-codegen didn't work with this newer version, upgrade to
oapi-codegen/oapi-codegen v2.

Mitigating CVE-2025-30153
This commit is contained in:
Sanne Raymaekers 2025-03-21 11:50:30 +01:00 committed by Ondřej Budai
parent c5cb0d0618
commit b2700903ae
403 changed files with 44758 additions and 16347 deletions

View file

@ -0,0 +1,202 @@
package overlay
import (
"fmt"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3"
"strings"
)
// ApplyTo will take an overlay and apply its changes to the given YAML
// document.
func (o *Overlay) ApplyTo(root *yaml.Node) error {
for _, action := range o.Actions {
var err error
if action.Remove {
err = applyRemoveAction(root, action)
} else {
err = applyUpdateAction(root, action, &[]string{})
}
if err != nil {
return err
}
}
return nil
}
func (o *Overlay) ApplyToStrict(root *yaml.Node) (error, []string) {
multiError := []string{}
warnings := []string{}
for i, action := range o.Actions {
err := validateSelectorHasAtLeastOneTarget(root, action)
if err != nil {
multiError = append(multiError, err.Error())
}
if action.Remove {
err = applyRemoveAction(root, action)
} else {
actionWarnings := []string{}
err = applyUpdateAction(root, action, &actionWarnings)
for _, warning := range actionWarnings {
warnings = append(warnings, fmt.Sprintf("update action (%v / %v) target=%s: %s", i+1, len(o.Actions), action.Target, warning))
}
}
}
if len(multiError) > 0 {
return fmt.Errorf("error applying overlay (strict): %v", strings.Join(multiError, ",")), warnings
}
return nil, warnings
}
func validateSelectorHasAtLeastOneTarget(root *yaml.Node, action Action) error {
if action.Target == "" {
return nil
}
p, err := yamlpath.NewPath(action.Target)
if err != nil {
return err
}
nodes, err := p.Find(root)
if err != nil {
return err
}
if len(nodes) == 0 {
return fmt.Errorf("selector %q did not match any targets", action.Target)
}
return nil
}
func applyRemoveAction(root *yaml.Node, action Action) error {
if action.Target == "" {
return nil
}
idx := newParentIndex(root)
p, err := yamlpath.NewPath(action.Target)
if err != nil {
return err
}
nodes, err := p.Find(root)
if err != nil {
return err
}
for _, node := range nodes {
removeNode(idx, node)
}
return nil
}
func removeNode(idx parentIndex, node *yaml.Node) {
parent := idx.getParent(node)
if parent == nil {
return
}
for i, child := range parent.Content {
if child == node {
switch parent.Kind {
case yaml.MappingNode:
// we have to delete the key too
parent.Content = append(parent.Content[:i-1], parent.Content[i+1:]...)
return
case yaml.SequenceNode:
parent.Content = append(parent.Content[:i], parent.Content[i+1:]...)
return
}
}
}
}
func applyUpdateAction(root *yaml.Node, action Action, warnings *[]string) error {
if action.Target == "" {
return nil
}
if action.Update.IsZero() {
return nil
}
p, err := yamlpath.NewPath(action.Target)
if err != nil {
return err
}
nodes, err := p.Find(root)
if err != nil {
return err
}
prior, err := yaml.Marshal(root)
if err != nil {
return err
}
for _, node := range nodes {
if err := updateNode(node, action.Update); err != nil {
return err
}
}
post, err := yaml.Marshal(root)
if err != nil {
return err
}
if warnings != nil && string(prior) == string(post) {
*warnings = append(*warnings, "does nothing")
}
return nil
}
func updateNode(node *yaml.Node, updateNode yaml.Node) error {
mergeNode(node, updateNode)
return nil
}
func mergeNode(node *yaml.Node, merge yaml.Node) {
if node.Kind != merge.Kind {
*node = merge
return
}
switch node.Kind {
default:
node.Value = merge.Value
case yaml.MappingNode:
mergeMappingNode(node, merge)
case yaml.SequenceNode:
mergeSequenceNode(node, merge)
}
}
// mergeMappingNode will perform a shallow merge of the merge node into the main
// node.
func mergeMappingNode(node *yaml.Node, merge yaml.Node) {
NextKey:
for i := 0; i < len(merge.Content); i += 2 {
mergeKey := merge.Content[i].Value
mergeValue := merge.Content[i+1]
for j := 0; j < len(node.Content); j += 2 {
nodeKey := node.Content[j].Value
if nodeKey == mergeKey {
mergeNode(node.Content[j+1], *mergeValue)
continue NextKey
}
}
node.Content = append(node.Content, merge.Content[i], mergeValue)
}
}
// mergeSequenceNode will append the merge node's content to the original node.
func mergeSequenceNode(node *yaml.Node, merge yaml.Node) {
node.Content = append(node.Content, merge.Content...)
}

View file

@ -0,0 +1,260 @@
package overlay
import (
"bytes"
"fmt"
"log"
"strings"
"gopkg.in/yaml.v3"
)
// Compare compares input specifications from two files and returns an overlay
// that will convert the first into the second.
func Compare(title string, y1 *yaml.Node, y2 yaml.Node) (*Overlay, error) {
actions, err := walkTreesAndCollectActions(simplePath{}, y1, y2)
if err != nil {
return nil, err
}
return &Overlay{
Version: "1.0.0",
Info: Info{
Title: title,
Version: "0.0.0",
},
Actions: actions,
}, nil
}
type simplePart struct {
isKey bool
key string
index int
}
func intPart(index int) simplePart {
return simplePart{
index: index,
}
}
func keyPart(key string) simplePart {
return simplePart{
isKey: true,
key: key,
}
}
func (p simplePart) String() string {
if p.isKey {
return fmt.Sprintf("[%q]", p.key)
}
return fmt.Sprintf("[%d]", p.index)
}
func (p simplePart) KeyString() string {
if p.isKey {
return p.key
}
panic("FIXME: Bug detected in overlay comparison algorithm: attempt to use non key part as key")
}
type simplePath []simplePart
func (p simplePath) WithIndex(index int) simplePath {
return append(p, intPart(index))
}
func (p simplePath) WithKey(key string) simplePath {
return append(p, keyPart(key))
}
func (p simplePath) ToJSONPath() string {
out := &strings.Builder{}
out.WriteString("$")
for _, part := range p {
out.WriteString(part.String())
}
return out.String()
}
func (p simplePath) Dir() simplePath {
return p[:len(p)-1]
}
func (p simplePath) Base() simplePart {
return p[len(p)-1]
}
func walkTreesAndCollectActions(path simplePath, y1 *yaml.Node, y2 yaml.Node) ([]Action, error) {
if y1 == nil {
return []Action{{
Target: path.Dir().ToJSONPath(),
Update: y2,
}}, nil
}
if y2.IsZero() {
return []Action{{
Target: path.ToJSONPath(),
Remove: true,
}}, nil
}
if y1.Kind != y2.Kind {
return []Action{{
Target: path.ToJSONPath(),
Update: y2,
}}, nil
}
switch y1.Kind {
case yaml.DocumentNode:
return walkTreesAndCollectActions(path, y1.Content[0], *y2.Content[0])
case yaml.SequenceNode:
if len(y2.Content) == len(y1.Content) {
return walkSequenceNode(path, y1, y2)
}
if len(y2.Content) == len(y1.Content)+1 &&
yamlEquals(y2.Content[:len(y1.Content)], y1.Content) {
return []Action{{
Target: path.ToJSONPath(),
Update: yaml.Node{
Kind: y1.Kind,
Content: []*yaml.Node{y2.Content[len(y1.Content)]},
},
}}, nil
}
return []Action{{
Target: path.ToJSONPath() + "[*]", // target all elements
Remove: true,
}, {
Target: path.ToJSONPath(),
Update: yaml.Node{
Kind: y1.Kind,
Content: y2.Content,
},
}}, nil
case yaml.MappingNode:
return walkMappingNode(path, y1, y2)
case yaml.ScalarNode:
if y1.Value != y2.Value {
return []Action{{
Target: path.ToJSONPath(),
Update: y2,
}}, nil
}
case yaml.AliasNode:
log.Println("YAML alias nodes are not yet supported for compare.")
}
return nil, nil
}
func yamlEquals(nodes []*yaml.Node, content []*yaml.Node) bool {
for i := range nodes {
bufA := &bytes.Buffer{}
bufB := &bytes.Buffer{}
decodeA := yaml.NewEncoder(bufA)
decodeB := yaml.NewEncoder(bufB)
err := decodeA.Encode(nodes[i])
if err != nil {
return false
}
err = decodeB.Encode(content[i])
if err != nil {
return false
}
if bufA.String() != bufB.String() {
return false
}
}
return true
}
func walkSequenceNode(path simplePath, y1 *yaml.Node, y2 yaml.Node) ([]Action, error) {
nodeLen := max(len(y1.Content), len(y2.Content))
var actions []Action
for i := 0; i < nodeLen; i++ {
var c1, c2 *yaml.Node
if i < len(y1.Content) {
c1 = y1.Content[i]
}
if i < len(y2.Content) {
c2 = y2.Content[i]
}
newActions, err := walkTreesAndCollectActions(
path.WithIndex(i),
c1, *c2)
if err != nil {
return nil, err
}
actions = append(actions, newActions...)
}
return actions, nil
}
func walkMappingNode(path simplePath, y1 *yaml.Node, y2 yaml.Node) ([]Action, error) {
var actions []Action
foundKeys := map[string]struct{}{}
// Add or update keys in y2 that differ/missing from y1
Outer:
for i := 0; i < len(y2.Content); i += 2 {
k2 := y2.Content[i]
v2 := y2.Content[i+1]
foundKeys[k2.Value] = struct{}{}
// find keys in y1 to update
for j := 0; j < len(y1.Content); j += 2 {
k1 := y1.Content[j]
v1 := y1.Content[j+1]
if k1.Value == k2.Value {
newActions, err := walkTreesAndCollectActions(
path.WithKey(k2.Value),
v1, *v2)
if err != nil {
return nil, err
}
actions = append(actions, newActions...)
continue Outer
}
}
// key not found in y1, so add it
newActions, err := walkTreesAndCollectActions(
path.WithKey(k2.Value),
nil, yaml.Node{
Kind: y1.Kind,
Content: []*yaml.Node{k2, v2},
})
if err != nil {
return nil, err
}
actions = append(actions, newActions...)
}
// look for keys in y1 that are not in y2: remove them
for i := 0; i < len(y1.Content); i += 2 {
k1 := y1.Content[i]
if _, alreadySeen := foundKeys[k1.Value]; alreadySeen {
continue
}
actions = append(actions, Action{
Target: path.WithKey(k1.Value).ToJSONPath(),
Remove: true,
})
}
return actions, nil
}

View file

@ -0,0 +1,23 @@
package overlay
import "gopkg.in/yaml.v3"
type parentIndex map[*yaml.Node]*yaml.Node
// newParentIndex returns a new parentIndex, populated for the given root node.
func newParentIndex(root *yaml.Node) parentIndex {
index := parentIndex{}
index.indexNodeRecursively(root)
return index
}
func (index parentIndex) indexNodeRecursively(parent *yaml.Node) {
for _, child := range parent.Content {
index[child] = parent
index.indexNodeRecursively(child)
}
}
func (index parentIndex) getParent(child *yaml.Node) *yaml.Node {
return index[child]
}

View file

@ -0,0 +1,58 @@
package overlay
import (
"fmt"
"gopkg.in/yaml.v3"
"io"
"os"
"path/filepath"
)
// Parse will parse the given reader as an overlay file.
func Parse(path string) (*Overlay, error) {
filePath, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path for %q: %w", path, err)
}
ro, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("failed to open overlay file at path %q: %w", path, err)
}
defer ro.Close()
var overlay Overlay
dec := yaml.NewDecoder(ro)
err = dec.Decode(&overlay)
if err != nil {
return nil, err
}
return &overlay, err
}
// Format will validate reformat the given file
func Format(path string) error {
overlay, err := Parse(path)
if err != nil {
return err
}
filePath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("failed to open overlay file at path %q: %w", path, err)
}
formatted, err := overlay.ToString()
if err != nil {
return err
}
return os.WriteFile(filePath, []byte(formatted), 0644)
}
// Format writes the file back out as YAML.
func (o *Overlay) Format(w io.Writer) error {
enc := yaml.NewEncoder(w)
enc.SetIndent(2)
return enc.Encode(o)
}

View file

@ -0,0 +1,64 @@
package overlay
import (
"bytes"
"gopkg.in/yaml.v3"
)
// Extensible provides a place for extensions to be added to components of the
// Overlay configuration. These are a map from x-* extension fields to their values.
type Extensions map[string]any
// Overlay is the top-level configuration for an OpenAPI overlay.
type Overlay struct {
Extensions `yaml:"-,inline"`
// Version is the version of the overlay configuration. As the RFC was never
// really ratifies, this value does not mean much.
Version string `yaml:"overlay"`
// Info describes the metadata for the overlay.
Info Info `yaml:"info"`
// Extends is a URL to the OpenAPI specification this overlay applies to.
Extends string `yaml:"extends,omitempty"`
// Actions is the list of actions to perform to apply the overlay.
Actions []Action `yaml:"actions"`
}
func (o *Overlay) ToString() (string, error) {
buf := bytes.NewBuffer([]byte{})
decoder := yaml.NewEncoder(buf)
decoder.SetIndent(2)
err := decoder.Encode(o)
return buf.String(), err
}
// Info describes the metadata for the overlay.
type Info struct {
Extensions `yaml:"-,inline"`
// Title is the title of the overlay.
Title string `yaml:"title"`
// Version is the version of the overlay.
Version string `yaml:"version"`
}
type Action struct {
Extensions `yaml:"-,inline"`
// Target is the JSONPath to the target of the action.
Target string `yaml:"target"`
// Description is a description of the action.
Description string `yaml:"description,omitempty"`
// Update is the sub-document to use to merge or replace in the target. This is
// ignored if Remove is set.
Update yaml.Node `yaml:"update,omitempty"`
// Remove marks the target node for removal rather than update.
Remove bool `yaml:"remove,omitempty"`
}

View file

@ -0,0 +1,19 @@
package overlay
import (
"fmt"
"gopkg.in/yaml.v3"
)
func NewTargetSelector(path, method string) string {
return fmt.Sprintf(`$["paths"]["%s"]["%s"]`, path, method)
}
func NewUpdateAction(path, method string, update yaml.Node) Action {
target := NewTargetSelector(path, method)
return Action{
Target: target,
Update: update,
}
}

View file

@ -0,0 +1,61 @@
package overlay
import (
"fmt"
"net/url"
"strings"
)
type ValidationErrors []error
func (v ValidationErrors) Error() string {
msgs := make([]string, len(v))
for i, err := range v {
msgs[i] = err.Error()
}
return strings.Join(msgs, "\n")
}
func (v ValidationErrors) Return() error {
if len(v) > 0 {
return v
}
return nil
}
func (o *Overlay) Validate() error {
errs := make(ValidationErrors, 0)
if o.Version != "1.0.0" {
errs = append(errs, fmt.Errorf("overlay version must be 1.0.0"))
}
if o.Info.Title == "" {
errs = append(errs, fmt.Errorf("overlay info title must be defined"))
}
if o.Info.Version == "" {
errs = append(errs, fmt.Errorf("overlay info version must be defined"))
}
if o.Extends != "" {
_, err := url.Parse(o.Extends)
if err != nil {
errs = append(errs, fmt.Errorf("overlay extends must be a valid URL"))
}
}
if len(o.Actions) == 0 {
errs = append(errs, fmt.Errorf("overlay must define at least one action"))
} else {
for i, action := range o.Actions {
if action.Target == "" {
errs = append(errs, fmt.Errorf("overlay action at index %d target must be defined", i))
}
if action.Remove && !action.Update.IsZero() {
errs = append(errs, fmt.Errorf("overlay action at index %d should not both set remove and define update", i))
}
}
}
return errs.Return()
}