Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ If the field is only present in a schema other than the `read` operation `parame
| [pattern](https://json-schema.org/draft/2020-12/json-schema-validation.html#name-pattern) | [`validators`](https://developer.hashicorp.com/terraform/plugin/code-generation/specification#validators) |
| [uniqueItems](https://json-schema.org/draft/2020-12/json-schema-validation.html#name-uniqueItems) | [`validators`](https://developer.hashicorp.com/terraform/plugin/code-generation/specification#validators) |

A `null` entry in an `enum`, which OpenAPI 3.0 requires for a `nullable` enum to accept `null`, is not mapped to an allowed value in the `OneOf` validator: Terraform already represents `null` as an unset value. An `enum` that only lists `null` produces no validator.

### Attribute Names
After all attributes have been [mapped](#oas-types-to-provider-attributes) and any overrides/aliases have been applied, the attribute names mapped from the OAS will be converted (if needed) to valid [Terraform Identifiers](https://developer.hashicorp.com/terraform/language/syntax/configuration#identifiers). This [logic](https://github.com/hashicorp/terraform-plugin-codegen-openapi/blob/main/internal/mapper/util/framework_identifier.go#L25) performs the following, in order:
1. Removes all characters that are NOT alphanumeric or an underscore
Expand Down
8 changes: 8 additions & 0 deletions internal/cmd/testdata/edgecase/generator_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ resources:
path: /composition_siblings
method: GET

nullable_enum:
create:
path: /nullable_enum
method: POST
read:
path: /nullable_enum
method: GET

nested_path:
create:
path: /parents/{parent_id}/children
Expand Down
54 changes: 54 additions & 0 deletions internal/cmd/testdata/edgecase/openapi_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,30 @@ paths:
deprecated: true
allOf:
- $ref: "#/components/schemas/composition_refinable_object"
/nullable_enum:
get:
summary: Test for nullable enums that list null as an allowed value
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/nullable_enum_object"
post:
summary: Test for nullable enums that list null as an allowed value
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/nullable_enum_object"
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/nullable_enum_object"
/parents/{parent_id}/children:
# Declared on the path item rather than the operation, so that resolving the create
# operation's path parameters has to merge the path item in.
Expand Down Expand Up @@ -309,6 +333,36 @@ components:
- active
- disabled
- expired
nullable_enum_object:
type: object
properties:
string_prop:
description: The null entry must not become an empty string in the OneOf validator
type: [string, "null"]
enum:
- a
- b
- null
integer_prop:
description: The null entry must not become 0 in the OneOf validator
type: [integer, "null"]
enum:
- 1
- 2
- null
number_prop:
description: The null entry must not become 0 in the OneOf validator
type: [number, "null"]
format: double
enum:
- 1.5
- 2.5
- null
only_null_prop:
description: An enum that only lists null produces no validator
type: [string, "null"]
enum:
- null
composition_object:
description: This description loses to the composing schema's
type: object
Expand Down
71 changes: 71 additions & 0 deletions internal/cmd/testdata/edgecase/provider_code_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,77 @@
]
}
},
{
"name": "nullable_enum",
"schema": {
"attributes": [
{
"name": "integer_prop",
"int64": {
"computed_optional_required": "computed_optional",
"description": "The null entry must not become 0 in the OneOf validator",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
}
],
"schema_definition": "int64validator.OneOf(\n1,\n2,\n)"
}
}
]
}
},
{
"name": "number_prop",
"float64": {
"computed_optional_required": "computed_optional",
"description": "The null entry must not become 0 in the OneOf validator",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
}
],
"schema_definition": "float64validator.OneOf(\n1.5,\n2.5,\n)"
}
}
]
}
},
{
"name": "only_null_prop",
"string": {
"computed_optional_required": "computed_optional",
"description": "An enum that only lists null produces no validator"
}
},
{
"name": "string_prop",
"string": {
"computed_optional_required": "computed_optional",
"description": "The null entry must not become an empty string in the OneOf validator",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.OneOf(\n\"a\",\n\"b\",\n)"
}
}
]
}
}
]
}
},
{
"name": "set_test",
"schema": {
Expand Down
6 changes: 6 additions & 0 deletions internal/mapper/oas/integer.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ func (s *OASSchema) GetIntegerValidators() []schema.Int64Validator {
var enum []int64

for _, valueNode := range s.Schema.Enum {
// A nullable enum lists null (OpenAPI 3.0 requires it), but Terraform represents null as an unset value,
// not an allowed one. Decoding null would otherwise yield the zero value.
if valueNode.ShortTag() == "!!null" {
continue
}

var value int64
if err := valueNode.Decode(&value); err != nil {
// could consider error/panic here to notify developers
Expand Down
35 changes: 35 additions & 0 deletions internal/mapper/oas/integer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,41 @@ func TestGetIntegerValidators(t *testing.T) {
},
},
},
"enum-with-null": {
schema: oas.OASSchema{
Schema: &base.Schema{
Type: []string{"integer"},
Enum: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "1"},
{Kind: yaml.ScalarNode, Value: "2"},
{Kind: yaml.ScalarNode, Tag: "!!null", Value: "null"},
},
},
},
expected: []schema.Int64Validator{
{
Custom: &schema.CustomValidator{
Imports: []code.Import{
{
Path: "github.com/hashicorp/terraform-plugin-framework-validators/int64validator",
},
},
SchemaDefinition: "int64validator.OneOf(\n1,\n2,\n)",
},
},
},
},
"enum-only-null": {
schema: oas.OASSchema{
Schema: &base.Schema{
Type: []string{"integer"},
Enum: []*yaml.Node{
{Kind: yaml.ScalarNode, Tag: "!!null", Value: "null"},
},
},
},
expected: nil,
},
"maximum": {
schema: oas.OASSchema{
Schema: &base.Schema{
Expand Down
6 changes: 6 additions & 0 deletions internal/mapper/oas/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ func (s *OASSchema) GetFloatValidators() []schema.Float64Validator {
var enum []float64

for _, valueNode := range s.Schema.Enum {
// A nullable enum lists null (OpenAPI 3.0 requires it), but Terraform represents null as an unset value,
// not an allowed one. Decoding null would otherwise yield the zero value.
if valueNode.ShortTag() == "!!null" {
continue
}

var value float64
if err := valueNode.Decode(&value); err != nil {
// could consider error/panic here to notify developers
Expand Down
35 changes: 35 additions & 0 deletions internal/mapper/oas/number_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,41 @@ func TestGetFloatValidators(t *testing.T) {
},
},
},
"enum-with-null": {
schema: oas.OASSchema{
Schema: &base.Schema{
Type: []string{"number"},
Enum: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "1.2"},
{Kind: yaml.ScalarNode, Value: "2.3"},
{Kind: yaml.ScalarNode, Tag: "!!null", Value: "null"},
},
},
},
expected: []schema.Float64Validator{
{
Custom: &schema.CustomValidator{
Imports: []code.Import{
{
Path: "github.com/hashicorp/terraform-plugin-framework-validators/float64validator",
},
},
SchemaDefinition: "float64validator.OneOf(\n1.2,\n2.3,\n)",
},
},
},
},
"enum-only-null": {
schema: oas.OASSchema{
Schema: &base.Schema{
Type: []string{"number"},
Enum: []*yaml.Node{
{Kind: yaml.ScalarNode, Tag: "!!null", Value: "null"},
},
},
},
expected: nil,
},
}

for name, testCase := range testCases {
Expand Down
6 changes: 6 additions & 0 deletions internal/mapper/oas/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ func (s *OASSchema) GetStringValidators() []schema.StringValidator {
var enum []string

for _, valueNode := range s.Schema.Enum {
// A nullable enum lists null (OpenAPI 3.0 requires it), but Terraform represents null as an unset value,
// not an allowed one. Decoding null would otherwise yield the zero value.
if valueNode.ShortTag() == "!!null" {
continue
}

var value string
if err := valueNode.Decode(&value); err != nil {
// could consider error/panic here to notify developers
Expand Down
58 changes: 58 additions & 0 deletions internal/mapper/oas/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,64 @@ func TestGetStringValidators(t *testing.T) {
},
},
},
"enum-with-null": {
schema: oas.OASSchema{
Schema: &base.Schema{
Type: []string{"string"},
Enum: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "one"},
{Kind: yaml.ScalarNode, Value: "two"},
{Kind: yaml.ScalarNode, Tag: "!!null", Value: "null"},
},
},
},
expected: []schema.StringValidator{
{
Custom: &schema.CustomValidator{
Imports: []code.Import{
{
Path: "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator",
},
},
SchemaDefinition: "stringvalidator.OneOf(\n\"one\",\n\"two\",\n)",
},
},
},
},
"enum-only-null": {
schema: oas.OASSchema{
Schema: &base.Schema{
Type: []string{"string"},
Enum: []*yaml.Node{
{Kind: yaml.ScalarNode, Tag: "!!null", Value: "null"},
},
},
},
expected: nil,
},
"enum-with-quoted-null-string": {
schema: oas.OASSchema{
Schema: &base.Schema{
Type: []string{"string"},
Enum: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "one"},
{Kind: yaml.ScalarNode, Style: yaml.DoubleQuotedStyle, Value: "null"},
},
},
},
expected: []schema.StringValidator{
{
Custom: &schema.CustomValidator{
Imports: []code.Import{
{
Path: "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator",
},
},
SchemaDefinition: "stringvalidator.OneOf(\n\"one\",\n\"null\",\n)",
},
},
},
},
"maxLength": {
schema: oas.OASSchema{
Schema: &base.Schema{
Expand Down
Loading