From 25c96710a5911ccd966d0fa45dd46614313561d0 Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Tue, 5 May 2026 17:11:20 +0100 Subject: [PATCH 1/8] test/integration: bump github.com/getkin/kin-openapi to v0.135.0 Signed-off-by: Arthur Diniz --- test/integration/go.mod | 10 ++++++++-- test/integration/go.sum | 34 ++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/test/integration/go.mod b/test/integration/go.mod index ee4df170c..97530f828 100644 --- a/test/integration/go.mod +++ b/test/integration/go.mod @@ -6,7 +6,7 @@ replace k8s.io/kube-openapi => ../../ require ( github.com/emicklei/go-restful/v3 v3.13.0 - github.com/getkin/kin-openapi v0.103.0 + github.com/getkin/kin-openapi v0.135.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 k8s.io/kube-openapi v0.0.0 @@ -33,8 +33,14 @@ require ( github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect - github.com/invopop/yaml v0.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.9 // indirect + github.com/oasdiff/yaml3 v0.0.12 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.33.0 // indirect diff --git a/test/integration/go.sum b/test/integration/go.sum index 207aba1c2..1bf59c7d9 100644 --- a/test/integration/go.sum +++ b/test/integration/go.sum @@ -4,17 +4,15 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/getkin/kin-openapi v0.103.0 h1:F5wAtaQvPWxKCAYZ69LgHAThgu16p4u41VQtbn1U8LA= -github.com/getkin/kin-openapi v0.103.0/go.mod h1:w4lRPHiyOdwGbOkLIyk+P0qCwlu7TXPCHD/64nSXzgE= +github.com/getkin/kin-openapi v0.135.0 h1:751SjYfbiwqukYuVjwYEIKNfrSwS5YpA7DZnKSwQgtg= +github.com/getkin/kin-openapi v0.135.0/go.mod h1:6dd5FJl6RdX4usBtFBaQhk9q62Yb2J0Mk5IhUO/QqFI= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= @@ -48,17 +46,16 @@ github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6 github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= -github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -66,13 +63,20 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48= +github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM= +github.com/oasdiff/yaml3 v0.0.12 h1:75urAtPeDg2/iDEWwzNrLOWxI9N/dCh81nTTJtokt2M= +github.com/oasdiff/yaml3 v0.0.12/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -82,13 +86,15 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -112,13 +118,9 @@ golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGN google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= From 5e164c3b7dc180a559eaab366c044a644a62fd22 Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Thu, 23 Apr 2026 00:25:55 +0100 Subject: [PATCH 2/8] Fix default annotation to match JSON property names The SubStruct fields serialize as "S" and "I" in JSON, but the +default annotation used lowercase "s" and "i". Signed-off-by: Arthur Diniz --- test/integration/pkg/generated/openapi_generated.go | 2 +- test/integration/testdata/defaults/default.go | 2 +- test/integration/testdata/golden.v2.json | 4 ++-- test/integration/testdata/golden.v3.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/integration/pkg/generated/openapi_generated.go b/test/integration/pkg/generated/openapi_generated.go index 993ef5815..043e9c130 100644 --- a/test/integration/pkg/generated/openapi_generated.go +++ b/test/integration/pkg/generated/openapi_generated.go @@ -138,7 +138,7 @@ func schema_test_integration_testdata_defaults_Defaulted(ref common.ReferenceCal }, "Sub": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{"i": 5, "s": "foo"}, + Default: map[string]interface{}{"I": 5, "S": "foo"}, Ref: ref("k8s.io/kube-openapi/test/integration/testdata/defaults.SubStruct"), }, }, diff --git a/test/integration/testdata/defaults/default.go b/test/integration/testdata/defaults/default.go index 61e503e67..249fa56d4 100644 --- a/test/integration/testdata/defaults/default.go +++ b/test/integration/testdata/defaults/default.go @@ -8,7 +8,7 @@ type Defaulted struct { OtherField int // +default=["foo", "bar"] List []Item - // +default={"s": "foo", "i": 5} + // +default={"S": "foo", "I": 5} Sub *SubStruct OtherSub SubStruct diff --git a/test/integration/testdata/golden.v2.json b/test/integration/testdata/golden.v2.json index 08f757e23..546f45412 100644 --- a/test/integration/testdata/golden.v2.json +++ b/test/integration/testdata/golden.v2.json @@ -798,8 +798,8 @@ }, "Sub": { "default": { - "i": 5, - "s": "foo" + "I": 5, + "S": "foo" }, "$ref": "#/definitions/defaults.SubStruct" }, diff --git a/test/integration/testdata/golden.v3.json b/test/integration/testdata/golden.v3.json index 7a3b832a8..4550e9eb1 100644 --- a/test/integration/testdata/golden.v3.json +++ b/test/integration/testdata/golden.v3.json @@ -740,8 +740,8 @@ }, "Sub": { "default": { - "i": 5, - "s": "foo" + "I": 5, + "S": "foo" }, "allOf": [ { From aeae52e4514204965a31d4058ff914c96470ee03 Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Thu, 23 Apr 2026 00:27:48 +0100 Subject: [PATCH 3/8] Fix namedmodels --output-pkg path in integration test README The command was missing the /namedmodels suffix, causing the generated package name to not match the golden file. Signed-off-by: Arthur Diniz --- test/integration/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/README.md b/test/integration/README.md index 3bfeb42d1..9fe995048 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -28,7 +28,7 @@ $ go run ../../cmd/openapi-gen/openapi-gen.go \ $ go run ../../cmd/openapi-gen/openapi-gen.go \ --output-dir pkg/generated/namedmodels \ - --output-pkg generated \ + --output-pkg generated/namedmodels \ --output-file openapi_generated.go \ --output-model-name-file zz_generated_model_name.go \ --go-header-file ../../boilerplate/boilerplate.go.txt \ From 779b92d8c03d01d3f2631197f0f4a4044b02765a Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Tue, 5 May 2026 17:25:42 +0100 Subject: [PATCH 4/8] Populate required fields in enforced struct defaults mustEnforceDefault now includes zero values for required members in struct defaults, ensuring they pass kin-openapi's schema validation when bumping beyond v0.103.0. Signed-off-by: Arthur Diniz --- pkg/generators/openapi.go | 56 ++++++++++++- pkg/generators/openapi_test.go | 143 ++++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 4 deletions(-) diff --git a/pkg/generators/openapi.go b/pkg/generators/openapi.go index 75817f930..7a3e3bb97 100644 --- a/pkg/generators/openapi.go +++ b/pkg/generators/openapi.go @@ -952,7 +952,7 @@ func mustEnforceDefault(t *types.Type, omitEmpty bool) (interface{}, error) { return mustEnforceDefault(t.Members[0].Type, omitEmpty) } - return map[string]interface{}{}, nil + return structZeroDefault(t) case types.Builtin: if !omitEmpty { if zero, ok := openapi.OpenAPIZeroValue(t.String()); ok { @@ -967,6 +967,60 @@ func mustEnforceDefault(t *types.Type, omitEmpty bool) (interface{}, error) { } } +// structZeroDefault builds a zero-value default for a struct type that includes +// zero values for all required (non-optional) fields. This ensures the generated +// default satisfies the schema's required constraints. +func structZeroDefault(t *types.Type) (interface{}, error) { + for t.Kind == types.Pointer { + t = t.Elem + } + result := map[string]interface{}{} + for _, m := range t.Members { + if hasOpenAPITagValue(m.CommentLines, tagValueFalse) { + continue + } + if shouldInlineMembers(&m) { + inlined, err := structZeroDefault(m.Type) + if err != nil { + return nil, err + } + if inlinedMap, ok := inlined.(map[string]interface{}); ok { + for k, v := range inlinedMap { + result[k] = v + } + } + continue + } + name := getReferableName(&m) + if name == "" { + continue + } + // Skip unexported fields with no JSON tag, they are not + // serialized and do not appear in the OpenAPI schema. + jsonTags := getJsonTags(&m) + if len(jsonTags) == 0 && len(m.Name) > 0 && strings.ToLower(m.Name[:1]) == m.Name[:1] { + continue + } + optional, err := isOptional(&m) + if err != nil { + return nil, err + } + if optional { + continue + } + // Include zero value for this required field + memberOmitEmpty := strings.Contains(reflect.StructTag(m.Tags).Get("json"), "omitempty") + zero, err := mustEnforceDefault(m.Type, memberOmitEmpty) + if err != nil { + return nil, err + } + if zero != nil { + result[name] = zero + } + } + return result, nil +} + func (g openAPITypeWriter) generateDefault(comments []string, t *types.Type, omitEmpty bool, commentOwningType *types.Type) error { def, ref, err := defaultFromComments(comments, commentOwningType.Name.Package, t) if err != nil { diff --git a/pkg/generators/openapi_test.go b/pkg/generators/openapi_test.go index 57d806d5c..34dc9673e 100644 --- a/pkg/generators/openapi_test.go +++ b/pkg/generators/openapi_test.go @@ -495,7 +495,7 @@ Properties: map[string]spec.Schema{ "Field": { SchemaProps: spec.SchemaProps{ Description: "A struct field", -Default: map[string]interface {}{}, +Default: map[string]interface {}{"String":""}, Ref: ref("example.com/base/foo.Nested"), }, }, @@ -615,7 +615,7 @@ Properties: map[string]spec.Schema{ "Nested": { SchemaProps: spec.SchemaProps{ Description: "An embedded struct field", -Default: map[string]interface {}{}, +Default: map[string]interface {}{"String":""}, Ref: ref("example.com/base/foo.Nested"), }, }, @@ -2408,7 +2408,7 @@ func TestMustEnforceDefaultStruct(t *testing.T) { }, "Regular": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]interface{}{"Field": ""}, Ref: ref("example.com/base/foo.NothingSpecial"), }, }, @@ -2424,6 +2424,143 @@ func TestMustEnforceDefaultStruct(t *testing.T) { }) } +func TestStructZeroDefault(t *testing.T) { + tests := []struct { + name string + typ *types.Type + expected interface{} + }{ + { + name: "empty struct", + typ: &types.Type{ + Kind: types.Struct, + Members: []types.Member{}, + }, + expected: map[string]interface{}{}, + }, + { + name: "required string field", + typ: &types.Type{ + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Name", + Type: types.String, + Tags: `json:"Name"`, + }, + }, + }, + expected: map[string]interface{}{"Name": ""}, + }, + { + name: "optional field skipped", + typ: &types.Type{ + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Name", + Type: types.String, + Tags: `json:"Name,omitempty"`, + CommentLines: []string{"+optional"}, + }, + }, + }, + expected: map[string]interface{}{}, + }, + { + name: "required int field", + typ: &types.Type{ + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Count", + Type: types.Int32, + Tags: `json:"Count"`, + }, + }, + }, + expected: map[string]interface{}{"Count": float64(0)}, + }, + { + name: "unexported field without json tag skipped", + typ: &types.Type{ + Kind: types.Struct, + Members: []types.Member{ + { + Name: "hidden", + Type: types.String, + }, + }, + }, + expected: map[string]interface{}{}, + }, + { + name: "openapi-gen=false field skipped", + typ: &types.Type{ + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Skipped", + Type: types.String, + Tags: `json:"Skipped"`, + CommentLines: []string{"+k8s:openapi-gen=false"}, + }, + }, + }, + expected: map[string]interface{}{}, + }, + { + name: "pointer to struct is dereferenced", + typ: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{ + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Field", + Type: types.String, + Tags: `json:"Field"`, + }, + }, + }, + }, + expected: map[string]interface{}{"Field": ""}, + }, + { + name: "mixed required and optional fields", + typ: &types.Type{ + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Required", + Type: types.String, + Tags: `json:"Required"`, + }, + { + Name: "Optional", + Type: types.String, + Tags: `json:"Optional,omitempty"`, + CommentLines: []string{"+optional"}, + }, + }, + }, + expected: map[string]interface{}{"Required": ""}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := structZeroDefault(tt.typ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !cmp.Equal(result, tt.expected) { + t.Errorf("wrong result:\n%s", cmp.Diff(tt.expected, result)) + } + }) + } +} + func TestMarkerComments(t *testing.T) { inputFile := ` package foo From b9c73505c77dbb8b39af6156880fb791ae109bf5 Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Tue, 5 May 2026 17:27:22 +0100 Subject: [PATCH 5/8] test/integration: regenerate openapi_generated.go Signed-off-by: Arthur Diniz --- test/integration/pkg/generated/openapi_generated.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/pkg/generated/openapi_generated.go b/test/integration/pkg/generated/openapi_generated.go index 043e9c130..0bb7ee340 100644 --- a/test/integration/pkg/generated/openapi_generated.go +++ b/test/integration/pkg/generated/openapi_generated.go @@ -144,7 +144,7 @@ func schema_test_integration_testdata_defaults_Defaulted(ref common.ReferenceCal }, "OtherSub": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]interface{}{"S": ""}, Ref: ref("k8s.io/kube-openapi/test/integration/testdata/defaults.SubStruct"), }, }, @@ -499,7 +499,7 @@ func schema_test_integration_testdata_listtype_MapList(ref common.ReferenceCallb Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]interface{}{"Port": 0, "Protocol": ""}, Ref: ref("k8s.io/kube-openapi/test/integration/testdata/listtype.Item"), }, }, @@ -721,7 +721,7 @@ func schema_test_integration_testdata_structtype_FieldLevelOverrideStruct(ref co }, }, SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]interface{}{"Field": 0}, Ref: ref("k8s.io/kube-openapi/test/integration/testdata/structtype.DeclaredAtomicStruct"), }, }, From c2e61b3ab369e0e7dd2824119dfba2a2192059ff Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Tue, 5 May 2026 17:30:05 +0100 Subject: [PATCH 6/8] test/integration: make valuevalidation fields with incompatible constraints optional StringValue and NumberValue have validation constraints (minLength, exclusiveMinimum) that their zero values cannot satisfy. Mark them optional to avoid enforced defaults that violate the schema. Signed-off-by: Arthur Diniz --- test/integration/testdata/valuevalidation/alpha.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/testdata/valuevalidation/alpha.go b/test/integration/testdata/valuevalidation/alpha.go index cedf90aa2..afdf13e1d 100644 --- a/test/integration/testdata/valuevalidation/alpha.go +++ b/test/integration/testdata/valuevalidation/alpha.go @@ -18,14 +18,16 @@ type Foo struct { // +k8s:validation:maxLength=5 // +k8s:validation:minLength=1 // +k8s:validation:pattern="^a.*b$" - StringValue string + // +optional + StringValue string `json:"StringValue,omitempty"` // +k8s:validation:maximum=5.0 // +k8s:validation:minimum=1.0 // +k8s:validation:exclusiveMinimum=true // +k8s:validation:exclusiveMaximum=true // +k8s:validation:multipleOf=2.0 - NumberValue float64 + // +optional + NumberValue float64 `json:"NumberValue,omitempty"` // +k8s:validation:maxItems=5 // +k8s:validation:minItems=1 From 6fac225ee150aad9efbb20145e531fd69e96222e Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Tue, 5 May 2026 17:31:44 +0100 Subject: [PATCH 7/8] test/integration: regenerate openapi_generated.go for valuevalidation changes Signed-off-by: Arthur Diniz --- test/integration/pkg/generated/openapi_generated.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/integration/pkg/generated/openapi_generated.go b/test/integration/pkg/generated/openapi_generated.go index 0bb7ee340..5c9cf31b6 100644 --- a/test/integration/pkg/generated/openapi_generated.go +++ b/test/integration/pkg/generated/openapi_generated.go @@ -1021,7 +1021,6 @@ func schema_test_integration_testdata_valuevalidation_Foo(ref common.ReferenceCa Properties: map[string]spec.Schema{ "StringValue": { SchemaProps: spec.SchemaProps{ - Default: "", MinLength: ptr.To[int64](1), MaxLength: ptr.To[int64](5), Pattern: "^a.*b$", @@ -1031,7 +1030,6 @@ func schema_test_integration_testdata_valuevalidation_Foo(ref common.ReferenceCa }, "NumberValue": { SchemaProps: spec.SchemaProps{ - Default: 0, Minimum: ptr.To[float64](1), Maximum: ptr.To[float64](5), ExclusiveMinimum: true, @@ -1088,7 +1086,7 @@ func schema_test_integration_testdata_valuevalidation_Foo(ref common.ReferenceCa }, }, }, - Required: []string{"StringValue", "NumberValue", "ArrayValue", "MapValue"}, + Required: []string{"ArrayValue", "MapValue"}, }, VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ From 0b2e70a140a302a8d2bdac6cd6c2a025b2858b12 Mon Sep 17 00:00:00 2001 From: Arthur Diniz Date: Tue, 5 May 2026 17:34:27 +0100 Subject: [PATCH 8/8] test/integration: regenerate golden v2 and v3 JSON files Signed-off-by: Arthur Diniz --- test/integration/testdata/golden.v2.json | 17 ++++++++++------- test/integration/testdata/golden.v3.json | 17 ++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/test/integration/testdata/golden.v2.json b/test/integration/testdata/golden.v2.json index 546f45412..a79e9cd6d 100644 --- a/test/integration/testdata/golden.v2.json +++ b/test/integration/testdata/golden.v2.json @@ -793,7 +793,9 @@ "default": 0 }, "OtherSub": { - "default": {}, + "default": { + "S": "" + }, "$ref": "#/definitions/defaults.SubStruct" }, "Sub": { @@ -987,7 +989,10 @@ "Field": { "type": "array", "items": { - "default": {}, + "default": { + "Port": 0, + "Protocol": "" + }, "$ref": "#/definitions/listtype.Item" }, "x-kubernetes-list-map-keys": [ @@ -1089,7 +1094,9 @@ ], "properties": { "Field": { - "default": {}, + "default": { + "Field": 0 + }, "$ref": "#/definitions/structtype.DeclaredAtomicStruct", "x-kubernetes-map-type": "atomic" }, @@ -1224,8 +1231,6 @@ "maxProperties": 5, "minProperties": 1, "required": [ - "StringValue", - "NumberValue", "ArrayValue", "MapValue" ], @@ -1252,7 +1257,6 @@ "NumberValue": { "type": "number", "format": "double", - "default": 0, "maximum": 5, "exclusiveMaximum": true, "minimum": 1, @@ -1261,7 +1265,6 @@ }, "StringValue": { "type": "string", - "default": "", "maxLength": 5, "minLength": 1, "pattern": "^a.*b$" diff --git a/test/integration/testdata/golden.v3.json b/test/integration/testdata/golden.v3.json index 4550e9eb1..0b936a475 100644 --- a/test/integration/testdata/golden.v3.json +++ b/test/integration/testdata/golden.v3.json @@ -731,7 +731,9 @@ "default": 0 }, "OtherSub": { - "default": {}, + "default": { + "S": "" + }, "allOf": [ { "$ref": "#/components/schemas/defaults.SubStruct" @@ -933,7 +935,10 @@ "Field": { "type": "array", "items": { - "default": {}, + "default": { + "Port": 0, + "Protocol": "" + }, "allOf": [ { "$ref": "#/components/schemas/listtype.Item" @@ -1043,7 +1048,9 @@ ], "properties": { "Field": { - "default": {}, + "default": { + "Field": 0 + }, "allOf": [ { "$ref": "#/components/schemas/structtype.DeclaredAtomicStruct" @@ -1186,8 +1193,6 @@ "maxProperties": 5, "minProperties": 1, "required": [ - "StringValue", - "NumberValue", "ArrayValue", "MapValue" ], @@ -1214,7 +1219,6 @@ "NumberValue": { "type": "number", "format": "double", - "default": 0, "maximum": 5, "exclusiveMaximum": true, "minimum": 1, @@ -1223,7 +1227,6 @@ }, "StringValue": { "type": "string", - "default": "", "maxLength": 5, "minLength": 1, "pattern": "^a.*b$"