From b8c804873ecc8f801ddeda9bef8beb7eee71a053 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 8 Oct 2026 17:44:29 +0200 Subject: [PATCH 1/2] refactor(openapi): decouple YAML node decoding Introduce a small nodeDecoder interface so yamlNodeToValue no longer depends on a concrete yaml.Node type, letting it work across old and new YAML node implementations. Assisted-By: docker-agent Signed-off-by: David Gageot --- go.mod | 2 +- pkg/tools/builtin/openapi/openapi.go | 17 ++--- pkg/tools/builtin/openapi/openapi_test.go | 85 +++++++++++++++++++++++ 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 347967a11..30d62e859 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,6 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.47.0 go.opentelemetry.io/otel/trace v1.47.0 go.opentelemetry.io/proto/otlp v1.11.0 - go.yaml.in/yaml/v4 v4.0.0-rc.6 golang.org/x/crypto v0.57.0 golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 @@ -220,6 +219,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect golang.org/x/mod v0.41.0 // indirect golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect diff --git a/pkg/tools/builtin/openapi/openapi.go b/pkg/tools/builtin/openapi/openapi.go index 7def9a22b..6de38eaa7 100644 --- a/pkg/tools/builtin/openapi/openapi.go +++ b/pkg/tools/builtin/openapi/openapi.go @@ -17,7 +17,6 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" - "go.yaml.in/yaml/v4" "github.com/docker/docker-agent/pkg/config" "github.com/docker/docker-agent/pkg/config/latest" @@ -388,13 +387,13 @@ func schemaProxyToProperty(proxy *base.SchemaProxy) map[string]any { enumValues := make([]any, 0, len(s.Enum)) for _, node := range s.Enum { if node != nil { - enumValues = append(enumValues, yamlNodeToValue(node)) + enumValues = append(enumValues, yamlNodeToValue(node, node.Value)) } } prop["enum"] = enumValues } if s.Default != nil { - prop["default"] = yamlNodeToValue(s.Default) + prop["default"] = yamlNodeToValue(s.Default, s.Default.Value) } return prop @@ -410,13 +409,15 @@ func schemaType(s *base.Schema) string { return "string" } -// yamlNodeToValue converts a yaml.Node to a native Go value, preserving the -// original type (int, float, bool, null) instead of returning everything as a -// string. -func yamlNodeToValue(node *yaml.Node) any { +type nodeDecoder interface { + Decode(target any) error +} + +// yamlNodeToValue preserves decoded types, falling back to the raw value on error. +func yamlNodeToValue(node nodeDecoder, fallback string) any { var v any if err := node.Decode(&v); err != nil { - return node.Value + return fallback } return v } diff --git a/pkg/tools/builtin/openapi/openapi_test.go b/pkg/tools/builtin/openapi/openapi_test.go index d3591e95b..2346f9dfb 100644 --- a/pkg/tools/builtin/openapi/openapi_test.go +++ b/pkg/tools/builtin/openapi/openapi_test.go @@ -3,6 +3,7 @@ package openapi import ( "context" "encoding/json" + "errors" "io" "net/http" "net/http/httptest" @@ -626,6 +627,90 @@ func TestOpenAPITool_EnumAndDefaultTypes(t *testing.T) { assert.Equal(t, 25, limitProp["default"]) } +func TestOpenAPITool_EnumAndDefaultValueTypes(t *testing.T) { + t.Parallel() + + for _, tt := range []struct { + name string + schema string + want any + }{ + {"float", `{"type":"number","enum":[1.5],"default":1.5}`, 1.5}, + {"boolean", `{"type":"boolean","enum":[false],"default":false}`, false}, + {"null", `{"type":"null","enum":[null],"default":null}`, nil}, + {"sequence", `{"type":"array","enum":[[1,true,null]],"default":[1,true,null]}`, []any{1, true, nil}}, + {"mapping", `{"type":"object","enum":[{"enabled":true}],"default":{"enabled":true}}`, map[string]any{"enabled": true}}, + } { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + spec := `{ + "openapi": "3.1.0", + "info": {"title": "Test", "version": "1.0.0"}, + "paths": { + "/items": { + "get": { + "operationId": "listItems", + "parameters": [{"name": "value", "in": "query", "schema": ` + tt.schema + `}], + "responses": {"200": {"description": "ok"}} + } + } + } + }` + specServer := serveSpec(t, spec) + toolsList, err := newOpenAPIToolForTest(specServer.URL+"/openapi.json", nil).Tools(t.Context()) + require.NoError(t, err) + require.Len(t, toolsList, 1) + + schema := toolsList[0].Parameters.(map[string]any) + prop := schema["properties"].(map[string]any)["value"].(map[string]any) + assert.Equal(t, []any{tt.want}, prop["enum"]) + require.Contains(t, prop, "default") + assert.Equal(t, tt.want, prop["default"]) + }) + } +} + +type stubNodeDecoder struct { + value any + err error +} + +func (d stubNodeDecoder) Decode(target any) error { + *target.(*any) = d.value + return d.err +} + +func TestYAMLNodeToValue(t *testing.T) { + t.Parallel() + + for _, tt := range []struct { + name string + value any + }{ + {"string", "active"}, + {"integer", 25}, + {"float", 1.5}, + {"boolean", false}, + {"null", nil}, + {"sequence", []any{1, "two", nil}}, + {"mapping", map[string]any{"enabled": true}}, + } { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.value, yamlNodeToValue(stubNodeDecoder{value: tt.value}, "fallback")) + }) + } +} + +func TestYAMLNodeToValue_DecodeError(t *testing.T) { + t.Parallel() + + decoder := stubNodeDecoder{value: "partial", err: errors.New("decode failed")} + assert.Equal(t, "raw value", yamlNodeToValue(decoder, "raw value")) + assert.Empty(t, yamlNodeToValue(decoder, "")) +} + type testEnvProvider map[string]string func (p *testEnvProvider) Get(_ context.Context, name string) (string, bool) { From e89ea8c723db10eb9a48811b1f8d7502bb7fea3c Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 8 Oct 2026 17:46:54 +0200 Subject: [PATCH 2/2] chore(deps): bump libopenapi to v0.41.5 Pulls in updated jsonpath and ordered-map deps. Signed-off-by: David Gageot Assisted-By: docker-agent --- go.mod | 7 ++++--- go.sum | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 30d62e859..5b2dd56aa 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/muesli/cancelreader v0.2.2 github.com/natefinch/atomic v1.0.1 github.com/openai/openai-go/v3 v3.61.0 - github.com/pb33f/libopenapi v0.40.0 + github.com/pb33f/libopenapi v0.41.5 github.com/rivo/uniseg v0.4.7 github.com/rumpl/harness v0.0.0-20261007225003-7683a7eab445 github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 @@ -189,8 +189,9 @@ require ( github.com/ncruces/go-strftime v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pb33f/jsonpath v0.8.3 // indirect - github.com/pb33f/ordered-map/v2 v2.3.1 // indirect + github.com/pb33f/go-yaml v0.1.1 // indirect + github.com/pb33f/jsonpath v0.8.4 // indirect + github.com/pb33f/ordered-map/v2 v2.3.2 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/segmentio/asm v1.1.3 // indirect diff --git a/go.sum b/go.sum index c8c9a4f73..1737c544c 100644 --- a/go.sum +++ b/go.sum @@ -371,14 +371,16 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pb33f/jsonpath v0.8.3 h1:gdOUYn31vic8iRSSR4I2SEv7UQf2WxLeDcUnDPpGZNE= -github.com/pb33f/jsonpath v0.8.3/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= -github.com/pb33f/libopenapi v0.40.0 h1:Hnzok6iUwo6PddYLc4YviTyBt0Q61TGeQf80KhiPdgg= -github.com/pb33f/libopenapi v0.40.0/go.mod h1:nISN8KLZBliHRwZY0V53JZJtuzI8P52WGfJ6J56z9Tg= -github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= -github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= -github.com/pb33f/testify v0.1.0 h1:g48/HDU/jn2COspS4nM0scptxiKTJ4DnbX/4ehK6IZ8= -github.com/pb33f/testify v0.1.0/go.mod h1:nq283P/jJ8hXMmdhAqfj7BJIz0y+6IOHj9q0044rKt4= +github.com/pb33f/go-yaml v0.1.1 h1:yWGmhVdwzionRnSc/hEJwcfq6PaZL2QdYzyReXBbX7Q= +github.com/pb33f/go-yaml v0.1.1/go.mod h1:QvWdkHP5VjFYAeHMQUkaPQqREaQnBNOXmWzAjNrnSdo= +github.com/pb33f/jsonpath v0.8.4 h1:Yx7fxsKl8scgERL22WhKDs2c6gYNNUonPSWEG8Y194s= +github.com/pb33f/jsonpath v0.8.4/go.mod h1:eO6mgdhw5RgzCxm/bXTi75VUs0/cCYJzdGjMCCUEI6M= +github.com/pb33f/libopenapi v0.41.5 h1:kwEFkd/6W3tmTfsWQCVsMa0a9b1x3HYIZvt9Ek4jIDw= +github.com/pb33f/libopenapi v0.41.5/go.mod h1:mUldESjO6ownR1TCcywvoYp7e4v/bCdQyiiIqsgWne0= +github.com/pb33f/ordered-map/v2 v2.3.2 h1:wDyaZ2Pv9QLh64X4utCeD5Zoi9nIv2aW3lwv172O5PQ= +github.com/pb33f/ordered-map/v2 v2.3.2/go.mod h1:1OhFrXu3OYw3kM+FXF+Ug3ugmmZ9LE8z0JfQMLvtV0w= +github.com/pb33f/testify v0.1.1 h1:mnHe7uxKt8dyNYEGUspow72VjD264DB5rOJq4bz5tJw= +github.com/pb33f/testify v0.1.1/go.mod h1:keghMqOLECF1ENzESnfM+cwPcKN5uhTOpvbtjgL6aZg= github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=