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'nameis directly copied to the provider code specification field:provider.name.schema_refis 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: DELETEIn 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):
createoperation: requestBodyrequestBodyis the only schema required for resources. If not found, the generator will skip the resource without mapping.- Will attempt to use
application/jsoncontent-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
createoperation: response body in responses- Will attempt to use
200or201response body. If not found, will grab the first available2xxresponse code with a schema (lexicographic order) - Will attempt to use
application/jsoncontent-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
- Will attempt to use
readoperation: response body in responses- Will attempt to use
200or201response body. If not found, will grab the first available2xxresponse code with a schema (lexicographic order) - Will attempt to use
application/jsoncontent-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
- Will attempt to use
readoperation: parameters- The generator will merge all
queryandpathparameters 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
idanduser_idwould 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_fieldandexample_object.bool_fieldwill be merged into the sameSingleNestedAttributeschema.
For generating Data Source specifications, the generator config defines a map data_sources:
data_sources:
thing:
read:
path: /thing/{id}
method: GETThe 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):
readoperation: parameters- The generator will merge all
queryandpathparameters 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
readoperation: 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
200or201response body. If not found, will grab the first available2xxresponse code with a schema (lexicographic order) - Will attempt to use
application/jsoncontent-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
idanduser_idwould 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_fieldandexample_object.bool_fieldwill be merged into the sameSingleNestedAttributeschema.
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{
"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, andSingleNestedAttributefor new provider development.
- While the provider code specification supports blocks, the recommendation is to prefer
ObjectAttribute- The generator will default to
SingleNestedAttributefor 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_thingfakeThing->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
descriptionwill 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:
numberintegerboolean
// 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"
}
]
}
}
{ // ... Rest of OAS "/pet/findByStatus": { "get": { "responses": { "200": { "description": "successful operation", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Pet" } } } } } } } } }