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, }); }
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); } } string discriminatorPropertyName = compositeType.BasePolymorphicDiscriminator; if (!string.IsNullOrWhiteSpace(discriminatorPropertyName)) { definition.AddProperty(discriminatorPropertyName, new JsonSchema() { JsonType = "string" }, true); Func <CompositeType, bool> isSubTypeOrSelf = type => type == compositeType || type.BaseModelType == compositeType; CompositeType[] subTypes = modelTypes.Where(isSubTypeOrSelf).ToArray(); foreach (CompositeType subType in subTypes) { JsonSchema derivedTypeDefinitionRef = new JsonSchema(); JsonSchema discriminatorDefinition = new JsonSchema() { JsonType = "string" }; discriminatorDefinition.AddEnum(subType.SerializedName); derivedTypeDefinitionRef.AddProperty(discriminatorPropertyName, discriminatorDefinition); if (subType != 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. ParseCompositeType(null, subType, false, definitions, modelTypes); derivedTypeDefinitionRef.AddAllOf(new JsonSchema() { Ref = "#/definitions/" + subType.Name, }); } definition.AddOneOf(derivedTypeDefinitionRef); } } } JsonSchema result = new JsonSchema() { Ref = "#/definitions/" + definitionName }; if (property != null) { result.Description = RemovePossibleValuesFromDescription(property.Documentation); } return(result); }