public static void Write(TextWriter writer, ResourceSchema resourceSchema)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }
            if (resourceSchema == null)
            {
                throw new ArgumentNullException("resourceSchema");
            }

            var mdWriter = new ResourceMarkdownWriter(writer, resourceSchema);
            mdWriter.Generate();
        }
Exemple #2
0
        public static void Write(TextWriter writer, ResourceSchema resourceSchema)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }
            if (resourceSchema == null)
            {
                throw new ArgumentNullException("resourceSchema");
            }

            var mdWriter = new ResourceMarkdownWriter(writer, resourceSchema);

            mdWriter.Generate();
        }
        public static void Write(TextWriter writer, ResourceSchema 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);
            }
        }
        public static void Write(TextWriter writer, ResourceSchema 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);
            }
        }
        public static void Write(JsonWriter writer, ResourceSchema 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();
        }
        public static void Write(JsonWriter writer, ResourceSchema 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();
        }
        public static Markdown[] Generate(ResourceSchema resourceSchema)
        {
            if (resourceSchema == null)
            {
                throw new ArgumentNullException("resourceSchema");
            }

            var mds   = new Markdown[resourceSchema.ResourceDefinitions.Keys.Count];
            int index = 0;

            foreach (var resDefName in resourceSchema.ResourceDefinitions.Keys)
            {
                var resDef = resourceSchema.ResourceDefinitions[resDefName];

                var writer   = new StringWriter();
                var mdWriter = new ResourceMarkdownGenerator(writer, resourceSchema);
                mdWriter.Generate(resDef);

                mds[index] = new Markdown(resDefName, writer.ToString());
                ++index;
            }

            return(mds);
        }
        public static void Write(JsonWriter writer, ResourceSchema 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);

            var rgDefinitions = GetResourceDefinitions(resourceSchema, ScopeType.ResourceGroup);

            WriteDefinitionMap(writer, "resourceDefinitions", rgDefinitions, sortDefinitions: true, addExpressionReferences: false);

            var subDefinitions = GetResourceDefinitions(resourceSchema, ScopeType.Subcription);

            if (subDefinitions.Any())
            {
                WriteDefinitionMap(writer, "subscription_resourceDefinitions", subDefinitions, sortDefinitions: true, addExpressionReferences: false);
            }

            var mgDefinitions = GetResourceDefinitions(resourceSchema, ScopeType.ManagementGroup);

            if (mgDefinitions.Any())
            {
                WriteDefinitionMap(writer, "managementGroup_resourceDefinitions", mgDefinitions, sortDefinitions: true, addExpressionReferences: false);
            }

            var tenantDefinitions = GetResourceDefinitions(resourceSchema, ScopeType.Tenant);

            if (tenantDefinitions.Any())
            {
                WriteDefinitionMap(writer, "tenant_resourceDefinitions", tenantDefinitions, sortDefinitions: true, addExpressionReferences: false);
            }

            var extDefinitions = GetResourceDefinitions(resourceSchema, ScopeType.Extension);

            if (extDefinitions.Any())
            {
                WriteDefinitionMap(writer, "extension_resourceDefinitions", extDefinitions, sortDefinitions: true, addExpressionReferences: false);
            }

            var unknownDefinitions = GetResourceDefinitions(resourceSchema, ScopeType.Unknown);

            if (unknownDefinitions.Any())
            {
                WriteDefinitionMap(writer, "unknown_resourceDefinitions", unknownDefinitions, sortDefinitions: true, addExpressionReferences: false);
            }

            WriteDefinitionMap(writer, "definitions", resourceSchema.Definitions, sortDefinitions: true, addExpressionReferences: false);

            writer.WriteEndObject();
        }
 private static IDictionary <string, JsonSchema> GetResourceDefinitions(ResourceSchema resourceSchema, ScopeType scopeType)
 => resourceSchema.ResourceDefinitions
 .Where(kvp => kvp.Key.ScopeType == scopeType)
 .ToDictionary(kvp => ResourceSchema.FormatResourceSchemaKey(kvp.Key.ResourceTypeSegments), kvp => kvp.Value);
        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 ResourceMarkdownGenerator(TextWriter writer, ResourceSchema resourceSchema)
 {
     _writer = new MarkdownWriter(writer);
     _schema = resourceSchema;
 }
        /// <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);
        }
        private static (bool success, string failureReason, JsonSchema nameSchema) ParseNameSchema(CodeModel codeModel, Method method, ResourceSchema providerSchema, ResourceDescriptor descriptor)
        {
            // get the resource name parameter, e.g. {fooName}
            var resNameParam = descriptor.RoutingScope.Substring(descriptor.RoutingScope.LastIndexOf('/') + 1);

            if (IsPathVariable(resNameParam))
            {
                // strip the enclosing braces
                resNameParam = TrimParamBraces(resNameParam);

                // look up the type
                var param = method.Parameters.FirstOrDefault(p => p.SerializedName == resNameParam);

                if (param == null)
                {
                    return(false, $"Unable to locate parameter with name '{resNameParam}'", null);
                }

                var nameSchema = ParseType(param.ClientProperty, param.ModelType, providerSchema.Definitions, codeModel.ModelTypes);
                nameSchema.ResourceType = resNameParam;

                return(true, string.Empty, nameSchema);
            }

            if (!resNameParam.All(c => char.IsLetterOrDigit(c)))
            {
                return(false, $"Unable to process non-alphanumeric name '{resNameParam}'", null);
            }

            // Resource name is a constant; enforce it with regex.
            var pattern = descriptor.ResourceTypeSegments.Count > 1 ? $"^.*/{resNameParam}$" : $"^{resNameParam}$";

            return(true, string.Empty, new JsonSchema
            {
                JsonType = "string",
                Pattern = pattern,
            });
        }
Exemple #14
0
 private static IDictionary <string, JsonSchema> GetResourceDefinitions(ResourceSchema resourceSchema, ScopeType scopeType)
 => resourceSchema.ResourceDefinitions
 .Where(kvp => kvp.Value.Descriptor.ScopeType == scopeType)
 .ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Schema);
Exemple #15
0
        /// <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);
        }
        /// <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 ResourceMarkdownWriter(TextWriter writer, ResourceSchema resourceSchema)
 {
     _writer = new MarkdownWriter(writer);
     _schema = resourceSchema;
 }