From 42cd6036800f21baa624b2b54b0c013c12d7707d Mon Sep 17 00:00:00 2001 From: Dan Schulte Date: Mon, 9 May 2016 15:47:57 -0700 Subject: [PATCH] Storage, Batch, and CDN schemas generating --- ...Generator.AzureResourceSchema.Tests.csproj | 2 + .../AzureResourceSchemaCodeGeneratorTests.cs | 8 +- .../Expected/Batch/Microsoft.Batch.json | 16 +- .../Expected/CDN/Microsoft.Cdn.json | 190 +++++----- .../Expected/Storage/Microsoft.Storage.json | 38 +- .../JSONSchemaTests.cs | 57 ++- .../ResourceSchemaParserTests.cs | 190 ++++++++++ .../ResourceSchemaWriterTests.cs | 314 ++++++++++++++++ ...oRest.Generator.AzureResourceSchema.csproj | 3 + .../AzureResourceSchemaCodeGenerator.cs | 215 +---------- .../AzureResourceSchema/JSONSchema.cs | 201 ++++++++++- .../Properties/AssemblyInfo.cs | 2 + .../ResourceSchemaModel.cs | 107 ++++++ .../ResourceSchemaParser.cs | 338 ++++++++++++++++++ .../ResourceSchemaWriter.cs | 134 +++++++ 15 files changed, 1462 insertions(+), 353 deletions(-) create mode 100644 AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaParserTests.cs create mode 100644 AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaWriterTests.cs create mode 100644 AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaModel.cs create mode 100644 AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaParser.cs create mode 100644 AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaWriter.cs diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AutoRest.Generator.AzureResourceSchema.Tests.csproj b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AutoRest.Generator.AzureResourceSchema.Tests.csproj index 8f0d851228ece..d6939139f1806 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AutoRest.Generator.AzureResourceSchema.Tests.csproj +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AutoRest.Generator.AzureResourceSchema.Tests.csproj @@ -69,6 +69,8 @@ + + diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AzureResourceSchemaCodeGeneratorTests.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AzureResourceSchemaCodeGeneratorTests.cs index 1c105e053783b..8e74da48f303d 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AzureResourceSchemaCodeGeneratorTests.cs +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/AzureResourceSchemaCodeGeneratorTests.cs @@ -55,12 +55,8 @@ public async void GenerateWithEmptyServiceClient() { await TestGenerate(null, new string[0], @"{ - ""id"": null, - ""$schema"": ""http://json-schema.org/draft-04/schema#"", - ""title"": null, - ""description"": null, - ""resourceDefinitions"": { } - }"); + ""$schema"": ""http://json-schema.org/draft-04/schema#"" + }"); } [Fact] diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Batch/Microsoft.Batch.json b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Batch/Microsoft.Batch.json index 286e7c21c44f4..7361e997baffa 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Batch/Microsoft.Batch.json +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Batch/Microsoft.Batch.json @@ -31,10 +31,8 @@ "description": "The user specified tags associated with the account." }, "properties": { - "autoStorage": { - "$ref": "#/definitions/AutoStorageBaseProperties", - "description": "The properties related to auto storage account." - } + "$ref": "#/definitions/AccountBaseProperties", + "description": "The properties of the account." } }, "required": [ @@ -46,6 +44,16 @@ } }, "definitions": { + "AccountBaseProperties": { + "type": "object", + "properties": { + "autoStorage": { + "$ref": "#/definitions/AutoStorageBaseProperties", + "description": "The properties related to auto storage account." + } + }, + "description": "The properties of a Batch account." + }, "AutoStorageBaseProperties": { "type": "object", "properties": { diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/CDN/Microsoft.Cdn.json b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/CDN/Microsoft.Cdn.json index 8af4ea9ade278..559de83126ace 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/CDN/Microsoft.Cdn.json +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/CDN/Microsoft.Cdn.json @@ -20,16 +20,7 @@ ] }, "properties": { - "type": "object", - "properties": { - "hostName": { - "type": "string", - "description": "The host name of the custom domain. Must be a domain name." - } - }, - "required": [ - "hostName" - ] + "$ref": "#/definitions/CustomDomainPropertiesParameters" } }, "required": [ @@ -66,55 +57,7 @@ "description": "Endpoint tags" }, "properties": { - "type": "object", - "properties": { - "originHostHeader": { - "type": "string", - "description": "The host header CDN provider will send along with content requests to origins. The default value is the host name of the origin." - }, - "originPath": { - "type": "string", - "description": "The path used for origin requests." - }, - "contentTypesToCompress": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of content types on which compression will be applied. The value for the elements should be a valid MIME type." - }, - "isCompressionEnabled": { - "type": "boolean", - "description": "Indicates whether content compression is enabled. Default value is false. If compression is enabled, the content transferred from the CDN endpoint to the end user will be compressed. The requested content must be larger than 1 byte and smaller than 1 MB." - }, - "isHttpAllowed": { - "type": "boolean", - "description": "Indicates whether HTTP traffic is allowed on the endpoint. Default value is true. At least one protocol (HTTP or HTTPS) must be allowed." - }, - "isHttpsAllowed": { - "type": "boolean", - "description": "Indicates whether https traffic is allowed on the endpoint. Default value is true. At least one protocol (HTTP or HTTPS) must be allowed." - }, - "queryStringCachingBehavior": { - "type": "string", - "enum": [ - "IgnoreQueryString", - "BypassCaching", - "UseQueryString", - "NotSet" - ], - "description": "Defines the query string caching behavior. Possible values include: 'IgnoreQueryString', 'BypassCaching', 'UseQueryString', 'NotSet'" - }, - "origins": { - "type": "array", - "items": { - "$ref": "#/definitions/DeepCreatedOrigin" - } - } - }, - "required": [ - "origins" - ] + "$ref": "#/definitions/EndpointPropertiesCreateParameters" } }, "required": [ @@ -141,24 +84,7 @@ ] }, "properties": { - "type": "object", - "properties": { - "hostName": { - "type": "string", - "description": "The address of the origin. Domain names, IPv4 addresses, and IPv6 addresses are supported." - }, - "httpPort": { - "type": "integer", - "description": "The value of the HTTP port. Must be between 1 and 65535." - }, - "httpsPort": { - "type": "integer", - "description": "The value of the HTTPS port. Must be between 1 and 65535." - } - }, - "required": [ - "hostName" - ] + "$ref": "#/definitions/OriginPropertiesParameters" } }, "required": [ @@ -166,17 +92,19 @@ "apiVersion", "properties" ], - "description": "Microsoft.Cdn/profiles.endpoints/origins" + "description": "Microsoft.Cdn/profiles/endpoints/origins" }, "profiles": { "type": "object", "properties": { "type": { + "type": "string", "enum": [ "Microsoft.Cdn/profiles" ] }, "apiVersion": { + "type": "string", "enum": [ "2016-04-02" ] @@ -200,7 +128,6 @@ "required": [ "type", "apiVersion", - "properties", "location", "sku" ], @@ -208,6 +135,18 @@ } }, "definitions": { + "CustomDomainPropertiesParameters": { + "type": "object", + "properties": { + "hostName": { + "type": "string", + "description": "The host name of the custom domain. Must be a domain name." + } + }, + "required": [ + "hostName" + ] + }, "DeepCreatedOrigin": { "type": "object", "properties": { @@ -215,6 +154,18 @@ "type": "string", "description": "Origin name" }, + "properties": { + "$ref": "#/definitions/DeepCreatedOriginProperties" + } + }, + "required": [ + "name" + ], + "description": "Deep created origins within a CDN endpoint." + }, + "DeepCreatedOriginProperties": { + "type": "object", + "properties": { "hostName": { "type": "string", "description": "The address of the origin. Domain names, IPv4 addresses, and IPv6 addresses are supported." @@ -229,10 +180,73 @@ } }, "required": [ - "name", "hostName" ], - "description": "Deep created origins within a CDN endpoint." + "description": "Properties of deep created origin on a CDN endpoint." + }, + "EndpointPropertiesCreateParameters": { + "type": "object", + "properties": { + "originHostHeader": { + "type": "string", + "description": "The host header CDN provider will send along with content requests to origins. The default value is the host name of the origin." + }, + "originPath": { + "type": "string", + "description": "The path used for origin requests." + }, + "contentTypesToCompress": { + "type": "array" + }, + "isCompressionEnabled": { + "type": "boolean", + "description": "Indicates whether content compression is enabled. Default value is false. If compression is enabled, the content transferred from the CDN endpoint to the end user will be compressed. The requested content must be larger than 1 byte and smaller than 1 MB." + }, + "isHttpAllowed": { + "type": "boolean", + "description": "Indicates whether HTTP traffic is allowed on the endpoint. Default value is true. At least one protocol (HTTP or HTTPS) must be allowed." + }, + "isHttpsAllowed": { + "type": "boolean", + "description": "Indicates whether https traffic is allowed on the endpoint. Default value is true. At least one protocol (HTTP or HTTPS) must be allowed." + }, + "queryStringCachingBehavior": { + "type": "string", + "enum": [ + "IgnoreQueryString", + "BypassCaching", + "UseQueryString", + "NotSet" + ], + "description": "Defines the query string caching behavior. Possible values include: 'IgnoreQueryString', 'BypassCaching', 'UseQueryString', 'NotSet'" + }, + "origins": { + "type": "array" + } + }, + "required": [ + "origins" + ] + }, + "OriginPropertiesParameters": { + "type": "object", + "properties": { + "hostName": { + "type": "string", + "description": "The address of the origin. Domain names, IPv4 addresses, and IPv6 addresses are supported." + }, + "httpPort": { + "type": "integer", + "description": "The value of the HTTP port. Must be between 1 and 65535." + }, + "httpsPort": { + "type": "integer", + "description": "The value of the HTTPS port. Must be between 1 and 65535." + } + }, + "required": [ + "hostName" + ] }, "Sku": { "type": "object", @@ -245,20 +259,10 @@ "Custom_Verizon", "Standard_Akamai" ], - "description": "Name of the pricing tier" + "description": "Name of the pricing tier. Possible values include: 'Standard_Verizon', 'Premium_Verizon', 'Custom_Verizon', 'Standard_Akamai'" } }, "description": "The SKU (pricing tier) of the CDN profile." - }, - "QueryStringCachingBehavior": { - "type": "string", - "enum": [ - "IgnoreQueryString", - "BypassCaching", - "UseQueryString", - "NotSet" - ], - "description": "Defines the query string caching behavior." } } -} \ No newline at end of file +} diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Storage/Microsoft.Storage.json b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Storage/Microsoft.Storage.json index fd5d7955160a6..1ae2c864a0e32 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Storage/Microsoft.Storage.json +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/Expected/Storage/Microsoft.Storage.json @@ -43,22 +43,7 @@ "description": "Gets or sets a list of key value pairs that describe the resource. These tags can be used in viewing and grouping this resource (across resource groups). A maximum of 15 tags can be provided for a resource. Each tag must have a key no greater than 128 characters and value no greater than 256 characters." }, "properties": { - "customDomain": { - "$ref": "#/definitions/CustomDomain", - "description": "User domain assigned to the storage account. Name is the CNAME source. Only one custom domain is supported per storage account at this time. To clear the existing custom domain, use an empty string for the custom domain name property." - }, - "encryption": { - "$ref": "#/definitions/Encryption", - "description": "Provides the encryption settings on the account. If left unspecified the account encryption settings will remain. The default setting is unencrypted." - }, - "accessTier": { - "type": "string", - "enum": [ - "Hot", - "Cool" - ], - "description": "Required for StandardBlob accounts. The access tier used for billing. Access tier cannot be changed more than once every 7 days (168 hours). Access tier cannot be set for StandardLRS, StandardGRS, StandardRAGRS, or PremiumLRS account types. Possible values include: 'Hot', 'Cool'" - } + "$ref": "#/definitions/StorageAccountPropertiesCreateParameters" } }, "required": [ @@ -149,6 +134,27 @@ "name" ], "description": "The SKU of the storage account." + }, + "StorageAccountPropertiesCreateParameters": { + "type": "object", + "properties": { + "customDomain": { + "$ref": "#/definitions/CustomDomain", + "description": "User domain assigned to the storage account. Name is the CNAME source. Only one custom domain is supported per storage account at this time. To clear the existing custom domain, use an empty string for the custom domain name property." + }, + "encryption": { + "$ref": "#/definitions/Encryption", + "description": "Provides the encryption settings on the account. If left unspecified the account encryption settings will remain. The default setting is unencrypted." + }, + "accessTier": { + "type": "string", + "enum": [ + "Hot", + "Cool" + ], + "description": "Required for StandardBlob accounts. The access tier used for billing. Access tier cannot be changed more than once every 7 days (168 hours). Access tier cannot be set for StandardLRS, StandardGRS, StandardRAGRS, or PremiumLRS account types. Possible values include: 'Hot', 'Cool'" + } + } } } } diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/JSONSchemaTests.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/JSONSchemaTests.cs index fe791fe293630..e994aeed02283 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/JSONSchemaTests.cs +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/JSONSchemaTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.Rest.Generator.AzureResourceSchema; +using System; +using System.Collections.Generic; using Xunit; namespace AutoRest.Generator.AzureResourceSchema.Tests @@ -9,15 +11,54 @@ namespace AutoRest.Generator.AzureResourceSchema.Tests public class JSONSchemaTests { [Fact] - public void AddProperty() + public void AddPropertyWithNullPropertyName() { - JSONSchema schema = new JSONSchema(); - JSONSchema age = new JSONSchema() - { - Type = "number" - }; - schema.AddProperty("age", age); - Assert.Same(age, schema.Properties["age"]); + JSONSchema jsonSchema = new JSONSchema(); + Assert.Throws(() => { jsonSchema.AddProperty(null, null); }); + } + + [Fact] + public void AddPropertyWithEmptyPropertyName() + { + JSONSchema jsonSchema = new JSONSchema(); + Assert.Throws(() => { jsonSchema.AddProperty("", null); }); + } + + [Fact] + public void AddPropertyWithWhitespacePropertyName() + { + JSONSchema jsonSchema = new JSONSchema(); + Assert.Throws(() => { jsonSchema.AddProperty(" ", null); }); + } + + [Fact] + public void AddRequiredWithOneValueWhenPropertyDoesntExist() + { + JSONSchema jsonSchema = new JSONSchema(); + Assert.Throws(() => { jsonSchema.AddRequired("a"); }); + Assert.Null(jsonSchema.Properties); + Assert.Null(jsonSchema.Required); + } + + [Fact] + public void AddRequiredWithTwoValuesWhenSecondPropertyDoesntExist() + { + JSONSchema jsonSchema = new JSONSchema(); + jsonSchema.AddProperty("a", new JSONSchema()); + Assert.Throws(() => { jsonSchema.AddRequired("a", "b"); }); + } + + [Fact] + public void AddRequiredWithThreeValuesWhenAllPropertiesExist() + { + JSONSchema jsonSchema = new JSONSchema(); + jsonSchema.AddProperty("a", new JSONSchema()); + jsonSchema.AddProperty("b", new JSONSchema()); + jsonSchema.AddProperty("c", new JSONSchema()); + + jsonSchema.AddRequired("a", "b", "c"); + + Assert.Equal(new List() { "a", "b", "c" }, jsonSchema.Required); } } } diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaParserTests.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaParserTests.cs new file mode 100644 index 0000000000000..970431b634b43 --- /dev/null +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaParserTests.cs @@ -0,0 +1,190 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Rest.Generator.AzureResourceSchema; +using Microsoft.Rest.Generator.ClientModel; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace AutoRest.Generator.AzureResourceSchema.Tests +{ + public class ResourceSchemaParserTests + { + [Fact] + public void ParseWithNullServiceClient() + { + Assert.Throws(() => { ResourceSchemaParser.Parse(null); }); + } + + [Fact] + public void ParseWithEmptyServiceClient() + { + ServiceClient serviceClient = new ServiceClient(); + ResourceSchemaModel schema = ResourceSchemaParser.Parse(serviceClient); + Assert.NotNull(schema); + Assert.Null(schema.Id); + Assert.Equal("http://json-schema.org/draft-04/schema#", schema.Schema); + Assert.Null(schema.Title); + Assert.Null(schema.Description); + Assert.Null(schema.ResourceDefinitions); + Assert.Null(schema.Definitions); + } + + [Fact] + public void ParseWithServiceClientWithCreateResourceMethod() + { + ServiceClient serviceClient = new ServiceClient(); + + Parameter body = new Parameter() + { + Location = ParameterLocation.Body, + Type = new CompositeType(), + }; + + CompositeType responseBody = new CompositeType(); + responseBody.Extensions.Add("x-ms-azure-resource", true); + + const string url = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Mock.Provider/mockResourceNames"; + + Method method = CreateMethod(body: body, responseBody: responseBody, url: url); + + serviceClient.Methods.Add(method); + + ResourceSchemaModel schema = ResourceSchemaParser.Parse(serviceClient); + Assert.NotNull(schema); + Assert.Null(schema.Id); + Assert.Equal("http://json-schema.org/draft-04/schema#", schema.Schema); + Assert.Equal("Mock.Provider", schema.Title); + Assert.Equal("Mock Provider Resource Types", schema.Description); + Assert.Equal(1, schema.ResourceDefinitions.Count); + Assert.Equal("mockResourceNames", schema.ResourceDefinitions.Keys.Single()); + Assert.Equal( + new JSONSchema() + { + JSONType = "object", + Properties = new Dictionary() + { + { + "type", + new JSONSchema() + { + JSONType = "string", + Enum = new string[] { "Mock.Provider/mockResourceNames" }, + } + } + }, + Required = new string[] { "type" }, + Description = "Mock.Provider/mockResourceNames", + }, + schema.ResourceDefinitions["mockResourceNames"]); + Assert.Null(schema.Definitions); + } + + [Fact] + public void IsCreateResourceMethodWithNullMethod() + { + Assert.Throws(() => { ResourceSchemaParser.IsCreateResourceMethod(null); }); + } + + [Fact] + public void IsCreateResourceMethodWithGetHttpMethod() + { + Assert.False(ResourceSchemaParser.IsCreateResourceMethod(CreateMethod(HttpMethod.Get))); + } + + [Fact] + public void IsCreateResourceMethodWithNoBody() + { + Assert.False(ResourceSchemaParser.IsCreateResourceMethod(CreateMethod())); + } + + [Fact] + public void IsCreateResourceMethodNoReturnType() + { + Assert.False(ResourceSchemaParser.IsCreateResourceMethod(CreateMethod(body: new Parameter() + { + Location = ParameterLocation.Body + }))); + } + + [Fact] + public void IsCreateResourceMethodWithNonResourceReturnType() + { + Assert.False(ResourceSchemaParser.IsCreateResourceMethod(CreateMethod( + body: new Parameter() + { + Location = ParameterLocation.Body + }, + responseBody: new PrimaryType(KnownPrimaryType.Int)))); + } + + [Fact] + public void IsCreateResourceMethodWithCompositeNonResourceReturnType() + { + Assert.False(ResourceSchemaParser.IsCreateResourceMethod(CreateMethod( + body: new Parameter() + { + Location = ParameterLocation.Body + }, + responseBody: new CompositeType()))); + } + + [Fact] + public void IsCreateResourceMethodWithResourceReturnTypeButNoUrl() + { + CompositeType responseBody = new CompositeType(); + responseBody.Extensions.Add("x-ms-azure-resource", true); + + Assert.False(ResourceSchemaParser.IsCreateResourceMethod(CreateMethod( + body: new Parameter() + { + Location = ParameterLocation.Body + }, + responseBody: responseBody))); + } + + + + [Fact] + public void IsCreateResourceMethodWithResourceReturnTypeAndUrl() + { + CompositeType responseBody = new CompositeType(); + responseBody.Extensions.Add("x-ms-azure-resource", true); + + Assert.True(ResourceSchemaParser.IsCreateResourceMethod(CreateMethod( + body: new Parameter() + { + Location = ParameterLocation.Body + }, + responseBody: responseBody, + url: "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Mock.Provider/mockResourceNames"))); + } + + [Fact] + public void GetResourceTypeWithOneLevelOfResources() + { + Assert.Equal("Microsoft.Cdn/profiles", ResourceSchemaParser.GetResourceType("Microsoft.Cdn", "profiles/{profileName}")); + } + + [Fact] + public void GetResourceTypeWithMultipleLevelsOfResources() + { + Assert.Equal("Microsoft.Cdn/profiles/endpoints/customDomains", ResourceSchemaParser.GetResourceType("Microsoft.Cdn", "profiles/{profileName}/endpoints/{endpointName}/customDomains/{customDomainName}")); + } + + private static Method CreateMethod(HttpMethod httpMethod = HttpMethod.Put, Parameter body = null, IType responseBody = null, string url = null) + { + Method method = new Method() + { + HttpMethod = httpMethod, + ReturnType = new Response(responseBody, null), + Url = url, + }; + method.Parameters.Add(body); + + return method; + } + } +} diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaWriterTests.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaWriterTests.cs new file mode 100644 index 0000000000000..7c21f04057ec7 --- /dev/null +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema.Tests/ResourceSchemaWriterTests.cs @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Rest.Generator.AzureResourceSchema; +using Newtonsoft.Json; +using System; +using System.IO; +using Xunit; + +namespace AutoRest.Generator.AzureResourceSchema.Tests +{ + public class ResourceSchemaWriterTests + { + [Fact] + public void WriteWithNullJsonTextWriter() + { + JsonTextWriter writer = null; + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + Assert.Throws(() => { ResourceSchemaWriter.Write(writer, resourceSchema); }); + } + + [Fact] + public void WriteWithJsonTextWriterAndNullResourceSchema() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + ResourceSchemaModel resourceSchema = null; + Assert.Throws(() => { ResourceSchemaWriter.Write(writer, resourceSchema); }); + } + + [Fact] + public void WriteWithEmptyResourceSchema() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + ResourceSchemaWriter.Write(writer, resourceSchema); + Assert.Equal("{}", stringWriter.ToString()); + } + + [Fact] + public void WriteWithId() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + resourceSchema.Id = "MockId"; + + ResourceSchemaWriter.Write(writer, resourceSchema); + Assert.Equal("{'id':'MockId'}", stringWriter.ToString()); + } + + [Fact] + public void WriteWithSchema() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + resourceSchema.Id = "MockId"; + resourceSchema.Schema = "MockSchema"; + + ResourceSchemaWriter.Write(writer, resourceSchema); + Assert.Equal("{'id':'MockId','$schema':'MockSchema'}", stringWriter.ToString()); + } + + [Fact] + public void WriteWithTitle() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + resourceSchema.Schema = "MockSchema"; + resourceSchema.Title = "MockTitle"; + + ResourceSchemaWriter.Write(writer, resourceSchema); + Assert.Equal("{'$schema':'MockSchema','title':'MockTitle'}", stringWriter.ToString()); + } + + [Fact] + public void WriteWithDescription() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + resourceSchema.Title = "MockTitle"; + resourceSchema.Description = "MockDescription"; + + ResourceSchemaWriter.Write(writer, resourceSchema); + Assert.Equal("{'title':'MockTitle','description':'MockDescription'}", stringWriter.ToString()); + } + + [Fact] + public void WriteWithOneResourceDefinition() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + resourceSchema.Description = "MockDescription"; + resourceSchema.AddResourceDefinition("mockResource", new JSONSchema()); + + ResourceSchemaWriter.Write(writer, resourceSchema); + Assert.Equal("{'description':'MockDescription','resourceDefinitions':{'mockResource':{}}}", stringWriter.ToString()); + } + + [Fact] + public void WriteWithOneDefinition() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + ResourceSchemaModel resourceSchema = new ResourceSchemaModel(); + resourceSchema.AddResourceDefinition("mockResource", new JSONSchema()); + resourceSchema.AddDefinition("mockDefinition", new JSONSchema()); + + ResourceSchemaWriter.Write(writer, resourceSchema); + Assert.Equal("{'resourceDefinitions':{'mockResource':{}},'definitions':{'mockDefinition':{}}}", stringWriter.ToString()); + } + + + + + [Fact] + public void WriteDefinitionWithEmptyDefinition() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string definitionName = "mockDefinition"; + JSONSchema definition = new JSONSchema(); + + ResourceSchemaWriter.WriteDefinition(writer, definitionName, definition); + Assert.Equal("'mockDefinition':{}", stringWriter.ToString()); + } + + [Fact] + public void WriteDefinitionWithType() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string definitionName = "mockDefinition"; + JSONSchema definition = new JSONSchema(); + definition.JSONType = "MockType"; + + ResourceSchemaWriter.WriteDefinition(writer, definitionName, definition); + Assert.Equal("'mockDefinition':{'type':'MockType'}", stringWriter.ToString()); + } + + [Fact] + public void WriteDefinitionWithTypeAndEnum() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string definitionName = "mockDefinition"; + JSONSchema definition = new JSONSchema(); + definition.JSONType = "MockType"; + definition.Enum = new string[] { "MockEnum1", "MockEnum2" }; + + ResourceSchemaWriter.WriteDefinition(writer, definitionName, definition); + Assert.Equal("'mockDefinition':{'type':'MockType','enum':['MockEnum1','MockEnum2']}", stringWriter.ToString()); + } + + [Fact] + public void WriteDefinitionWithEnumAndUnrequiredProperty() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string definitionName = "mockDefinition"; + JSONSchema definition = new JSONSchema(); + definition.Enum = new string[] { "MockEnum1", "MockEnum2" }; + definition.AddProperty("mockPropertyName", new JSONSchema()); + + ResourceSchemaWriter.WriteDefinition(writer, definitionName, definition); + Assert.Equal("'mockDefinition':{'enum':['MockEnum1','MockEnum2'],'properties':{'mockPropertyName':{}}}", stringWriter.ToString()); + } + + [Fact] + public void WriteDefinitionWithEnumAndRequiredProperty() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string definitionName = "mockDefinition"; + JSONSchema definition = new JSONSchema(); + definition.Enum = new string[] { "MockEnum1", "MockEnum2" }; + definition.AddProperty("mockPropertyName", new JSONSchema(), true); + + ResourceSchemaWriter.WriteDefinition(writer, definitionName, definition); + Assert.Equal("'mockDefinition':{'enum':['MockEnum1','MockEnum2'],'properties':{'mockPropertyName':{}},'required':['mockPropertyName']}", stringWriter.ToString()); + } + + [Fact] + public void WriteDefinitionWithRequiredPropertyAndDescription() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string definitionName = "mockDefinition"; + JSONSchema definition = new JSONSchema(); + definition.AddProperty("mockPropertyName", new JSONSchema(), true); + definition.Description = "MockDescription"; + + ResourceSchemaWriter.WriteDefinition(writer, definitionName, definition); + Assert.Equal("'mockDefinition':{'properties':{'mockPropertyName':{}},'required':['mockPropertyName'],'description':'MockDescription'}", stringWriter.ToString()); + } + + + + + + + + [Fact] + public void WritePropertyWithNullWriter() + { + JsonTextWriter writer = null; + const string propertyName = "mockPropertyName"; + const string propertyValue = "mockPropertyValue"; + Assert.Throws(() => { ResourceSchemaWriter.WriteProperty(writer, propertyName, propertyValue); }); + } + + [Fact] + public void WritePropertyWithNullPropertyName() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string propertyName = null; + const string propertyValue = "mockPropertyValue"; + + Assert.Throws(() => { ResourceSchemaWriter.WriteProperty(writer, propertyName, propertyValue); }); + } + + [Fact] + public void WritePropertyWithNullPropertyValue() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string propertyName = "mockPropertyName"; + const string propertyValue = null; + + ResourceSchemaWriter.WriteProperty(writer, propertyName, propertyValue); + + Assert.Equal("", stringWriter.ToString()); + } + + [Fact] + public void WritePropertyWithEmptyPropertyValue() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string propertyName = "mockPropertyName"; + const string propertyValue = ""; + + ResourceSchemaWriter.WriteProperty(writer, propertyName, propertyValue); + + Assert.Equal("", stringWriter.ToString()); + } + + [Fact] + public void WritePropertyWithWhitespacePropertyValue() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string propertyName = "mockPropertyName"; + const string propertyValue = " "; + + ResourceSchemaWriter.WriteProperty(writer, propertyName, propertyValue); + + Assert.Equal("", stringWriter.ToString()); + } + + [Fact] + public void WritePropertyWithNonWhitespacePropertyValue() + { + StringWriter stringWriter = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(stringWriter); + writer.QuoteChar = '\''; + + const string propertyName = "mockPropertyName"; + const string propertyValue = "mockPropertyValue"; + + ResourceSchemaWriter.WriteProperty(writer, propertyName, propertyValue); + + Assert.Equal("'mockPropertyName':'mockPropertyValue'", stringWriter.ToString()); + } + } +} diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AutoRest.Generator.AzureResourceSchema.csproj b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AutoRest.Generator.AzureResourceSchema.csproj index 6c81c828ff292..6c112c4fa29ab 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AutoRest.Generator.AzureResourceSchema.csproj +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AutoRest.Generator.AzureResourceSchema.csproj @@ -31,6 +31,9 @@ + + + diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AzureResourceSchemaCodeGenerator.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AzureResourceSchemaCodeGenerator.cs index f4baa7d2dc69d..4c76bc25df7ba 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AzureResourceSchemaCodeGenerator.cs +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/AzureResourceSchemaCodeGenerator.cs @@ -2,12 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.Rest.Generator.ClientModel; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.Rest.Generator.AzureResourceSchema @@ -54,218 +49,12 @@ public override void NormalizeClientModel(ServiceClient serviceClient) public override async Task Generate(ServiceClient serviceClient) { - ResourceSchema schema = ResourceSchema.Parse(serviceClient); + ResourceSchemaModel resourceSchema = ResourceSchemaParser.Parse(serviceClient); StringWriter stringWriter = new StringWriter(); - using (JsonTextWriter writer = new JsonTextWriter(stringWriter)) - { - writer.Formatting = Formatting.Indented; - writer.Indentation = 2; - writer.IndentChar = ' '; - writer.QuoteChar = '\"'; - - WriteResourceSchema(writer, schema); - } + ResourceSchemaWriter.Write(stringWriter, resourceSchema); await Write(stringWriter.ToString(), SchemaPath); } - - private static void WriteResourceSchema(JsonTextWriter writer, ResourceSchema schema) - { - WriteObject(writer, () => - { - WriteJsonStringProperty(writer, "id", schema.Id); - WriteJsonStringProperty(writer, "$schema", schema.Schema); - WriteJsonStringProperty(writer, "title", schema.Title); - WriteJsonStringProperty(writer, "description", schema.Description); - WriteJsonObjectProperty(writer, "resourceDefinitions", () => - { - foreach (Resource resource in schema.Resources) - { - WriteResource(writer, resource, schema.Definitions.Keys); - } - }); - - if (schema.Definitions.Count() > 0) - { - WriteJsonObjectProperty(writer, "definitions", () => - { - foreach (Definition definition in schema.Definitions.Values.OrderBy(definition => definition.Name)) - { - WriteJsonObjectProperty(writer, definition.Name, null, definition, schema.Definitions.Keys); - } - }); - } - }); - } - - private static void WriteJsonStringProperty(JsonTextWriter writer, string propertyName, string propertyValue) - { - writer.WritePropertyName(propertyName); - writer.WriteValue(propertyValue); - } - - private static void WriteJsonObjectProperty(JsonTextWriter writer, string propertyName, string propertyDescription, Definition propertyDefinition, IEnumerable schemaDefinitionNames) - { - WriteJsonObjectProperty(writer, propertyName, () => - { - Debug.Assert(!String.IsNullOrWhiteSpace(propertyDefinition.DefinitionType)); - WriteJsonStringProperty(writer, "type", propertyDefinition.DefinitionType); - - if (propertyDefinition.ArrayElement != null) - { - WriteJsonObjectProperty(writer, "items", null, propertyDefinition.ArrayElement, schemaDefinitionNames); - } - - if (propertyDefinition.AllowedValues != null) - { - WriteArrayProperty(writer, "enum", propertyDefinition.AllowedValues); - } - - if (propertyDefinition.AdditionalProperties != null) - { - WriteJsonObjectProperty(writer, "additionalProperties", null, propertyDefinition.AdditionalProperties, schemaDefinitionNames); - } - - if (propertyDefinition.Properties != null) - { - WriteJsonObjectProperty(writer, "properties", () => - { - foreach (SchemaProperty definitionProperty in propertyDefinition.Properties) - { - WriteResourceProperty(writer, definitionProperty, schemaDefinitionNames); - } - }); - - if (propertyDefinition.RequiredPropertyNames != null) - { - WriteArrayProperty(writer, "required", propertyDefinition.RequiredPropertyNames); - } - } - - if (propertyDescription != null) - { - WriteJsonStringProperty(writer, "description", propertyDescription); - } - else if (propertyDefinition.Description != null) - { - WriteJsonStringProperty(writer, "description", propertyDefinition.Description); - } - }); - } - - private static void WriteResource(JsonTextWriter writer, Resource resource, IEnumerable schemaDefinitionNames) - { - WriteJsonObjectProperty(writer, resource.Name, () => - { - WriteJsonStringProperty(writer, "type", "object"); - - // Root level properties of the resource - WriteJsonObjectProperty(writer, "properties", () => - { - WriteJsonObjectProperty(writer, "type", () => - { - WriteJsonStringProperty(writer, "type", "string"); - WriteArrayProperty(writer, "enum", new string[] { resource.ResourceType }); - }); - - WriteJsonObjectProperty(writer, "apiVersion", () => - { - WriteJsonStringProperty(writer, "type", "string"); - WriteArrayProperty(writer, "enum", resource.ApiVersions); - }); - - if (resource.Properties != null) - { - foreach (SchemaProperty property in resource.Properties) - { - WriteResourceProperty(writer, property, schemaDefinitionNames); - } - } - }); - WriteArrayProperty(writer, "required", resource.RequiredPropertyNames); - WriteJsonStringProperty(writer, "description", resource.Description); - }); - } - - private static void WriteResourceProperty(JsonTextWriter writer, SchemaProperty property, IEnumerable schemaDefinitionNames) - { - Definition definition = property.Definition; - //if (!property.ShouldFlatten) - { - if (schemaDefinitionNames.Contains(definition.Name)) - { - WriteJsonObjectProperty(writer, property.Name, () => - { - WriteJsonStringProperty(writer, "$ref", "#/definitions/" + definition.Name); - if (property.Description != null) - { - WriteJsonStringProperty(writer, "description", property.Description); - } - }); - } - else - { - WriteJsonObjectProperty(writer, property.Name, property.Description, definition, schemaDefinitionNames); - } - } - //else - //{ - // Debug.Assert(property.Name == "properties"); - // WriteObjectProperty(writer, "properties", () => - // { - // foreach (SchemaProperty definitionProperty in definition.Properties) - // { - // WriteResourceProperty(writer, definitionProperty, schemaDefinitionNames); - // } - // }); - //} - } - - //private static void WriteObjectOrExpression(JsonTextWriter writer, Action writeObjectContents) - //{ - // writer.WritePropertyName("oneOf"); - // writer.WriteStartArray(); - - // WriteObject(writer, writeObjectContents); - - // WriteObject(writer, () => - // { - // WriteStringProperty(writer, "$ref", "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"); - // }); - - // writer.WriteEndArray(); - //} - - private static void WriteArrayProperty(JsonTextWriter writer, string propertyName, IEnumerable writeArrayContents) - { - writer.WritePropertyName(propertyName); - writer.WriteStartArray(); - - if (writeArrayContents != null) - { - foreach (string value in writeArrayContents) - { - writer.WriteValue(value); - } - } - - writer.WriteEndArray(); - } - - private static void WriteJsonObjectProperty(JsonTextWriter writer, string propertyName, Action writeObjectContents) - { - writer.WritePropertyName(propertyName); - WriteObject(writer, writeObjectContents); - } - - private static void WriteObject(JsonTextWriter writer, Action writeObjectContents) - { - writer.WriteStartObject(); - - writeObjectContents.Invoke(); - - writer.WriteEndObject(); - } } } diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/JSONSchema.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/JSONSchema.cs index 0858f9df5801c..9a51dcc398c44 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/JSONSchema.cs +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/JSONSchema.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Linq; namespace Microsoft.Rest.Generator.AzureResourceSchema { @@ -22,6 +23,18 @@ public class JSONSchema /// public string Title { get; set; } + /// + /// A reference to the location in the parent schema where this schema's definition can be + /// found. + /// + public string Ref { get; set; } + + /// + /// The JSONSchema that will be applied to the elements of this schema, assuming this + /// schema is an array schema type. + /// + public JSONSchema Items { get; set; } + /// /// The description metadata that describes this schema. /// @@ -30,7 +43,19 @@ public class JSONSchema /// /// The type metadata of this schema that describes what type matching JSON values must be. /// - public string Type { get; set; } + public string JSONType { get; set; } + + /// + /// The schema that matches additional properties that have not been specified in the + /// Properties dictionary. + /// + public JSONSchema AdditionalProperties { get; set; } + + /// + /// An enumeration of values that will match this JSON schema. Any value not in this + /// enumeration will not match this schema. + /// + public IList Enum { get; set; } /// /// The schemas that describe the properties of a matching JSON value. @@ -40,24 +65,174 @@ public class JSONSchema /// /// The names of the properties that are required for a matching JSON value. /// - public IEnumerable Required { get; set; } + public IList Required { get; set; } - /// - /// Add the provided property to this JSON schema. - /// - /// The name of the property to add. - /// The schema of the property to add. - public void AddProperty(string propertyName, JSONSchema propertySchema) + public void AddEnum(string enumValue) { - Debug.Assert(!string.IsNullOrWhiteSpace(propertyName)); - Debug.Assert(Properties == null || !Properties.ContainsKey(propertyName)); - Debug.Assert(propertySchema != null); + if (string.IsNullOrWhiteSpace(enumValue)) + { + throw new ArgumentException("enumValue cannot be null or whitespace", "enumValue"); + } + + if (Enum == null) + { + Enum = new List(); + } + + if (Enum.Contains(enumValue)) + { + throw new ArgumentException("enumValue (" + enumValue + ") already exists in the list of allowed values."); + } + Enum.Add(enumValue); + } + + public void AddProperty(string propertyName, JSONSchema propertyDefinition, bool isRequired = false) + { + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new ArgumentException("propertyName cannot be null or whitespace", "propertyName"); + } + if (propertyDefinition == null) + { + throw new ArgumentNullException("propertyDefinition"); + } + if (Properties == null) { Properties = new Dictionary(); } - Properties.Add(propertyName, propertySchema); + + if (Properties.ContainsKey(propertyName)) + { + throw new ArgumentException("A property with the name \"" + propertyName + "\" already exists in this JSONSchema", "propertyName"); + } + + Properties[propertyName] = propertyDefinition; + + if (isRequired) + { + AddRequired(propertyName); + } + } + + /// + /// Add the provided required property names to this JSON schema's list of required property names. + /// + /// + /// + public void AddRequired(string requiredPropertyName, params string[] extraRequiredPropertyNames) + { + if (Properties == null || !Properties.ContainsKey(requiredPropertyName)) + { + throw new ArgumentException("No property exists with the provided requiredPropertyName (" + requiredPropertyName + ")", "requiredPropertyName"); + } + + if (Required == null) + { + Required = new List(); + } + + Required.Add(requiredPropertyName); + + if (extraRequiredPropertyNames != null) + { + foreach (string extraRequiredPropertyName in extraRequiredPropertyNames) + { + if (Properties == null || !Properties.ContainsKey(extraRequiredPropertyName)) + { + throw new ArgumentException("No property exists with the provided extraRequiredPropertyName (" + extraRequiredPropertyName + ")", "extraRequiredPropertyNames"); + } + Required.Add(extraRequiredPropertyName); + } + } + } + + public override bool Equals(object obj) + { + bool result = false; + + JSONSchema rhs = obj as JSONSchema; + if (rhs != null) + { + result = Equals(Schema, rhs.Schema) && + Equals(Title, rhs.Title) && + Equals(Description, rhs.Description) && + Equals(JSONType, rhs.JSONType) && + Equals(Enum, rhs.Enum) && + Equals(Properties, rhs.Properties) && + Equals(Required, rhs.Required); + } + + return result; + } + + public override int GetHashCode() + { + return GetHashCode(GetType()) ^ + GetHashCode(Schema) ^ + GetHashCode(Title) ^ + GetHashCode(Description) ^ + GetHashCode(JSONType) ^ + GetHashCode(Enum) ^ + GetHashCode(Properties) ^ + GetHashCode(Required); + } + + private static int GetHashCode(object value) + { + return value == null ? 0 : value.GetHashCode(); + } + + private static bool Equals(IEnumerable lhs, IEnumerable rhs) + { + bool result = lhs == rhs; + + if (!result && + lhs != null && + rhs != null && + lhs.Count() == rhs.Count()) + { + result = true; + + IEnumerator lhsEnumerator = lhs.GetEnumerator(); + IEnumerator rhsEnumerator = rhs.GetEnumerator(); + while (lhsEnumerator.MoveNext() && rhsEnumerator.MoveNext()) + { + if (!Equals(lhsEnumerator.Current, rhsEnumerator.Current)) + { + result = false; + break; + } + } + } + + return result; + } + + private static bool Equals(IDictionary lhs, IDictionary rhs) + { + bool result = lhs == rhs; + + if (!result && + lhs != null && + rhs != null && + lhs.Count == rhs.Count) + { + result = true; + + foreach (string key in lhs.Keys) + { + if (rhs.ContainsKey(key) == false || + !Equals(lhs[key], rhs[key])) + { + result = false; + break; + } + } + } + + return result; } } } diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/Properties/AssemblyInfo.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/Properties/AssemblyInfo.cs index 37bc04b37366f..da321c159d8f4 100644 --- a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/Properties/AssemblyInfo.cs +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -34,3 +35,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: CLSCompliant(true)] [assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: InternalsVisibleTo("AutoRest.Generator.AzureResourceSchema.Tests")] diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaModel.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaModel.cs new file mode 100644 index 0000000000000..991480b1b851b --- /dev/null +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaModel.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Rest.Generator.AzureResourceSchema +{ + /// + /// An object representing an Azure Resource Schema. It is important to note that an Azure + /// resource schema is actually not a valid JSON schema by itself. It is part of the + /// deploymentTemplate.json schema. + /// + public class ResourceSchemaModel + { + /// + /// The id metadata that uniquely identifies this schema. Usually this will be the URL to + /// the permanent location of this schema in schema.management.azure.com/schemas/. + /// + public string Id { get; set; } + + /// + /// The JSON schema metadata url that points to the schema that can be used to validate + /// this schema. + /// + public string Schema { get; set; } + + /// + /// The title metadata for this schema. + /// + public string Title { get; set; } + + /// + /// The description metadata that describes this schema. + /// + public string Description { get; set; } + + /// + /// The named JSON schemas that define the resources of this resource schema. + /// + public IDictionary ResourceDefinitions { get; set; } + + /// + /// The named reusable JSON schemas that the resource definitions reference. These + /// definitions can also reference each other or themselves. + /// + public IDictionary Definitions { get; set; } + + /// + /// Add the provided resource definition JSON schema to this resourceh schema. + /// + /// The name of the resource definition. + /// The JSON schema that describes the resource. + public void AddResourceDefinition(string resourceName, JSONSchema resourceDefinition) + { + if (string.IsNullOrWhiteSpace(resourceName)) + { + throw new ArgumentException("resourceName cannot be null or whitespace", "resourceName"); + } + if (resourceDefinition == null) + { + throw new ArgumentNullException("resourceDefinition"); + } + + if (ResourceDefinitions == null) + { + ResourceDefinitions = new Dictionary(); + } + + if (ResourceDefinitions.ContainsKey(resourceName)) + { + throw new ArgumentException("A resource definition for \"" + resourceName + "\" already exists in this resource schema.", "resourceName"); + } + + ResourceDefinitions.Add(resourceName, resourceDefinition); + } + + /// + /// Add the provided definition JSON schema to this resourceh schema. + /// + /// The name of the resource definition. + /// The JSON schema that describes the resource. + public void AddDefinition(string definitionName, JSONSchema definition) + { + if (string.IsNullOrWhiteSpace(definitionName)) + { + throw new ArgumentException("definitionName cannot be null or whitespace", "definitionName"); + } + if (definition == null) + { + throw new ArgumentNullException("definition"); + } + + if (Definitions == null) + { + Definitions = new Dictionary(); + } + + if (Definitions.ContainsKey(definitionName)) + { + throw new ArgumentException("A definition for \"" + definitionName + "\" already exists in this resource schema.", "definitionName"); + } + + Definitions.Add(definitionName, definition); + } + } +} diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaParser.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaParser.cs new file mode 100644 index 0000000000000..67aa1032078a5 --- /dev/null +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaParser.cs @@ -0,0 +1,338 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Rest.Generator.ClientModel; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.Rest.Generator.AzureResourceSchema +{ + /// + /// The ResourceSchemaParser class is responsible for converting a ServiceClient object into a + /// ResourceSchemaModel. + /// + public static class ResourceSchemaParser + { + private const string resourceMethodPrefix = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/"; + + /// + /// Parse a ResourceSchemaModel from the provided ServiceClient. + /// + /// + /// + public static ResourceSchemaModel Parse(ServiceClient serviceClient) + { + if (serviceClient == null) + { + throw new ArgumentNullException("serviceClient"); + } + + ResourceSchemaModel result = new ResourceSchemaModel(); + result.Schema = "http://json-schema.org/draft-04/schema#"; + + List createResourceMethods = new List(); + foreach (Method method in serviceClient.Methods) + { + if (IsCreateResourceMethod(method)) + { + createResourceMethods.Add(method); + } + } + + string apiVersion = serviceClient.ApiVersion; + string resourceProvider = null; + + foreach (Method createResourceMethod in createResourceMethods) + { + JSONSchema resourceDefinition = new JSONSchema(); + resourceDefinition.JSONType = "object"; + + string afterPrefix = createResourceMethod.Url.Substring(resourceMethodPrefix.Length); + int forwardSlashIndexAfterProvider = afterPrefix.IndexOf('/'); + string resourceMethodProvider = afterPrefix.Substring(0, forwardSlashIndexAfterProvider); + if (resourceProvider == null) + { + resourceProvider = resourceMethodProvider; + + if (apiVersion != null) + { + result.Id = string.Format("http://schema.management.azure.com/schemas/{0}/{1}.json#", apiVersion, resourceProvider); + } + + result.Title = resourceProvider; + result.Description = resourceProvider.Replace('.', ' ') + " Resource Types"; + } + else + { + Debug.Assert(resourceProvider == resourceMethodProvider); + } + + string methodUrlPathAfterProvider = afterPrefix.Substring(forwardSlashIndexAfterProvider + 1); + string resourceType = GetResourceType(resourceProvider, methodUrlPathAfterProvider); + + string resourceName = resourceType.Split('/').Last(); + + resourceDefinition.AddProperty("type", new JSONSchema() + { + JSONType = "string", + Enum = new string[] { resourceType } + }); + + if (!string.IsNullOrWhiteSpace(apiVersion)) + { + resourceDefinition.AddProperty("apiVersion", new JSONSchema() + { + JSONType = "string", + Enum = new string[] { apiVersion } + }); + } + + IDictionary definitionMap = new Dictionary(); + + CompositeType body = createResourceMethod.Body.Type as CompositeType; + Debug.Assert(body != null, "The create resource method's body must be a CompositeType and cannot be null."); + if (body != null) + { + foreach (Property property in body.Properties) + { + JSONSchema propertyDefinition = ParseProperty(property, definitionMap); + if (propertyDefinition != null) + { + resourceDefinition.AddProperty(property.Name, propertyDefinition, property.IsRequired); + } + } + } + + foreach (string definitionName in definitionMap.Keys) + { + result.AddDefinition(definitionName, definitionMap[definitionName]); + } + + resourceDefinition.Description = resourceType; + + foreach (string standardPropertyName in new string[] { "properties", "apiVersion", "type" }) + { + if (resourceDefinition.Properties.ContainsKey(standardPropertyName)) + { + if (resourceDefinition.Required == null) + { + resourceDefinition.AddRequired(standardPropertyName); + } + else + { + resourceDefinition.Required.Insert(0, standardPropertyName); + } + } + } + + result.AddResourceDefinition(resourceName, resourceDefinition); + } + + return result; + } + + private static JSONSchema ParseProperty(Property property, IDictionary definitionMap) + { + JSONSchema propertyDefinition = null; + + if (!property.IsReadOnly) + { + propertyDefinition = new JSONSchema(); + + IType propertyType = property.Type; + if (propertyType is CompositeType) + { + propertyDefinition = ParseCompositeType(propertyType as CompositeType, definitionMap); + propertyDefinition.Description = property.Documentation; + } + else if (propertyType is DictionaryType) + { + DictionaryType dictionaryType = propertyType as DictionaryType; + + string definitionName = dictionaryType.Name; + propertyDefinition.JSONType = "object"; + propertyDefinition.Description = property.Documentation; + + if (dictionaryType.ValueType is PrimaryType) + { + propertyDefinition.AdditionalProperties = ParsePrimaryType(dictionaryType.ValueType as PrimaryType); + } + else + { + Debug.Assert(false, "Unrecognized DictionaryType.ValueType: " + dictionaryType.ValueType.GetType()); + } + } + else if (propertyType is EnumType) + { + EnumType enumType = propertyType as EnumType; + + string definitionName = enumType.Name; + propertyDefinition.JSONType = "string"; + foreach (EnumValue enumValue in enumType.Values) + { + propertyDefinition.AddEnum(enumValue.Name); + } + + propertyDefinition.Description = property.Documentation; + } + else if (propertyType is PrimaryType) + { + propertyDefinition = ParsePrimaryType(propertyType as PrimaryType); + propertyDefinition.Description = property.Documentation; + + if (property.DefaultValue != null) + { + propertyDefinition.AddEnum(property.DefaultValue); + } + } + else if (propertyType is SequenceType) + { + SequenceType sequenceType = propertyType as SequenceType; + + string definitionName = sequenceType.Name; + propertyDefinition.JSONType = "array"; + + if (sequenceType.ElementType is CompositeType) + { + propertyDefinition.Items = ParseCompositeType(sequenceType.ElementType as CompositeType, definitionMap); + } + else if (sequenceType.ElementType is PrimaryType) + { + propertyDefinition.Items = ParsePrimaryType(sequenceType.ElementType as PrimaryType); + } + else + { + Debug.Assert(false, "Unrecognized SequenceType.ElementType: " + sequenceType.ElementType.GetType()); + } + } + else + { + Debug.Assert(false, "Unrecognized property type: " + propertyType.GetType()); + } + } + + return propertyDefinition; + } + + private static JSONSchema ParseCompositeType(CompositeType compositeType, IDictionary definitionMap) + { + JSONSchema result = new JSONSchema(); + + string definitionName = compositeType.Name; + + if (!definitionMap.ContainsKey(definitionName)) + { + JSONSchema definition = new JSONSchema(); + definition.JSONType = "object"; + + Debug.Assert(compositeType.Properties.Count == compositeType.ComposedProperties.Count()); + foreach (Property subProperty in compositeType.ComposedProperties) + { + JSONSchema subPropertyDefinition = ParseProperty(subProperty, definitionMap); + if (subPropertyDefinition != null) + { + definition.AddProperty(subProperty.Name, subPropertyDefinition, subProperty.IsRequired); + } + } + + definition.Description = compositeType.Documentation; + + definitionMap.Add(definitionName, definition); + } + + result.Ref = "#/definitions/" + definitionName; + + return result; + } + + private static JSONSchema ParsePrimaryType(PrimaryType primaryType) + { + JSONSchema result = new JSONSchema(); + + switch (primaryType.Type) + { + case KnownPrimaryType.Boolean: + result.JSONType = "boolean"; + break; + + case KnownPrimaryType.Int: + result.JSONType = "integer"; + break; + + case KnownPrimaryType.String: + result.JSONType = "string"; + break; + + default: + Debug.Assert(false, "Unrecognized known property type: " + primaryType.Type); + break; + } + + return result; + } + + /// + /// Determine whether the provided method object represents an Azure REST API that would + /// create an Azure resource. + /// + /// + /// + internal static bool IsCreateResourceMethod(Method method) + { + if (method == null) + { + throw new ArgumentNullException("method"); + } + + bool result = false; + + if (!string.IsNullOrWhiteSpace(method.Url) && + method.Url.StartsWith(resourceMethodPrefix) && + method.HttpMethod == HttpMethod.Put && + method.ReturnType.Body != null) + { + CompositeType body = method.ReturnType.Body as CompositeType; + if (body != null) + { + Dictionary bodyComposedExtensions = body.ComposedExtensions; + const string azureResource = "x-ms-azure-resource"; + result = bodyComposedExtensions.ContainsKey(azureResource) ? (bool)bodyComposedExtensions[azureResource] : false; + } + } + + return result; + } + + /// + /// Get the resource type from the provided resourceProvider and the portion of the method + /// URL path that comes after the resourceProvider section. + /// + /// + /// + /// + internal static string GetResourceType(string resourceProvider, string methodUrlPathAfterProvider) + { + if (string.IsNullOrWhiteSpace(resourceProvider)) + { + throw new ArgumentException("resourceProvider cannot be null or whitespace", "resourceProvider"); + } + if (string.IsNullOrWhiteSpace(methodUrlPathAfterProvider)) + { + throw new ArgumentException("methodUrlPathAfterProvider cannot be null or whitespace", "methodUrlPathAfterProvider"); + } + + List resourceTypeParts = new List(); + resourceTypeParts.Add(resourceProvider); + + string[] pathSegments = methodUrlPathAfterProvider.Split(new char[] { '/' }); + for (int i = 0; i < pathSegments.Length; i += 2) + { + resourceTypeParts.Add(pathSegments[i]); + } + + return string.Join("/", resourceTypeParts); + } + } +} diff --git a/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaWriter.cs b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaWriter.cs new file mode 100644 index 0000000000000..dbac591b4f345 --- /dev/null +++ b/AutoRest/Generators/AzureResourceSchema/AzureResourceSchema/ResourceSchemaWriter.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.Rest.Generator.AzureResourceSchema +{ + public static class ResourceSchemaWriter + { + public static void Write(TextWriter writer, ResourceSchemaModel resourceSchema) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (resourceSchema == null) + { + throw new ArgumentNullException("resourceSchema"); + } + + using (JsonTextWriter jsonWriter = new JsonTextWriter(writer)) + { + jsonWriter.Formatting = Formatting.Indented; + jsonWriter.Indentation = 2; + jsonWriter.IndentChar = ' '; + jsonWriter.QuoteChar = '\"'; + + Write(jsonWriter, resourceSchema); + } + } + + internal static void Write(JsonTextWriter writer, ResourceSchemaModel resourceSchema) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (resourceSchema == null) + { + throw new ArgumentNullException("resourceSchema"); + } + + writer.WriteStartObject(); + + WriteProperty(writer, "id", resourceSchema.Id); + WriteProperty(writer, "$schema", resourceSchema.Schema); + WriteProperty(writer, "title", resourceSchema.Title); + WriteProperty(writer, "description", resourceSchema.Description); + + WriteDefinitionMap(writer, "resourceDefinitions", resourceSchema.ResourceDefinitions, sortDefinitions: true); + + WriteDefinitionMap(writer, "definitions", resourceSchema.Definitions, sortDefinitions: true); + + writer.WriteEndObject(); + } + + private static void WriteDefinitionMap(JsonTextWriter writer, string definitionMapName, IDictionary definitionMap, bool sortDefinitions = false) + { + if (definitionMap != null && definitionMap.Count > 0) + { + writer.WritePropertyName(definitionMapName); + writer.WriteStartObject(); + + IEnumerable definitionNames = definitionMap.Keys; + if (sortDefinitions) + { + definitionNames = definitionNames.OrderBy(key => key); + } + + foreach (string definitionName in definitionNames) + { + JSONSchema definition = definitionMap[definitionName]; + WriteDefinition(writer, definitionName, definition); + } + writer.WriteEndObject(); + } + } + + internal static void WriteDefinition(JsonTextWriter writer, string resourceName, JSONSchema definition) + { + if (definition != null) + { + writer.WritePropertyName(resourceName); + writer.WriteStartObject(); + + WriteProperty(writer, "type", definition.JSONType); + WriteStringArray(writer, "enum", definition.Enum); + WriteProperty(writer, "$ref", definition.Ref); + WriteDefinition(writer, "additionalProperties", definition.AdditionalProperties); + WriteDefinitionMap(writer, "properties", definition.Properties); + WriteStringArray(writer, "required", definition.Required); + WriteProperty(writer, "description", definition.Description); + + writer.WriteEndObject(); + } + } + + private static void WriteStringArray(JsonTextWriter writer, string arrayName, IEnumerable arrayValues) + { + if (arrayValues != null && arrayValues.Count() > 0) + { + writer.WritePropertyName(arrayName); + writer.WriteStartArray(); + foreach (string arrayValue in arrayValues) + { + writer.WriteValue(arrayValue); + } + writer.WriteEndArray(); + } + } + + internal static void WriteProperty(JsonTextWriter writer, string propertyName, string propertyValue) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new ArgumentException("propertyName cannot be null or whitespace", "propertyName"); + } + + if (!string.IsNullOrWhiteSpace(propertyValue)) + { + writer.WritePropertyName(propertyName); + writer.WriteValue(propertyValue); + } + } + } +}