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,
            });
        }
Пример #3
0
        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);
        }