private static JsonSchema ParseCompositeType(Property property, CompositeType compositeType, bool includeBaseModelTypeProperties, IDictionary <string, JsonSchema> definitions, IEnumerable <CompositeType> modelTypes) { string definitionName = compositeType.Name; if (!definitions.ContainsKey(definitionName)) { JsonSchema definition = new JsonSchema() { JsonType = "object", Description = compositeType.Documentation, }; // This definition must be added to the definition map before we start parsing // its properties because its properties may recursively reference back to this // definition. definitions.Add(definitionName, definition); IEnumerable <Property> compositeTypeProperties = includeBaseModelTypeProperties ? compositeType.ComposedProperties : compositeType.Properties; foreach (Property subProperty in compositeTypeProperties) { JsonSchema subPropertyDefinition = ParseType(subProperty, subProperty.ModelType, definitions, modelTypes); if (subPropertyDefinition != null) { definition.AddProperty(subProperty.SerializedName.Else(subProperty.Name.RawValue), subPropertyDefinition, subProperty.IsRequired); } } HandlePolymorphicType(definition, compositeType, definitions, modelTypes); } JsonSchema result = new JsonSchema() { Ref = "#/definitions/" + definitionName }; if (property != null) { result.Description = RemovePossibleValuesFromDescription(property.Documentation); } return(result); }
private static JsonSchema ParseCompositeType(Property property, CompositeType compositeType, IDictionary<string, JsonSchema> definitions, IEnumerable<CompositeType> modelTypes) { string definitionName = compositeType.Name; if (!definitions.ContainsKey(definitionName)) { JsonSchema definition = new JsonSchema() { JsonType = "object", Description = compositeType.Documentation }; // This definition must be added to the definition map before we start parsing // its properties because its properties may recursively reference back to this // definition. definitions.Add(definitionName, definition); foreach (Property subProperty in compositeType.ComposedProperties) { JsonSchema subPropertyDefinition = ParseType(subProperty, subProperty.Type, definitions, modelTypes); if (subPropertyDefinition != null) { definition.AddProperty(subProperty.Name, subPropertyDefinition, subProperty.IsRequired); } } string discriminatorPropertyName = compositeType.PolymorphicDiscriminator; if (!string.IsNullOrWhiteSpace(discriminatorPropertyName)) { CompositeType[] subTypes = modelTypes.Where(modelType => modelType.BaseModelType == compositeType).ToArray(); if (subTypes != null && subTypes.Length > 0) { JsonSchema discriminatorDefinition = new JsonSchema() { JsonType = "string" }; if (subTypes.Length == 1) { CompositeType subType = subTypes[0]; if (subType != null) { foreach (Property subTypeProperty in subType.Properties) { JsonSchema subTypePropertyDefinition = ParseType(subTypeProperty, subTypeProperty.Type, definitions, modelTypes); if (subTypePropertyDefinition != null) { definition.AddProperty(subTypeProperty.Name, subTypePropertyDefinition, subTypeProperty.IsRequired); } } const string discriminatorValueExtensionName = "x-ms-discriminator-value"; if (subType.ComposedExtensions.ContainsKey(discriminatorValueExtensionName)) { string discriminatorValue = subType.ComposedExtensions[discriminatorValueExtensionName] as string; if (!string.IsNullOrWhiteSpace(discriminatorValue)) { discriminatorDefinition.AddEnum(discriminatorValue); } } } definition.AddProperty(discriminatorPropertyName, discriminatorDefinition); } else { string errorMessage = string.Format( CultureInfo.CurrentCulture, "Multiple sub-types ({0}) of a polymorphic discriminated type ({1}) are not currently supported.", string.Join(", ", subTypes.Select(subType => subType.Name)), compositeType.Name); throw new NotSupportedException(errorMessage); } } } } JsonSchema result = new JsonSchema() { Ref = "#/definitions/" + definitionName }; if (property != null) { result.Description = RemovePossibleValuesFromDescription(property.Documentation); } return result; }
/// <summary> /// Parse a ResourceSchemaModel from the provided ServiceClient. /// </summary> /// <param name="serviceClient"></param> /// <returns></returns> public static IDictionary<string, ResourceSchema> Parse(ServiceClient serviceClient) { if (serviceClient == null) { throw new ArgumentNullException(nameof(serviceClient)); } string apiVersion = serviceClient.ApiVersion; if (string.IsNullOrWhiteSpace(apiVersion)) { throw new ArgumentException("No API version is provided in the swagger document."); } Dictionary<string, ResourceSchema> resourceSchemas = new Dictionary<string, ResourceSchema>(); foreach (Method method in serviceClient.Methods) { if (method.HttpMethod != HttpMethod.Put || method.ReturnType.Body == null || !(method.ReturnType.Body is CompositeType) || string.IsNullOrWhiteSpace(method.Url) || !method.Url.StartsWith(resourceMethodPrefix, StringComparison.OrdinalIgnoreCase) || !method.Url.EndsWith("}", StringComparison.OrdinalIgnoreCase)) { continue; } string afterPrefix = method.Url.Substring(resourceMethodPrefix.Length); int forwardSlashIndexAfterProvider = afterPrefix.IndexOf('/'); string resourceProvider = afterPrefix.Substring(0, forwardSlashIndexAfterProvider); if (IsPathVariable(resourceProvider)) { // If the resourceProvider is a path variable, such as {someValue}, then this // is not a create resource method. Skip it. continue; } ResourceSchema resourceSchema; if (!resourceSchemas.ContainsKey(resourceProvider)) { resourceSchema = new ResourceSchema(); resourceSchema.Id = string.Format(CultureInfo.InvariantCulture, "http://schema.management.azure.com/schemas/{0}/{1}.json#", apiVersion, resourceProvider); resourceSchema.Title = resourceProvider; resourceSchema.Description = resourceProvider.Replace('.', ' ') + " Resource Types"; resourceSchema.Schema = "http://json-schema.org/draft-04/schema#"; resourceSchemas.Add(resourceProvider, resourceSchema); } else { resourceSchema = resourceSchemas[resourceProvider]; } string methodUrlPathAfterProvider = afterPrefix.Substring(forwardSlashIndexAfterProvider + 1); string[] resourceTypes = ParseResourceTypes(resourceProvider, methodUrlPathAfterProvider, method); foreach (string resourceType in resourceTypes) { JsonSchema resourceDefinition = new JsonSchema(); resourceDefinition.JsonType = "object"; resourceDefinition.AddProperty("type", JsonSchema.CreateStringEnum(resourceType), true); resourceDefinition.AddProperty("apiVersion", JsonSchema.CreateStringEnum(apiVersion), true); if (method.Body != null) { CompositeType body = method.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.ComposedProperties) { if (!resourceDefinition.Properties.Keys.Contains(property.Name)) { JsonSchema propertyDefinition = ParseType(property, property.Type, resourceSchema.Definitions, serviceClient.ModelTypes); if (propertyDefinition != null) { resourceDefinition.AddProperty(property.Name, propertyDefinition, property.IsRequired || property.Name == "properties"); } } } } } resourceDefinition.Description = resourceType; string resourcePropertyName = resourceType.Substring(resourceProvider.Length + 1).Replace('/', '_'); Debug.Assert(!resourceSchema.ResourceDefinitions.ContainsKey(resourcePropertyName)); resourceSchema.AddResourceDefinition(resourcePropertyName, resourceDefinition); } } // This loop adds child resource schemas to their parent resource schemas. We can't do // this until we're done adding all resources as top level resources, though, because // it's possible that we will parse a child resource before we parse the parent // resource. foreach (ResourceSchema resourceSchema in resourceSchemas.Values) { // By iterating over the reverse order of the defined resource definitions, I'm // counting on the resource definitions being in sorted order. That way I'm // guaranteed to visit child resource definitions before I visit their parent // resource definitions. By doing this, I've guaranteed that grandchildren resource // definitions will be added to their grandparent (and beyond) ancestor // resource definitions. foreach (string resourcePropertyName in resourceSchema.ResourceDefinitions.Keys.Reverse()) { JsonSchema resourceDefinition = resourceSchema.ResourceDefinitions[resourcePropertyName]; string resourceType = resourceDefinition.ResourceType; int lastSlashIndex = resourceType.LastIndexOf('/'); string parentResourceType = resourceType.Substring(0, lastSlashIndex); JsonSchema parentResourceDefinition = resourceSchema.GetResourceDefinitionByResourceType(parentResourceType); if (parentResourceDefinition != null) { string childResourceType = resourceType.Substring(lastSlashIndex + 1); JsonSchema childResourceDefinition = resourceDefinition.Clone(); childResourceDefinition.ResourceType = childResourceType; string childResourceDefinitionPropertyName = string.Join("_", resourcePropertyName, "childResource"); resourceSchema.AddDefinition(childResourceDefinitionPropertyName, childResourceDefinition); JsonSchema childResources; if (parentResourceDefinition.Properties.ContainsKey("resources")) { childResources = parentResourceDefinition.Properties["resources"]; } else { childResources = new JsonSchema() { JsonType = "array", Items = new JsonSchema() }; parentResourceDefinition.AddProperty("resources", childResources); } childResources.Items.AddOneOf(new JsonSchema() { Ref = "#/definitions/" + childResourceDefinitionPropertyName, }); } } } return resourceSchemas; }
private static JsonSchema ParseCompositeType(Property property, CompositeType compositeType, bool includeBaseModelTypeProperties, IDictionary <string, JsonSchema> definitions, IEnumerable <CompositeType> modelTypes) { string definitionName = compositeType.SerializedName; if (!definitions.ContainsKey(definitionName)) { JsonSchema definition = new JsonSchema() { JsonType = "object", Description = compositeType.Documentation, }; // This definition must be added to the definition map before we start parsing // its properties because its properties may recursively reference back to this // definition. definitions.Add(definitionName, definition); JsonSchema baseTypeDefinition; string discriminatorPropertyName = compositeType.PolymorphicDiscriminator; if (string.IsNullOrWhiteSpace(discriminatorPropertyName)) { baseTypeDefinition = definition; } else { baseTypeDefinition = new JsonSchema(); definition.AddAllOf(baseTypeDefinition); JsonSchema derivedTypeDefinitionRefs = new JsonSchema(); CompositeType[] subTypes = modelTypes.Where(modelType => modelType.BaseModelType == compositeType).ToArray(); if (subTypes != null && subTypes.Length > 0) { JsonSchema discriminatorDefinition = new JsonSchema() { JsonType = "string" }; foreach (CompositeType subType in subTypes) { if (subType != null) { // Sub-types are never referenced directly in the Swagger // discriminator scenario, so they wouldn't be added to the // produced resource schema. By calling ParseCompositeType() on the // sub-type we add the sub-type to the resource schema. ParseCompositeType(null, subType, false, definitions, modelTypes); derivedTypeDefinitionRefs.AddAnyOf(new JsonSchema() { Ref = "#/definitions/" + subType.SerializedName, }); const string discriminatorValueExtensionName = "x-ms-discriminator-value"; if (subType.ComposedExtensions.ContainsKey(discriminatorValueExtensionName)) { string discriminatorValue = subType.ComposedExtensions[discriminatorValueExtensionName] as string; if (!string.IsNullOrWhiteSpace(discriminatorValue)) { discriminatorDefinition.AddEnum(discriminatorValue); } } } } baseTypeDefinition.AddProperty(discriminatorPropertyName, discriminatorDefinition); definition.AddAllOf(derivedTypeDefinitionRefs); } } IEnumerable <Property> compositeTypeProperties = includeBaseModelTypeProperties ? compositeType.ComposedProperties : compositeType.Properties; foreach (Property subProperty in compositeTypeProperties) { JsonSchema subPropertyDefinition = ParseType(subProperty, subProperty.ModelType, definitions, modelTypes); if (subPropertyDefinition != null) { baseTypeDefinition.AddProperty(subProperty.SerializedName.Else(subProperty.Name.RawValue), subPropertyDefinition, subProperty.IsRequired); } } } JsonSchema result = new JsonSchema() { Ref = "#/definitions/" + definitionName }; if (property != null) { result.Description = RemovePossibleValuesFromDescription(property.Documentation); } return(result); }
/// <summary> /// Parse a ResourceSchemaModel from the provided ServiceClient. /// </summary> /// <param name="serviceClient"></param> /// <returns></returns> public static IDictionary <string, ResourceSchema> Parse(CodeModel serviceClient, string version) { if (serviceClient == null) { throw new ArgumentNullException(nameof(serviceClient)); } Dictionary <string, ResourceSchema> resourceSchemas = new Dictionary <string, ResourceSchema>(); foreach (Method method in serviceClient.Methods.Where(method => method.Parameters.FirstOrDefault(p => p.SerializedName == "api-version")?.DefaultValue.Value == version || version == serviceClient.ApiVersion)) { if (method.HttpMethod != HttpMethod.Put || string.IsNullOrWhiteSpace(method.Url) || !method.Url.Value.StartsWith(resourceMethodPrefix, StringComparison.OrdinalIgnoreCase) || !method.Url.Value.EndsWith("}", StringComparison.OrdinalIgnoreCase)) { continue; } string afterPrefix = method.Url.Value.Substring(resourceMethodPrefix.Length); int forwardSlashIndexAfterProvider = afterPrefix.IndexOf('/'); string resourceProvider = afterPrefix.Substring(0, forwardSlashIndexAfterProvider); if (IsPathVariable(resourceProvider)) { // If the resourceProvider is a path variable, such as {someValue}, then this // is not a create resource method. Skip it. continue; } // extract API version string apiVersion = serviceClient.ApiVersion.Else(method.Parameters.FirstOrDefault(p => p.SerializedName == "api-version")?.DefaultValue); if (string.IsNullOrWhiteSpace(apiVersion)) { throw new ArgumentException("No API version is provided in the swagger document or the method."); } ResourceSchema resourceSchema; if (!resourceSchemas.ContainsKey(resourceProvider)) { resourceSchema = new ResourceSchema(); resourceSchema.Id = string.Format(CultureInfo.InvariantCulture, "https://schema.management.azure.com/schemas/{0}/{1}.json#", apiVersion, resourceProvider); resourceSchema.Title = resourceProvider; resourceSchema.Description = resourceProvider.Replace('.', ' ') + " Resource Types"; resourceSchema.Schema = "http://json-schema.org/draft-04/schema#"; resourceSchemas.Add(resourceProvider, resourceSchema); } else { resourceSchema = resourceSchemas[resourceProvider]; } string methodUrlPathAfterProvider = afterPrefix.Substring(forwardSlashIndexAfterProvider + 1); string[] resourceTypes = ParseResourceTypes(resourceProvider, methodUrlPathAfterProvider, method); foreach (string resourceType in resourceTypes) { JsonSchema resourceDefinition = new JsonSchema(); resourceDefinition.JsonType = "object"; resourceDefinition.ResourceType = resourceType; // get the resource name parameter, e.g. {fooName} var resNameParam = methodUrlPathAfterProvider.Substring(methodUrlPathAfterProvider.LastIndexOf('/') + 1); if (IsPathVariable(resNameParam)) { // strip the enclosing braces resNameParam = resNameParam.Trim(new[] { '{', '}' }); // look up the type var param = method.Parameters.Where(p => p.SerializedName == resNameParam).FirstOrDefault(); if (param != null) { // create a schema for it var nameParamSchema = ParseType(param.ClientProperty, param.ModelType, resourceSchema.Definitions, serviceClient.ModelTypes); nameParamSchema.ResourceType = resNameParam; // add it as the name property resourceDefinition.AddProperty("name", nameParamSchema, true); } } resourceDefinition.AddProperty("type", JsonSchema.CreateStringEnum(resourceType), true); resourceDefinition.AddProperty("apiVersion", JsonSchema.CreateStringEnum(apiVersion), true); if (method.Body != null) { CompositeType body = method.Body.ModelType 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.ComposedProperties) { if (!resourceDefinition.Properties.Keys.Contains(property.SerializedName)) { JsonSchema propertyDefinition = ParseType(property, property.ModelType, resourceSchema.Definitions, serviceClient.ModelTypes); if (propertyDefinition != null) { resourceDefinition.AddProperty(property.SerializedName, propertyDefinition, property.IsRequired || property.SerializedName == "properties"); } } } } } resourceDefinition.Description = resourceType; string resourcePropertyName = resourceType.Substring(resourceProvider.Length + 1).Replace('/', '_'); Debug.Assert(!resourceSchema.ResourceDefinitions.ContainsKey(resourcePropertyName)); resourceSchema.AddResourceDefinition(resourcePropertyName, resourceDefinition); } } // This loop adds child resource schemas to their parent resource schemas. We can't do // this until we're done adding all resources as top level resources, though, because // it's possible that we will parse a child resource before we parse the parent // resource. foreach (ResourceSchema resourceSchema in resourceSchemas.Values) { // By iterating over the reverse order of the defined resource definitions, I'm // counting on the resource definitions being in sorted order. That way I'm // guaranteed to visit child resource definitions before I visit their parent // resource definitions. By doing this, I've guaranteed that grandchildren resource // definitions will be added to their grandparent (and beyond) ancestor // resource definitions. foreach (string resourcePropertyName in resourceSchema.ResourceDefinitions.Keys.Reverse()) { JsonSchema resourceDefinition = resourceSchema.ResourceDefinitions[resourcePropertyName]; string resourceType = resourceDefinition.ResourceType; int lastSlashIndex = resourceType.LastIndexOf('/'); string parentResourceType = resourceType.Substring(0, lastSlashIndex); JsonSchema parentResourceDefinition = resourceSchema.GetResourceDefinitionByResourceType(parentResourceType); if (parentResourceDefinition != null) { string childResourceType = resourceType.Substring(lastSlashIndex + 1); JsonSchema childResourceDefinition = resourceDefinition.Clone(); childResourceDefinition.ResourceType = childResourceType; string childResourceDefinitionPropertyName = string.Join("_", resourcePropertyName, "childResource"); resourceSchema.AddDefinition(childResourceDefinitionPropertyName, childResourceDefinition); JsonSchema childResources; if (parentResourceDefinition.Properties.ContainsKey("resources")) { childResources = parentResourceDefinition.Properties["resources"]; } else { childResources = new JsonSchema() { JsonType = "array", Items = new JsonSchema() }; parentResourceDefinition.AddProperty("resources", childResources); } childResources.Items.AddOneOf(new JsonSchema() { Ref = "#/definitions/" + childResourceDefinitionPropertyName, }); } } } return(resourceSchemas); }
private static JsonSchema ParseCompositeType(Property property, CompositeType compositeType, IDictionary <string, JsonSchema> definitions, IEnumerable <CompositeType> modelTypes) { string definitionName = compositeType.Name; if (!definitions.ContainsKey(definitionName)) { JsonSchema definition = new JsonSchema() { JsonType = "object", Description = compositeType.Documentation }; // This definition must be added to the definition map before we start parsing // its properties because its properties may recursively reference back to this // definition. definitions.Add(definitionName, definition); foreach (Property subProperty in compositeType.ComposedProperties) { JsonSchema subPropertyDefinition = ParseType(subProperty, subProperty.Type, definitions, modelTypes); if (subPropertyDefinition != null) { definition.AddProperty(subProperty.Name, subPropertyDefinition, subProperty.IsRequired); } } string discriminatorPropertyName = compositeType.PolymorphicDiscriminator; if (!string.IsNullOrWhiteSpace(discriminatorPropertyName)) { CompositeType[] subTypes = modelTypes.Where(modelType => modelType.BaseModelType == compositeType).ToArray(); if (subTypes != null && subTypes.Length > 0) { JsonSchema discriminatorDefinition = new JsonSchema() { JsonType = "string" }; if (subTypes.Length == 1) { CompositeType subType = subTypes[0]; if (subType != null) { foreach (Property subTypeProperty in subType.Properties) { JsonSchema subTypePropertyDefinition = ParseType(subTypeProperty, subTypeProperty.Type, definitions, modelTypes); if (subTypePropertyDefinition != null) { definition.AddProperty(subTypeProperty.Name, subTypePropertyDefinition, subTypeProperty.IsRequired); } } const string discriminatorValueExtensionName = "x-ms-discriminator-value"; if (subType.ComposedExtensions.ContainsKey(discriminatorValueExtensionName)) { string discriminatorValue = subType.ComposedExtensions[discriminatorValueExtensionName] as string; if (!string.IsNullOrWhiteSpace(discriminatorValue)) { discriminatorDefinition.AddEnum(discriminatorValue); } } } definition.AddProperty(discriminatorPropertyName, discriminatorDefinition); } else { string errorMessage = string.Format( CultureInfo.CurrentCulture, "Multiple sub-types ({0}) of a polymorphic discriminated type ({1}) are not currently supported.", string.Join(", ", subTypes.Select(subType => subType.Name)), compositeType.Name); throw new NotSupportedException(errorMessage); } } } } JsonSchema result = new JsonSchema() { Ref = "#/definitions/" + definitionName }; if (property != null) { result.Description = RemovePossibleValuesFromDescription(property.Documentation); } return(result); }
/// <summary> /// Parse a ResourceSchemaModel from the provided ServiceClient. /// </summary> /// <param name="serviceClient"></param> /// <returns></returns> public static IDictionary <string, ResourceSchema> Parse(CodeModel serviceClient, string apiVersion) { if (serviceClient == null) { throw new ArgumentNullException(nameof(serviceClient)); } var providerDefinitions = new Dictionary <string, ProviderDefinition>(StringComparer.OrdinalIgnoreCase); foreach (var method in serviceClient.Methods.Where(method => ShouldProcess(serviceClient, method, apiVersion))) { var(success, failureReason, resourceDescriptors) = ParseMethod(method, apiVersion); if (!success) { LogWarning($"Skipping path '{method.Url}': {failureReason}"); continue; } foreach (var descriptor in resourceDescriptors) { if (!providerDefinitions.ContainsKey(descriptor.ProviderNamespace)) { providerDefinitions[descriptor.ProviderNamespace] = new ProviderDefinition { Namespace = descriptor.ProviderNamespace, ApiVersion = apiVersion, }; } var providerDefinition = providerDefinitions[descriptor.ProviderNamespace]; var baseSchema = new JsonSchema { JsonType = "object", ResourceType = descriptor.FullyQualifiedType, Description = descriptor.FullyQualifiedType, }; ResourceName resourceName; (success, failureReason, resourceName) = ParseNameSchema(serviceClient, method, providerDefinition, descriptor); if (!success) { LogWarning($"Skipping resource type {descriptor.FullyQualifiedType} under path '{method.Url}': {failureReason}"); continue; } if (method.Body?.ModelType is CompositeType body) { foreach (var property in body.ComposedProperties) { if (property.SerializedName == null) { continue; } if (baseSchema.Properties != null && baseSchema.Properties.Keys.Contains(property.SerializedName)) { continue; } var propertyDefinition = ParseType(property, property.ModelType, providerDefinition.SchemaDefinitions, serviceClient.ModelTypes); if (propertyDefinition != null) { baseSchema.AddProperty(property.SerializedName, propertyDefinition, property.IsRequired || property.SerializedName == "properties"); } } HandlePolymorphicType(baseSchema, body, providerDefinition.SchemaDefinitions, serviceClient.ModelTypes); } providerDefinition.ResourceDefinitions.Add(new ResourceDefinition { BaseSchema = baseSchema, Descriptor = descriptor, Name = resourceName, }); } } return(providerDefinitions.ToDictionary( kvp => kvp.Key, kvp => CreateSchema(kvp.Value), StringComparer.OrdinalIgnoreCase)); }
/// <summary> /// Parse a ResourceSchemaModel from the provided ServiceClient. /// </summary> /// <param name="serviceClient"></param> /// <returns></returns> public static IDictionary <string, ResourceSchema> Parse(CodeModel serviceClient, string apiVersion, bool multiScope) { if (serviceClient == null) { throw new ArgumentNullException(nameof(serviceClient)); } var providerSchemas = new Dictionary <string, ResourceSchema>(); foreach (var method in serviceClient.Methods.Where(method => ShouldProcess(serviceClient, method, apiVersion))) { var(success, failureReason, resourceDescriptors) = ParseMethod(method, apiVersion); if (!success) { LogMessage($"Skipping path '{method.Url}': {failureReason}"); continue; } foreach (var descriptor in resourceDescriptors) { if (!multiScope && descriptor.ScopeType != ScopeType.ResourceGroup) { LogMessage($"Skipping resource type {descriptor.FullyQualifiedType} under path '{method.Url}': Detected scope type {descriptor.ScopeType} and multi-scope is disabled"); continue; } if (!providerSchemas.ContainsKey(descriptor.ProviderNamespace)) { providerSchemas.Add(descriptor.ProviderNamespace, new ResourceSchema { Id = $"https://schema.management.azure.com/schemas/{apiVersion}/{descriptor.ProviderNamespace}.json#", Title = descriptor.ProviderNamespace, Description = descriptor.ProviderNamespace.Replace('.', ' ') + " Resource Types", Schema = "http://json-schema.org/draft-04/schema#" }); } var providerSchema = providerSchemas[descriptor.ProviderNamespace]; var resourceSchema = new JsonSchema { JsonType = "object", ResourceType = descriptor.FullyQualifiedType, Description = descriptor.FullyQualifiedType, }; JsonSchema nameSchema; (success, failureReason, nameSchema) = ParseNameSchema(serviceClient, method, providerSchema, descriptor); if (!success) { LogMessage($"Skipping resource type {descriptor.FullyQualifiedType} under path '{method.Url}': {failureReason}"); continue; } resourceSchema.AddProperty("name", nameSchema, true); resourceSchema.AddProperty("type", JsonSchema.CreateSingleValuedEnum(descriptor.FullyQualifiedType), true); resourceSchema.AddProperty("apiVersion", JsonSchema.CreateSingleValuedEnum(apiVersion), true); if (method.Body?.ModelType is CompositeType body) { foreach (var property in body.ComposedProperties) { if (property.SerializedName != null && !resourceSchema.Properties.Keys.Contains(property.SerializedName)) { var propertyDefinition = ParseType(property, property.ModelType, providerSchema.Definitions, serviceClient.ModelTypes); if (propertyDefinition != null) { resourceSchema.AddProperty(property.SerializedName, propertyDefinition, property.IsRequired || property.SerializedName == "properties"); } } } HandlePolymorphicType(resourceSchema, body, providerSchema.Definitions, serviceClient.ModelTypes); } string resourcePropertyName = ResourceSchema.FormatResourceSchemaKey(descriptor.ResourceTypeSegments); if (providerSchema.ResourceDefinitions.ContainsKey(resourcePropertyName)) { LogMessage($"Skipping resource type {descriptor.FullyQualifiedType} under path '{method.Url}': Duplicate resource definition {resourcePropertyName}"); continue; } providerSchema.AddResourceDefinition(resourcePropertyName, new ResourceDefinition { Descriptor = descriptor, Schema = resourceSchema, }); } } // This loop adds child resource schemas to their parent resource schemas. We can't do // this until we're done adding all resources as top level resources, though, because // it's possible that we will parse a child resource before we parse the parent // resource. foreach (var providerSchema in providerSchemas.Values) { // Sort by resource length to process parent resources before children var childResourceDefinitions = providerSchema.ResourceDefinitions.Values .Where(resource => resource.Descriptor.ResourceTypeSegments.Count() > 1) .OrderBy(resource => resource.Descriptor.ResourceTypeSegments.Count()); foreach (var childResource in childResourceDefinitions) { var parentTypeSegments = childResource.Descriptor.ResourceTypeSegments.SkipLast(1); if (!providerSchema.GetResourceDefinitionByResourceType(parentTypeSegments, out var parentResource)) { continue; } var childResourceSchema = childResource.Schema.Clone(); childResourceSchema.ResourceType = childResource.Descriptor.ResourceTypeSegments.Last(); var childResourceDefinitionName = ResourceSchema.FormatResourceSchemaKey(childResource.Descriptor.ResourceTypeSegments) + "_childResource"; providerSchema.AddDefinition(childResourceDefinitionName, childResourceSchema); if (!parentResource.Schema.Properties.ContainsKey("resources")) { parentResource.Schema.AddProperty("resources", new JsonSchema { JsonType = "array", Items = new JsonSchema() }); } parentResource.Schema.Properties["resources"].Items.AddOneOf(new JsonSchema { Ref = "#/definitions/" + childResourceDefinitionName, }); } } return(providerSchemas); }