private static ResourceName CreateConstantResourceName(ResourceDescriptor descriptor, string nameValue, string description = null) { var constantNameSchema = descriptor.IsRootType ? JsonSchema.CreateSingleValuedEnum(nameValue) : new JsonSchema { JsonType = "string", Pattern = $"^.*/{Regex.Escape(nameValue)}$", }; constantNameSchema.Description = description; return(new ResourceName { HasConstantName = true, NameString = nameValue, NameSchema = constantNameSchema, }); }
private static void HandlePolymorphicType(JsonSchema definition, CompositeType compositeType, IDictionary <string, JsonSchema> definitions, IEnumerable <CompositeType> modelTypes) { if (!string.IsNullOrWhiteSpace(compositeType.BasePolymorphicDiscriminator)) { foreach (var subType in modelTypes.Where(type => type.BaseModelType == compositeType)) { // 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. var polymorphicTypeRef = ParseCompositeType(null, subType, false, definitions, modelTypes); definitions[subType.Name].AddProperty(compositeType.BasePolymorphicDiscriminator, JsonSchema.CreateSingleValuedEnum(subType.SerializedName), true); definition.AddOneOf(polymorphicTypeRef); } } }
private static ResourceSchema CreateSchema(ProviderDefinition providerDefinition) { var processedSchemas = new Dictionary <string, JsonSchema>(StringComparer.OrdinalIgnoreCase); var resourceDefinitions = new Dictionary <ResourceDescriptor, JsonSchema>(ResourceDescriptor.Comparer); // Order by resource type length to process parent resources before child resources var definitionsByDescriptor = providerDefinition .ResourceDefinitions.ToLookup(x => x.Descriptor, ResourceDescriptor.Comparer) .OrderBy(grouping => grouping.Key.ResourceTypeSegments.Count); foreach (var definitionGrouping in definitionsByDescriptor) { var descriptor = definitionGrouping.Key; var definitions = definitionGrouping.ToArray(); if (processedSchemas.ContainsKey(descriptor.FullyQualifiedTypeWithScope)) { LogWarning($"Found duplicate definition for type {descriptor.FullyQualifiedType} in scope {descriptor.ScopeType}"); continue; } if (definitions.Length > 1 && descriptor.HasVariableName) { var selectedDefinition = definitions.First(); foreach (var definition in definitions.Skip(1)) { LogWarning($"Found duplicate definition for variable-named type {descriptor.FullyQualifiedType}. Skipping definition with path '{definition.Descriptor.XmsMetadata.path}'."); } LogWarning($"Found duplicate definition for variable-named type {descriptor.FullyQualifiedType}. Using definition with path '{selectedDefinition.Descriptor.XmsMetadata.path}'."); definitions = new[] { selectedDefinition }; } // Add schema to global resources { JsonSchema schema; if (definitions.Length == 1) { schema = definitions.Single().BaseSchema.Clone(); schema.AddPropertyWithOverwrite("name", definitions.Single().Name.NameSchema.Clone(), true); schema.AddPropertyWithOverwrite("type", JsonSchema.CreateSingleValuedEnum(descriptor.FullyQualifiedType), true); schema.AddPropertyWithOverwrite("apiVersion", JsonSchema.CreateSingleValuedEnum(descriptor.ApiVersion), true); } else { schema = new JsonSchema { JsonType = "object", Description = descriptor.FullyQualifiedType, }; foreach (var definition in definitions) { if (!definition.Name.HasConstantName) { throw new InvalidOperationException($"Unable to reconcile variable-named resource {descriptor.FullyQualifiedType}"); } var oneOfSchema = definition.BaseSchema.Clone(); oneOfSchema.AddPropertyWithOverwrite("name", definition.Name.NameSchema.Clone(), true); schema.AddOneOf(oneOfSchema); } schema.AddPropertyWithOverwrite("type", JsonSchema.CreateSingleValuedEnum(descriptor.FullyQualifiedType), true); schema.AddPropertyWithOverwrite("apiVersion", JsonSchema.CreateSingleValuedEnum(descriptor.ApiVersion), true); } processedSchemas[descriptor.FullyQualifiedTypeWithScope] = schema; resourceDefinitions[descriptor] = schema; } // Add schema to parent resources if necessary if (!descriptor.IsRootType && processedSchemas.TryGetValue(ResourceDescriptor.FormatParentFullyQualifiedTypeWithScope(descriptor), out var parentSchema)) { if (!parentSchema.Properties.ContainsKey("resources")) { parentSchema.AddProperty("resources", new JsonSchema { JsonType = "array", Items = new JsonSchema(), }); } JsonSchema childSchema; if (definitions.Length == 1) { childSchema = definitions.Single().BaseSchema.Clone(); var resourceName = definitions.Single().Name; var nameSchema = resourceName.HasConstantName ? JsonSchema.CreateSingleValuedEnum(resourceName.NameString) : resourceName.NameSchema; nameSchema.Description = resourceName.NameSchema?.Description; childSchema.AddPropertyWithOverwrite("name", nameSchema, true); childSchema.AddPropertyWithOverwrite("type", JsonSchema.CreateSingleValuedEnum(descriptor.ResourceTypeSegments.Last()), true); childSchema.AddPropertyWithOverwrite("apiVersion", JsonSchema.CreateSingleValuedEnum(descriptor.ApiVersion), true); } else { childSchema = new JsonSchema { JsonType = "object", Description = descriptor.FullyQualifiedType, }; foreach (var definition in definitions) { if (!definition.Name.HasConstantName) { throw new InvalidOperationException($"Unable to reconcile variable-named resource {descriptor.FullyQualifiedType}"); } var oneOfSchema = definition.BaseSchema.Clone(); var nameSchema = JsonSchema.CreateSingleValuedEnum(definition.Name.NameString); nameSchema.Description = definition.Name.NameSchema?.Description; oneOfSchema.AddPropertyWithOverwrite("name", nameSchema, true); childSchema.AddOneOf(oneOfSchema); } childSchema.AddPropertyWithOverwrite("type", JsonSchema.CreateSingleValuedEnum(descriptor.ResourceTypeSegments.Last()), true); childSchema.AddPropertyWithOverwrite("apiVersion", JsonSchema.CreateSingleValuedEnum(descriptor.ApiVersion), true); } var childDefinitionName = ResourceSchema.FormatResourceSchemaKey(descriptor.ResourceTypeSegments) + "_childResource"; providerDefinition.SchemaDefinitions.Add(childDefinitionName, childSchema); parentSchema.Properties["resources"].Items.AddOneOf(new JsonSchema { Ref = "#/definitions/" + childDefinitionName, }); } } return(new ResourceSchema { Id = $"https://schema.management.azure.com/schemas/{providerDefinition.ApiVersion}/{providerDefinition.Namespace}.json#", Title = providerDefinition.Namespace, Description = providerDefinition.Namespace.Replace('.', ' ') + " Resource Types", Schema = "http://json-schema.org/draft-04/schema#", Definitions = providerDefinition.SchemaDefinitions, ResourceDefinitions = resourceDefinitions, }); }
/// <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); }