The rules for mapping an OpenAPI spec (OAS) to Provider Code Specification.
For generating the Provider specification, the generator config defines a single provider
object:
provider:
name: examplecloud
# This schema needs to exist in the OpenAPI spec!
schema_ref: '#/components/schemas/examplecloud_provider_schema'
name
is directly copied to the provider code specification field:provider.name
.schema_ref
is a JSON schema reference that is used to map to the Provider's schema:provider.schema
For generating Resource specifications, the generator config defines a map resources
:
resources:
thing:
create:
path: /thing
method: POST
read:
path: /thing/{id}
method: GET
update:
path: /thing
method: PUT
delete:
path: /thing/{id}
method: DELETE
In these OAS operations, the generator will search the create
and read
for schemas to map to the provider code specification. Multiple schemas will have the OAS types mapped to Provider Attributes and then be merged together; with the final result being the Resource schema
. The schemas that will be merged together (in priority order):
create
operation: requestBodyrequestBody
is the only schema required for resources. If not found, the generator will skip the resource without mapping.- Will attempt to use
application/json
content-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
create
operation: response body in responses- Will attempt to use
200
or201
response body. If not found, will grab the first available2xx
response code with a schema (lexicographic order) - Will attempt to use
application/json
content-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
- Will attempt to use
read
operation: response body in responses- Will attempt to use
200
or201
response body. If not found, will grab the first available2xx
response code with a schema (lexicographic order) - Will attempt to use
application/json
content-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
- Will attempt to use
read
operation: parameters- The generator will merge all
query
andpath
parameters to the root of the schema. - The generator will consider as parameters the ones in the OAS Path Item and the ones in the OAS Operation, merged based on the rules in the specification
- The generator will merge all
All schemas found will be deep merged together, with the requestBody
schema from the create
operation being the main schema that the others will be merged on top. The deep merge has the following characteristics:
- Only attribute name is compared, if the attribute doesn't already exist in the main schema, it will be added. Any mismatched types of the same name will not raise an error and priority will favor the main schema.
- Names are strictly compared, so
id
anduser_id
would be two separate attributes in a schema.
- Names are strictly compared, so
- Arrays and Objects will have their child attributes merged, so
example_object.string_field
andexample_object.bool_field
will be merged into the sameSingleNestedAttribute
schema.
For generating Data Source specifications, the generator config defines a map data_sources
:
data_sources:
thing:
read:
path: /thing/{id}
method: GET
The generator uses the read
operation to map to the provider code specification. Multiple schemas will have the OAS types mapped to Provider Attributes and then be merged together; with the final result being the Data Source schema
. The schemas that will be merged together (in priority order):
read
operation: parameters- The generator will merge all
query
andpath
parameters to the root of the schema. - The generator will consider as parameters the ones in the Path Item Object and the ones in the Operation Object, merged based on the rules in the specification
- The generator will merge all
read
operation: response body in responses- The response body is the only schema required for data sources. If not found, the generator will skip the data source without mapping.
- Will attempt to use
200
or201
response body. If not found, will grab the first available2xx
response code with a schema (lexicographic order) - Will attempt to use
application/json
content-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
The response body schema found will be deep merged with the query/path parameters
, with the parameters
being the main schema that the others will be merged on top. The deep merge has the following characteristics:
- Only attribute name is compared, if the attribute doesn't already exist in the main schema, it will be added. Any mismatched types of the same name will not raise an error and priority will favor the main schema.
- Names are strictly compared, so
id
anduser_id
would be two separate attributes in a schema.
- Names are strictly compared, so
- Arrays and Objects will have their child attributes merged, so
example_object.string_field
andexample_object.bool_field
will be merged into the sameSingleNestedAttribute
schema.
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 will be followed for nested attributes.
provider:
name: petstore
data_sources:
pets:
read:
path: /pet/findByStatus
method: GET
{
// ... Rest of OAS
"/pet/findByStatus": {
"get": {
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
}
}
}
}
}
{
"datasources": [
{
"name": "pets",
"schema": {
"attributes": [
{
"name": "pets",
"set_nested": {
"computed_optional_required": "computed",
"nested_object": {
"attributes": [
// ... mapping of #/components/schemas/Pet
]
}
}
}
]
}
}
],
"provider": {
"name": "petstore"
}
}
For a given OAS type
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.
Type (OAS) | Format (OAS) | Other Criteria | Provider Attribute Type |
---|---|---|---|
boolean |
- | - | BoolAttribute |
integer |
- | - | Int64Attribute |
number |
double or float |
- | Float64Attribute |
number |
- | - | NumberAttribute |
string |
- | - | StringAttribute |
array |
- | items.type == object |
ListNestedAttribute |
array |
- | items.type == (any) |
ListAttribute (nests with element types) |
array |
set |
items.type == object |
SetNestedAttribute |
array |
set |
items.type == (any) |
SetAttribute (nests with element types) |
object |
- | additionalProperties.type == object |
MapNestedAttribute |
object |
- | additionalProperties.type == (any) |
MapAttribute (nests with element types) |
object |
- | - | SingleNestedAttribute |
ListNestedBlock
,SetNestedBlock
, andSingleNestedBlock
- While the provider code specification supports blocks, the recommendation is to prefer
ListNestedAttribute
,SetNestedAttribute
, andSingleNestedAttribute
for new provider development.
- While the provider code specification supports blocks, the recommendation is to prefer
ObjectAttribute
- The generator will default to
SingleNestedAttribute
for object types to provide additional schema information.
- The generator will default to
For attributes that don't have additional schema information (ListAttribute
, SetAttribute
, and MapAttribute
), the following rules will be applied for mapping from an OAS type
and format
combination, into Provider element types.
Type (OAS) | Format (OAS) | Other Criteria | Provider Element Type |
---|---|---|---|
boolean |
- | - | BoolType |
integer |
- | - | Int64Type |
number |
double or float |
- | Float64Type |
number |
- | - | NumberType |
string |
- | - | StringType |
array |
- | - | ListType |
array |
set |
- | SetType |
object |
- | additionalProperties.type == (any) |
MapType |
object |
- | - | ObjectType |
For the provider, all fields in the provided JSON schema (provider.schema_ref
) marked as required will be mapped as required
.
If not required, then the field will be mapped as optional
.
For resources, all fields in the create
operation requestBody
OAS schema marked as required will be mapped as required
. If default is also specified, it will be mapped as computed_optional
instead.
If not required, then the field will be mapped as computed_optional
.
If the field is only present in a schema other than the create
operation requestBody
, then the field will be mapped as computed
.
For data sources, all fields in the read
operation parameters
OAS schema marked as required will be mapped as required
.
If not required, then the field will be mapped as computed_optional
.
If the field is only present in a schema other than the read
operation parameters
, then the field will be mapped as computed
.
Field (OAS) | Field (Provider Code Specification) |
---|---|
default | default (resources only) |
deprecated | deprecation_message |
description | description |
enum | validators |
format (password) | sensitive |
maximum | validators |
maxItems | validators |
maxLength | validators |
maxProperties | validators |
minimum | validators |
minItems | validators |
minLength | validators |
minProperties | validators |
pattern | validators |
uniqueItems | validators |
After all attributes have been mapped and any overrides/aliases have been applied, the attribute names mapped from the OAS will be converted (if needed) to valid Terraform Identifiers. This logic performs the following, in order:
- Removes all characters that are NOT alphanumeric or an underscore
- Removes all leading numbers
- Inserts an underscore between any lowercase letter that is immediately followed by an uppercase letter
- Lowercases the final result
See the test cases for examples on the expectations of this conversion process.
This ensures all properties from an OAS are converted to valid Terraform identifiers, but can technically cause conflicts if multiple distinct OAS properties are scrubbed to the same value:
Fake_Thing
->fake_thing
fakeThing
->fake_thing
As OpenAPI is designed to describe HTTP APIs in general, it doesn't always fully align with Terraform Provider design principles. There are pieces of logic in this generator that make assumptions on what portions of the OAS to use when mapping to the provider code specification, however there are some limitations on what can be supported, which are documented below.
Generally, multi-types are not supported by the generator as the Terraform Plugin Framework does not support multi-types. There are two specific scenarios that are supported by the generator.
Note: with multi-type support described below, the
description
will be populated from the root-level schema, see examples.
If a multi-type is detected where one of the types is null
, the other type will be used for schema mapping using the same rules defined above.
// Maps to StringAttribute
{
"nullable_string_example": {
"description": "this is the description that's used!",
"type": [
"string",
"null"
]
}
}
// Maps to Int64Attribute
{
"nullable_integer_example": {
"description": "this is the description that's used!",
"type": [
"null",
"integer"
]
}
}
// Maps to SingleNestedAttribute
{
"nullable_object_one": {
"description": "this is the description that's used!",
"anyOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/example_object_one"
}
]
}
}
// Maps to SingleNestedAttribute
{
"nullable_object_two": {
"description": "this is the description that's used!",
"oneOf": [
{
"$ref": "#/components/schemas/example_object_two"
},
{
"type": "null"
}
]
}
}
If a multi-type is detected where one of the types is a string
and the other type is a primitive
, then the resulting attribute will be a StringAttribute
.
Supported primitive
types that can be represented as string
:
number
integer
boolean
// Maps to StringAttribute
{
"stringable_number_example": {
"description": "this is the description that's used!",
"type": [
"string",
"number"
]
}
}
// Maps to StringAttribute
{
"stringable_integer_example": {
"description": "this is the description that's used!",
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
}
}
// Maps to StringAttribute
{
"stringable_boolean_example": {
"description": "this is the description that's used!",
"oneOf": [
{
"type": "string"
},
{
"type": "boolean"
}
]
}
}