Skip to content

Commit d3b0da1

Browse files
authored
Fix map handling with schema composition + upgrade pb33f/libopenapi (#101)
* initial behavior * remove validation for provider ref, as it doesn't exist upstream anymore * add context from signature change * initial fix * add a nested test * update comment * add changelog
1 parent 18203e9 commit d3b0da1

File tree

17 files changed

+474
-347
lines changed

17 files changed

+474
-347
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
kind: BUG FIXES
2+
body: Fixed a bug where schemas that used `additionalProperties` with schema composition
3+
(allOf/anyOf/oneOf) would return an empty single nested attribute. Will now return
4+
map or map nested attribute.
5+
time: 2023-12-13T14:50:19.121128-05:00
6+
custom:
7+
Issue: "100"

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/hashicorp/terraform-plugin-codegen-spec v0.1.0
88
github.com/mattn/go-colorable v0.1.13
99
github.com/mitchellh/cli v1.1.5
10-
github.com/pb33f/libopenapi v0.11.0
10+
github.com/pb33f/libopenapi v0.13.22
1111
gopkg.in/yaml.v3 v3.0.1
1212
)
1313

@@ -35,7 +35,7 @@ require (
3535
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
3636
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
3737
golang.org/x/crypto v0.7.0 // indirect
38-
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
39-
golang.org/x/sync v0.3.0 // indirect
38+
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
39+
golang.org/x/sync v0.5.0 // indirect
4040
golang.org/x/sys v0.12.0 // indirect
4141
)

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
104104
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
105105
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
106106
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
107-
github.com/pb33f/libopenapi v0.11.0 h1:xhFWajHaTVXD5+hh7LHY7kVBDVEVMSO509NFs6SOiu4=
108-
github.com/pb33f/libopenapi v0.11.0/go.mod h1:s8uj6S0DjWrwZVj20ianJBz+MMjHAbeeRYNyo9ird74=
107+
github.com/pb33f/libopenapi v0.13.22 h1:QivxHLf+ZaYl2mFivUFkKZ7315mtYMipaQP5zp0U1H4=
108+
github.com/pb33f/libopenapi v0.13.22/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk=
109109
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
110110
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
111111
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@@ -148,8 +148,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
148148
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
149149
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
150150
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
151-
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
152-
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
151+
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
152+
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
153153
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
154154
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
155155
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -168,8 +168,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
168168
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
169169
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
170170
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
171-
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
172-
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
171+
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
172+
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
173173
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
174174
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
175175
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

internal/cmd/generate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020

2121
"github.com/mitchellh/cli"
2222
"github.com/pb33f/libopenapi"
23-
"github.com/pb33f/libopenapi/resolver"
23+
"github.com/pb33f/libopenapi/index"
2424
)
2525

