diff --git a/.vscode/launch.json b/.vscode/launch.json index 53a81b1cb9..995e2b47ba 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -35,6 +35,17 @@ "cwd": "${workspaceFolder}/src/generator", "preLaunchTask": "build autorest.bicep" }, + { + "name": "Test autorest.bicep", + "type": "node", + "request": "launch", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "test" + ], + "cwd": "${workspaceFolder}/src/autorest.bicep", + "preLaunchTask": "build autorest.bicep" + }, { "type": "node", "request": "attach", @@ -50,4 +61,4 @@ "description": "Pick a specific base path to generate types for" } ] -} \ No newline at end of file +} diff --git a/src/autorest.bicep/src/type-generator.ts b/src/autorest.bicep/src/type-generator.ts index 376f6ac5e1..7e95bd5e00 100755 --- a/src/autorest.bicep/src/type-generator.ts +++ b/src/autorest.bicep/src/type-generator.ts @@ -424,9 +424,29 @@ export function generateTypes(host: AutorestExtensionHost, definition: ProviderD } } + function getObjectName(putSchema: ObjectSchema | undefined, getSchema: ObjectSchema | undefined) { + const putName = putSchema ? getSerializedName(putSchema) : undefined; + const getName = getSchema ? getSerializedName(getSchema) : undefined; + + if (putSchema) { + if (getSchema) { + if (putName !== getName) { + return { + syntheticObject: true, + definitionName: `${putName}Or${getName}`, + }; + } + } + + return {syntheticObject: false, definitionName: putName}; + } + + return {syntheticObject: false, definitionName: getName}; + } + function parseObjectType(putSchema: ObjectSchema | undefined, getSchema: ObjectSchema | undefined, includeBaseProperties: boolean) { const combinedSchema = combineAndThrowIfNull(putSchema, getSchema); - const definitionName = getSerializedName(combinedSchema); + const {syntheticObject, definitionName} = getObjectName(putSchema, getSchema); if (includeBaseProperties && namedDefinitions[definitionName]) { // if we're building a discriminated subtype, we're going to be missing the base properties @@ -451,7 +471,17 @@ export function generateTypes(host: AutorestExtensionHost, definition: ProviderD namedDefinitions[definitionName] = definition; } - for (const { propertyName, putProperty, getProperty } of getObjectTypeProperties(putSchema, getSchema, includeBaseProperties)) { + // Only make a distinction between what's defined on PUT vs GET if we're dealing with a synthetic object or a discriminated subtype. + // If the schema on both PUT and GET is the same named object (or if one of the two is undefined), + // use the combined schema as both GET and PUT schemata to prevent ReadOnly/WriteOnly flags from trickling down + // to object properties (which is problematic if shapes are reused across resources) + // + // For discriminated subtypes, Bicep's type system does not have a great way to communicate which variants are available on read vs write, but this + // can be communicated on variant properties. NB: `putSchema` and `getSchema` will only be different in a discriminated subtype if the discriminated + // object was synthetic. + const [schemaForPut, schemaForGet] = syntheticObject || !includeBaseProperties ? [putSchema, getSchema] : [combinedSchema, combinedSchema]; + + for (const { propertyName, putProperty, getProperty } of getObjectTypeProperties(schemaForPut, schemaForGet, includeBaseProperties)) { const propertyDefinition = parseType(putProperty?.schema, getProperty?.schema); if (propertyDefinition) { const description = getPropertyDescription(putProperty, getProperty); @@ -463,7 +493,7 @@ export function generateTypes(host: AutorestExtensionHost, definition: ProviderD if (combinedSchema.discriminator) { const discriminatedObjectType = factory.lookupType(definition) as DiscriminatedObjectType; - handlePolymorphicType(discriminatedObjectType, putSchema, getSchema); + handlePolymorphicType(discriminatedObjectType, schemaForPut, schemaForGet); } return definition; diff --git a/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.json b/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.json index 006539f8ef..a404bb763b 100644 --- a/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.json +++ b/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.json @@ -1 +1 @@ -[{"1":{"Kind":1}},{"1":{"Kind":2}},{"1":{"Kind":3}},{"1":{"Kind":4}},{"1":{"Kind":5}},{"1":{"Kind":6}},{"1":{"Kind":7}},{"1":{"Kind":8}},{"6":{"Value":"Test.Rp1/testType1"}},{"6":{"Value":"2021-10-31"}},{"2":{"Name":"Test.Rp1/testType1","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":8,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":9,"Flags":10,"Description":"The resource api version"},"properties":{"Type":11,"Flags":0,"Description":"TestType1 properties"},"tags":{"Type":25,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":26,"Flags":2,"Description":"Azure Resource Manager metadata containing createdBy and modifiedBy information."}}}},{"2":{"Name":"TestType1Properties","Properties":{"basicString":{"Type":4,"Flags":0,"Description":"Description for a basic string property."},"stringEnum":{"Type":14,"Flags":0,"Description":"Description for a basic enum property."},"skuTier":{"Type":19,"Flags":0,"Description":"This field is required to be implemented by the Resource Provider if the service has more than one tier, but is not required on a PUT."},"encryptionProperties":{"Type":20,"Flags":0,"Description":"TestType1 encryption properties"}}}},{"6":{"Value":"Foo"}},{"6":{"Value":"Bar"}},{"5":{"Elements":[12,13,4]}},{"6":{"Value":"Free"}},{"6":{"Value":"Basic"}},{"6":{"Value":"Standard"}},{"6":{"Value":"Premium"}},{"5":{"Elements":[15,16,17,18]}},{"2":{"Name":"EncryptionProperties","Properties":{"status":{"Type":23,"Flags":0,"Description":"Indicates whether or not the encryption is enabled for container registry."},"keyVaultProperties":{"Type":24,"Flags":0,"Description":"Key vault properties."}}}},{"6":{"Value":"enabled"}},{"6":{"Value":"disabled"}},{"5":{"Elements":[21,22,4]}},{"2":{"Name":"KeyVaultProperties","Properties":{"keyIdentifier":{"Type":4,"Flags":0,"Description":"Key vault uri to access the encryption key."},"identity":{"Type":4,"Flags":0,"Description":"The client ID of the identity which will be used to access key vault."}}}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"SystemData","Properties":{"createdBy":{"Type":4,"Flags":0,"Description":"The identity that created the resource."},"createdByType":{"Type":31,"Flags":0,"Description":"The type of identity that created the resource."},"createdAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource creation (UTC)."},"lastModifiedBy":{"Type":4,"Flags":0,"Description":"The identity that last modified the resource."},"lastModifiedByType":{"Type":36,"Flags":0,"Description":"The type of identity that last modified the resource."},"lastModifiedAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource last modification (UTC)"}}}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[27,28,29,30,4]}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[32,33,34,35,4]}},{"4":{"Name":"Test.Rp1/testType1@2021-10-31","ScopeType":8,"Body":10}},{"2":{"Name":"FoosRequest","Properties":{"someString":{"Type":4,"Flags":5,"Description":"The foo request string"}}}},{"2":{"Name":"FoosResponse","Properties":{"someString":{"Type":4,"Flags":2,"Description":"The foo response string"}}}},{"8":{"Name":"listFoos","ResourceType":"Test.Rp1/testType1","ApiVersion":"2021-10-31","Output":39,"Input":38}},{"3":{"ItemType":39}},{"8":{"Name":"listArrayOfFoos","ResourceType":"Test.Rp1/testType1","ApiVersion":"2021-10-31","Output":41}}] \ No newline at end of file +[{"1":{"Kind":1}},{"1":{"Kind":2}},{"1":{"Kind":3}},{"1":{"Kind":4}},{"1":{"Kind":5}},{"1":{"Kind":6}},{"1":{"Kind":7}},{"1":{"Kind":8}},{"6":{"Value":"Test.Rp1/testType1"}},{"6":{"Value":"2021-10-31"}},{"2":{"Name":"Test.Rp1/testType1","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":8,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":9,"Flags":10,"Description":"The resource api version"},"properties":{"Type":11,"Flags":0,"Description":"The resource properties."},"tags":{"Type":26,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":27,"Flags":2,"Description":"Azure Resource Manager metadata containing createdBy and modifiedBy information."}}}},{"2":{"Name":"TestType1CreateOrUpdatePropertiesOrTestType1Properties","Properties":{"basicString":{"Type":4,"Flags":0,"Description":"Description for a basic string property."},"stringEnum":{"Type":14,"Flags":0,"Description":"Description for a basic enum property."},"skuTier":{"Type":19,"Flags":0,"Description":"This field is required to be implemented by the Resource Provider if the service has more than one tier, but is not required on a PUT."},"encryptionProperties":{"Type":20,"Flags":0,"Description":"TestType1 encryption properties"},"locationData":{"Type":25,"Flags":2,"Description":"Metadata pertaining to the geographic location of the resource."}}}},{"6":{"Value":"Foo"}},{"6":{"Value":"Bar"}},{"5":{"Elements":[12,13,4]}},{"6":{"Value":"Free"}},{"6":{"Value":"Basic"}},{"6":{"Value":"Standard"}},{"6":{"Value":"Premium"}},{"5":{"Elements":[15,16,17,18]}},{"2":{"Name":"EncryptionProperties","Properties":{"status":{"Type":23,"Flags":0,"Description":"Indicates whether or not the encryption is enabled for container registry."},"keyVaultProperties":{"Type":24,"Flags":0,"Description":"Key vault properties."}}}},{"6":{"Value":"enabled"}},{"6":{"Value":"disabled"}},{"5":{"Elements":[21,22,4]}},{"2":{"Name":"KeyVaultProperties","Properties":{"keyIdentifier":{"Type":4,"Flags":0,"Description":"Key vault uri to access the encryption key."},"identity":{"Type":4,"Flags":0,"Description":"The client ID of the identity which will be used to access key vault."}}}},{"2":{"Name":"LocationData","Properties":{"name":{"Type":4,"Flags":1,"Description":"A canonical name for the geographic or physical location."},"city":{"Type":4,"Flags":0,"Description":"The city or locality where the resource is located."},"district":{"Type":4,"Flags":0,"Description":"The district, state, or province where the resource is located."},"countryOrRegion":{"Type":4,"Flags":0,"Description":"The country or region where the resource is located"}}}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"SystemData","Properties":{"createdBy":{"Type":4,"Flags":0,"Description":"The identity that created the resource."},"createdByType":{"Type":32,"Flags":0,"Description":"The type of identity that created the resource."},"createdAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource creation (UTC)."},"lastModifiedBy":{"Type":4,"Flags":0,"Description":"The identity that last modified the resource."},"lastModifiedByType":{"Type":37,"Flags":0,"Description":"The type of identity that last modified the resource."},"lastModifiedAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource last modification (UTC)"}}}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[28,29,30,31,4]}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[33,34,35,36,4]}},{"4":{"Name":"Test.Rp1/testType1@2021-10-31","ScopeType":8,"Body":10}},{"2":{"Name":"FoosRequest","Properties":{"someString":{"Type":4,"Flags":1,"Description":"The foo request string"},"locationData":{"Type":25,"Flags":0,"Description":"Metadata pertaining to the geographic location of the resource."}}}},{"2":{"Name":"FoosResponse","Properties":{"someString":{"Type":4,"Flags":0,"Description":"The foo response string"}}}},{"8":{"Name":"listFoos","ResourceType":"Test.Rp1/testType1","ApiVersion":"2021-10-31","Output":40,"Input":39}},{"3":{"ItemType":40}},{"8":{"Name":"listArrayOfFoos","ResourceType":"Test.Rp1/testType1","ApiVersion":"2021-10-31","Output":42}}] \ No newline at end of file diff --git a/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.md b/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.md index d61d51d846..80c5de4b08 100644 --- a/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.md +++ b/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.md @@ -7,7 +7,7 @@ * **id**: string (ReadOnly, DeployTimeConstant): The resource id * **location**: string (Required): The geo-location where the resource lives * **name**: string (Required, DeployTimeConstant): The resource name -* **properties**: [TestType1Properties](#testtype1properties): TestType1 properties +* **properties**: [TestType1CreateOrUpdatePropertiesOrTestType1Properties](#testtype1createorupdatepropertiesortesttype1properties): The resource properties. * **systemData**: [SystemData](#systemdata) (ReadOnly): Azure Resource Manager metadata containing createdBy and modifiedBy information. * **tags**: [TrackedResourceTags](#trackedresourcetags): Resource tags. * **type**: 'Test.Rp1/testType1' (ReadOnly, DeployTimeConstant): The resource type @@ -30,21 +30,29 @@ ## FoosRequest ### Properties -* **someString**: string (Required, WriteOnly): The foo request string +* **locationData**: [LocationData](#locationdata): Metadata pertaining to the geographic location of the resource. +* **someString**: string (Required): The foo request string ## FoosResponse ### Properties -* **someString**: string (ReadOnly): The foo response string +* **someString**: string: The foo response string ## FoosResponse ### Properties -* **someString**: string (ReadOnly): The foo response string +* **someString**: string: The foo response string ## KeyVaultProperties ### Properties * **identity**: string: The client ID of the identity which will be used to access key vault. * **keyIdentifier**: string: Key vault uri to access the encryption key. +## LocationData +### Properties +* **city**: string: The city or locality where the resource is located. +* **countryOrRegion**: string: The country or region where the resource is located +* **district**: string: The district, state, or province where the resource is located. +* **name**: string (Required): A canonical name for the geographic or physical location. + ## SystemData ### Properties * **createdAt**: string: The timestamp of resource creation (UTC). @@ -54,10 +62,11 @@ * **lastModifiedBy**: string: The identity that last modified the resource. * **lastModifiedByType**: 'Application' | 'Key' | 'ManagedIdentity' | 'User' | string: The type of identity that last modified the resource. -## TestType1Properties +## TestType1CreateOrUpdatePropertiesOrTestType1Properties ### Properties * **basicString**: string: Description for a basic string property. * **encryptionProperties**: [EncryptionProperties](#encryptionproperties): TestType1 encryption properties +* **locationData**: [LocationData](#locationdata) (ReadOnly): Metadata pertaining to the geographic location of the resource. * **skuTier**: 'Basic' | 'Free' | 'Premium' | 'Standard': This field is required to be implemented by the Resource Provider if the service has more than one tier, but is not required on a PUT. * **stringEnum**: 'Bar' | 'Foo' | string: Description for a basic enum property. diff --git a/src/autorest.bicep/test/integration/specs/basic/resource-manager/Test.Rp1/stable/2021-10-31/spec.json b/src/autorest.bicep/test/integration/specs/basic/resource-manager/Test.Rp1/stable/2021-10-31/spec.json index 3fb71d7dc2..101f713c5d 100644 --- a/src/autorest.bicep/test/integration/specs/basic/resource-manager/Test.Rp1/stable/2021-10-31/spec.json +++ b/src/autorest.bicep/test/integration/specs/basic/resource-manager/Test.Rp1/stable/2021-10-31/spec.json @@ -42,8 +42,22 @@ "type": "object", "description": "The testType1 resource." }, - "TestType1Properties": { - "description": "TestType1 properties", + "TestType1Input": { + "allOf": [ + { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/TrackedResource" + } + ], + "type": "object", + "properties": { + "properties": { + "$ref": "#/definitions/TestType1CreateOrUpdateProperties", + "description": "The resource properties.", + "x-ms-client-flatten": true + } + } + }, + "TestType1CreateOrUpdateProperties": { "properties": { "basicString": { "type": "string", @@ -81,6 +95,18 @@ } } }, + "TestType1Properties": { + "allOf": [ + { + "$ref": "#/definitions/TestType1CreateOrUpdateProperties" + } + ], + "properties": { + "locationData": { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/locationData" + } + } + }, "FoosResponse": { "properties": { "someString": { @@ -94,6 +120,9 @@ "someString": { "type": "string", "description": "The foo request string" + }, + "locationData": { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/locationData" } }, "required": [ @@ -204,7 +233,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/TestType1" + "$ref": "#/definitions/TestType1Input" }, "description": "The request parameters" },