Skip to content

Commit e61922c

Browse files
austinvallebflad
andauthored
Add data source support for response body arrays (#78)
* intial implementation * added unit tests * add documentation * added changelog * Update DESIGN.md Co-authored-by: Brian Flad <[email protected]> * update to default to set --------- Co-authored-by: Brian Flad <[email protected]>
1 parent 28389a7 commit e61922c

File tree

6 files changed

+359
-12
lines changed

6 files changed

+359
-12
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: ENHANCEMENTS
2+
body: Added data source support for response body arrays
3+
time: 2023-10-19T12:06:11.101382-04:00
4+
custom:
5+
Issue: "16"

DESIGN.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,79 @@ The response body schema found will be deep merged with the query/path `paramete
8383
- Names are strictly compared, so `id` and `user_id` would be two separate attributes in a schema.
8484
- Arrays and Objects will have their child attributes merged, so `example_object.string_field` and `example_object.bool_field` will be merged into the same `SingleNestedAttribute` schema.
8585

86+
#### Collection Data Sources
87+
88+
If the response body schema for a data source is of type `array`, the schema in `items` will be mapped to a set collection attribute (`SetNested` or `Set`) at the root of the mapped data source. The name of the attribute will be the same as the data source name from the generator config. All [mapping rules](#oas-types-to-provider-attributes) will be followed for nested attributes.
89+
90+
##### Generator Config
91+
```yaml
92+
provider:
93+
name: petstore
94+
95+
data_sources:
96+
pets:
97+
read:
98+
path: /pet/findByStatus
99+
method: GET
100+
```
101+
102+
##### OpenAPI Spec
103+
```jsonc
104+
{
105+
// ... Rest of OAS
106+
"/pet/findByStatus": {
107+
"get": {
108+
"responses": {
109+
"200": {
110+
"description": "successful operation",
111+
"content": {
112+
"application/json": {
113+
"schema": {
114+
"type": "array",
115+
"items": {
116+
"$ref": "#/components/schemas/Pet"
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
}
125+
}
126+
```
127+
128+
##### Provider Code Spec output
129+
```jsonc
130+
{
131+
"datasources": [
132+
{
133+
"name": "pets",
134+
"schema": {
135+
"attributes": [
136+
{
137+
"name": "pets",
138+
"set_nested": {
139+
"computed_optional_required": "computed",
140+
"nested_object": {
141+
"attributes": [
142+
// ... mapping of #/components/schemas/Pet
143+
]
144+
}
145+
}
146+
}
147+
]
148+
}
149+
}
150+
],
151+
"provider": {
152+
"name": "petstore"
153+
}
154+
}
155+
```
156+
157+
158+
86159
### OAS Types to Provider Attributes
87160

88161
For a given OAS [`type`](https://spec.openapis.org/oas/v3.1.0#data-types) and `format` combination, the following rules will be applied for mapping to the provider code specification. Not all Provider attributes are represented natively with OAS, those types are noted below in [Unsupported Attributes](#unsupported-attributes).

internal/cmd/testdata/petstore3/provider_code_spec.json

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,86 @@
149149
}
150150
]
151151
}
152+
},
153+
{
154+
"name": "pets",
155+
"set_nested": {
156+
"computed_optional_required": "computed",
157+
"nested_object": {
158+
"attributes": [
159+
{
160+
"name": "category",
161+
"single_nested": {
162+
"computed_optional_required": "computed",
163+
"attributes": [
164+
{
165+
"name": "id",
166+
"int64": {
167+
"computed_optional_required": "computed"
168+
}
169+
},
170+
{
171+
"name": "name",
172+
"string": {
173+
"computed_optional_required": "computed"
174+
}
175+
}
176+
]
177+
}
178+
},
179+
{
180+
"name": "id",
181+
"int64": {
182+
"computed_optional_required": "computed"
183+
}
184+
},
185+
{
186+
"name": "name",
187+
"string": {
188+
"computed_optional_required": "computed"
189+
}
190+
},
191+
{
192+
"name": "photo_urls",
193+
"list": {
194+
"computed_optional_required": "computed",
195+
"element_type": {
196+
"string": {}
197+
}
198+
}
199+
},
200+
{
201+
"name": "status",
202+
"string": {
203+
"computed_optional_required": "computed",
204+
"description": "pet status in the store"
205+
}
206+
},
207+
{
208+
"name": "tags",
209+
"list_nested": {
210+
"computed_optional_required": "computed",
211+
"nested_object": {
212+
"attributes": [
213+
{
214+
"name": "id",
215+
"int64": {
216+
"computed_optional_required": "computed"
217+
}
218+
},
219+
{
220+
"name": "name",
221+
"string": {
222+
"computed_optional_required": "computed"
223+
}
224+
}
225+
]
226+
}
227+
}
228+
}
229+
]
230+
}
231+
}
152232
}
153233
]
154234
}

internal/mapper/datasource_mapper.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package mapper
55

66
import (
7+
"fmt"
78
"log/slog"
89

910
"github.com/hashicorp/terraform-plugin-codegen-openapi/internal/config"
@@ -44,7 +45,7 @@ func (m dataSourceMapper) MapToIR(logger *slog.Logger) ([]datasource.DataSource,
4445
dataSource := m.dataSources[name]
4546
dLogger := logger.With("data_source", name)
4647

47-
schema, err := generateDataSourceSchema(dLogger, dataSource)
48+
schema, err := generateDataSourceSchema(dLogger, name, dataSource)
4849
if err != nil {
4950
log.WarnLogOnError(dLogger, err, "skipping data source schema mapping")
5051
continue
@@ -59,7 +60,7 @@ func (m dataSourceMapper) MapToIR(logger *slog.Logger) ([]datasource.DataSource,
5960
return dataSourceSchemas, nil
6061
}
6162

62-
func generateDataSourceSchema(logger *slog.Logger, dataSource explorer.DataSource) (*datasource.Schema, error) {
63+
func generateDataSourceSchema(logger *slog.Logger, name string, dataSource explorer.DataSource) (*datasource.Schema, error) {
6364
dataSourceSchema := &datasource.Schema{
6465
Attributes: []datasource.Attribute{},
6566
}
@@ -72,9 +73,27 @@ func generateDataSourceSchema(logger *slog.Logger, dataSource explorer.DataSourc
7273
if err != nil {
7374
return nil, err
7475
}
75-
readResponseAttributes, schemaErr := readResponseSchema.BuildDataSourceAttributes()
76-
if schemaErr != nil {
77-
return nil, schemaErr
76+
77+
readResponseAttributes := attrmapper.DataSourceAttributes{}
78+
if readResponseSchema.Type == util.OAS_type_array {
79+
logger.Debug(fmt.Sprintf("response body is an array, building '%s' set attribute", name))
80+
81+
// API's generally don't guarantee ordering of results for collection/query responses, default mapping to set
82+
readResponseSchema.Format = util.TF_format_set
83+
84+
collectionAttribute, schemaErr := readResponseSchema.BuildDataSourceAttribute(name, schema.Computed)
85+
if schemaErr != nil {
86+
return nil, schemaErr
87+
}
88+
89+
readResponseAttributes = append(readResponseAttributes, collectionAttribute)
90+
} else {
91+
attributes, schemaErr := readResponseSchema.BuildDataSourceAttributes()
92+
if schemaErr != nil {
93+
return nil, schemaErr
94+
}
95+
96+
readResponseAttributes = attributes
7897
}
7998

8099
// ****************

0 commit comments

Comments
 (0)