2626
type GenerateCommand struct {
@@ -133,7 +133,7 @@ func (cmd *GenerateCommand) runInternal(logger *slog.Logger) error {
133133
// 4. Log circular references as warnings and fail on any other model building errors
134134
var errResult error
135135
for _, err := range errs {
136-
if rslvErr, ok := err.(*resolver.ResolvingError); ok {
136+
if rslvErr, ok := err.(*index.ResolvingError); ok {
137137
logger.Warn(
138138
"circular reference found in OpenAPI spec",
139139
"circular_ref", rslvErr.CircularReference.GenerateJourneyPath())

internal/cmd/testdata/edgecase/openapi_spec.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,16 @@ paths:
7373
properties:
7474
mapnested_prop:
7575
$ref: "#/components/schemas/mapnested_schema"
76+
mapnested_nullable:
77+
$ref: "#/components/schemas/mapnested_allof_oneof_nullable"
7678
map_prop:
7779
description: This is a map of strings
7880
type: object
7981
additionalProperties:
8082
type: string
83+
map_stringified:
84+
$ref: "#/components/schemas/map_allof_oneof_stringified"
85+
8186
/set_test:
8287
get:
8388
summary: Test for SetNested attributes and Set attributes in a data source
@@ -213,3 +218,33 @@ components:
213218
bool_prop:
214219
description: Bool inside a map!
215220
type: boolean
221+
mapnested_allof_oneof_nullable:
222+
description: This is a map with a nullable object
223+
type: object
224+
additionalProperties:
225+
allOf:
226+
- $ref: "#/components/schemas/nullable_object"
227+
map_allof_oneof_stringified:
228+
description: This is a map with a stringifed value
229+
type: object
230+
additionalProperties:
231+
allOf:
232+
- $ref: "#/components/schemas/stringable_number"
233+
stringable_number:
234+
oneOf:
235+
- type: string
236+
- type: number
237+
nullable_object:
238+
oneOf:
239+
- type: object
240+
required:
241+
- string_prop
242+
- bool_prop
243+
properties:
244+
string_prop:
245+
description: String inside a map!
246+
type: string
247+
bool_prop:
248+
description: Bool inside a map!
249+
type: boolean
250+
- type: "null"

internal/cmd/testdata/edgecase/provider_code_spec.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,41 @@
279279
"description": "This is a map of strings"
280280
}
281281
},
282+
{
283+
"name": "map_stringified",
284+
"map": {
285+
"computed_optional_required": "computed_optional",
286+
"element_type": {
287+
"string": {}
288+
},
289+
"description": "This is a map with a stringifed value"
290+
}
291+
},
292+
{
293+
"name": "mapnested_nullable",
294+
"map_nested": {
295+
"computed_optional_required": "computed_optional",
296+
"nested_object": {
297+
"attributes": [
298+
{
299+
"name": "bool_prop",
300+
"bool": {
301+
"computed_optional_required": "required",
302+
"description": "Bool inside a map!"
303+
}
304+
},
305+
{
306+
"name": "string_prop",
307+
"string": {
308+
"computed_optional_required": "required",
309+
"description": "String inside a map!"
310+
}
311+
}
312+
]
313+
},
314+
"description": "This is a map with a nullable object"
315+
}
316+
},
282317
{
283318
"name": "mapnested_prop",
284319
"map_nested": {

internal/cmd/testdata/kubernetes/provider_code_spec.json

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,15 +2540,21 @@
25402540
},
25412541
{
25422542
"name": "limits",
2543-
"single_nested": {
2543+
"map": {
25442544
"computed_optional_required": "computed_optional",
2545+
"element_type": {
2546+
"string": {}
2547+
},
25452548
"description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
25462549
}
25472550
},
25482551
{
25492552
"name": "requests",
2550-
"single_nested": {
2553+
"map": {
25512554
"computed_optional_required": "computed_optional",
2555+
"element_type": {
2556+
"string": {}
2557+
},
25522558
"description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
25532559
}
25542560
}
@@ -4146,15 +4152,21 @@
41464152
},
41474153
{
41484154
"name": "limits",
4149-
"single_nested": {
4155+
"map": {
41504156
"computed_optional_required": "computed_optional",
4157+
"element_type": {
4158+
"string": {}
4159+
},
41514160
"description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
41524161
}
41534162
},
41544163
{
41554164
"name": "requests",
4156-
"single_nested": {
4165+
"map": {
41574166
"computed_optional_required": "computed_optional",
4167+
"element_type": {
4168+
"string": {}
4169+
},
41584170
"description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
41594171
}
41604172
}
@@ -5773,15 +5785,21 @@
57735785
},
57745786
{
57755787
"name": "limits",
5776-
"single_nested": {
5788+
"map": {
57775789
"computed_optional_required": "computed_optional",
5790+
"element_type": {
5791+
"string": {}
5792+
},
57785793
"description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
57795794
}
57805795
},
57815796
{
57825797
"name": "requests",
5783-
"single_nested": {
5798+
"map": {
57845799
"computed_optional_required": "computed_optional",
5800+
"element_type": {
5801+
"string": {}
5802+
},
57855803
"description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
57865804
}
57875805
}
@@ -6345,8 +6363,11 @@
63456363
},
63466364
{
63476365
"name": "overhead",
6348-
"single_nested": {
6366+
"map": {
63496367
"computed_optional_required": "computed_optional",
6368+
"element_type": {
6369+
"string": {}
6370+
},
63506371
"description": "Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md"
63516372
}
63526373
},
@@ -7729,15 +7750,21 @@
77297750
"attributes": [
77307751
{
77317752
"name": "limits",
7732-
"single_nested": {
7753+
"map": {
77337754
"computed_optional_required": "computed_optional",
7755+
"element_type": {
7756+
"string": {}
7757+
},
77347758
"description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
77357759
}
77367760
},
77377761
{
77387762
"name": "requests",
7739-
"single_nested": {
7763+
"map": {
77407764
"computed_optional_required": "computed_optional",
7765+
"element_type": {
7766+
"string": {}
7767+
},
77417768
"description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
77427769
}
77437770
}

internal/config/parse.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"fmt"
99
"regexp"
1010

11-
"github.com/pb33f/libopenapi/index"
1211
"gopkg.in/yaml.v3"
1312
)
1413

@@ -139,11 +138,6 @@ func (p Provider) Validate() error {
139138
result = errors.Join(result, errors.New("must have a 'name' property"))
140139
}
141140

142-
// All schema refs must be a local, file, or http resolve type
143-
if p.SchemaRef != "" && index.DetermineReferenceResolveType(p.SchemaRef) < 0 {
144-
result = errors.Join(result, errors.New("'schema_ref' must be a valid JSON schema reference"))
145-
}
146-
147141
for _, ignore := range p.Ignores {
148142
if !attributeLocationRegex.MatchString(ignore) {
149143
result = errors.Join(result, fmt.Errorf("invalid item for ignores: %q - must be dot-separated string", ignore))

internal/config/parse_test.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,6 @@ func TestParseConfig_Invalid(t *testing.T) {
213213
input: ``,
214214
expectedErrRegex: `provider must have a 'name' property`,
215215
},
216-
"provider - invalid schema_ref - not resolvable": {
217-
input: `
218-
provider:
219-
name: example
220-
schema_ref: thisaintvalid
221-
222-
data_sources:
223-
thing_one:
224-
read:
225-
path: /example/path/to/thing/{id}
226-
method: GET`,
227-
expectedErrRegex: `provider 'schema_ref' must be a valid JSON schema reference`,
228-
},
229216
"provider - invalid ignore item": {
230217
input: `
231218
provider:

internal/explorer/config_explorer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package explorer
55

66
import (
7+
"context"
78
"errors"
89
"fmt"
910
"strings"
@@ -157,7 +158,7 @@ func extractSchemaProxy(document high.Document, componentRef string) (*highbase.
157158
}
158159

159160
// populate low-level schema, using root document.Index for resolving
160-
err = lowSchema.Build(indexRef.Node, document.Index)
161+
err = lowSchema.Build(context.Background(), indexRef.Node, document.Index)
161162
if err != nil {
162163
return nil, fmt.Errorf("error populating low-level schema: %w", err)
163164
}

0 commit comments

Comments
 (0)