private static JsonSchema ParsePrimaryType(Property property, PrimaryType primaryType)
        {
            JsonSchema result = new JsonSchema()
            {
                Format = primaryType.Format
            };

            switch (primaryType.Type)
            {
                case KnownPrimaryType.Boolean:
                    result.JsonType = "boolean";
                    break;

                case KnownPrimaryType.Int:
                case KnownPrimaryType.Long:
                    result.JsonType = "integer";
                    break;

                case KnownPrimaryType.Double:
                    result.JsonType = "number";
                    break;

                case KnownPrimaryType.Object:
                    result.JsonType = "object";
                    break;

                case KnownPrimaryType.DateTime:
                case KnownPrimaryType.String:
                case KnownPrimaryType.TimeSpan:
                    result.JsonType = "string";
                    break;

                default:
                    Debug.Assert(false, "Unrecognized known property type: " + primaryType.Type);
                    break;
            }

            if (property != null)
            {
                result.Description = property.Documentation;

                if (property.DefaultValue != null)
                {
                    result.AddEnum(property.DefaultValue);
                }

                if (property.Constraints.Count > 0)
                {
                    foreach (KeyValuePair<Constraint, string> entry in property.Constraints)
                    {
                        switch (entry.Key)
                        {
                            case Constraint.InclusiveMinimum:
                                Debug.Assert(result.JsonType == "integer" || result.JsonType == "number", "Expected to only find an InclusiveMinimum constraint on an integer or number property.");
                                result.Minimum = Double.Parse(entry.Value, CultureInfo.CurrentCulture);
                                break;

                            case Constraint.InclusiveMaximum:
                                Debug.Assert(result.JsonType == "integer" || result.JsonType == "number", "Expected to only find an InclusiveMaximum constraint on an integer or number property.");
                                result.Maximum = Double.Parse(entry.Value, CultureInfo.CurrentCulture);
                                break;

                            case Constraint.Pattern:
                                Debug.Assert(result.JsonType == "string", "Expected to only find a Pattern constraint on a string property.");
                                result.Pattern = entry.Value;
                                break;

                            default:
                                Debug.Fail("Unrecognized property Constraint: " + entry.Key);
                                break;
                        }
                    }
                }
            }

            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 ParseDictionaryType(Property property, DictionaryType dictionaryType, IDictionary<string, JsonSchema> definitions, IEnumerable<CompositeType> modelTypes)
        {
            JsonSchema result = new JsonSchema()
            {
                JsonType = "object",
                AdditionalProperties = ParseType(null, dictionaryType.ValueType, definitions, modelTypes)
            };

            if (property != null)
            {
                result.Description = RemovePossibleValuesFromDescription(property.Documentation);
            }

            return result;
        }
Exemple #4
0
        /// <summary>
        /// Add the provided JSON schema as an option for the oneOf property of this JSON schema.
        /// </summary>
        /// <param name="oneOfOption"></param>
        /// <returns></returns>
        public JsonSchema AddOneOf(JsonSchema oneOfOption)
        {
            if (oneOfOption == null)
            {
                throw new ArgumentNullException(nameof(oneOfOption));
            }

            if (oneOfList == null)
            {
                oneOfList = new List<JsonSchema>();
            }

            oneOfList.Add(oneOfOption);

            return this;
        }
Exemple #5
0
        private static JsonSchema Clone(JsonSchema toClone)
        {
            JsonSchema result = null;

            if (toClone != null)
            {
                result = toClone.Clone();
            }

            return result;
        }
 /// <summary>
 /// Creates a JToken hierarchy for the specified JsonSchema object.
 /// </summary>
 /// <param name="propName">The property name or null if the specified schema object is not a property.</param>
 /// <param name="jsonSchema">The schema object to transform into a JToken object.</param>
 /// <returns>A JToken object.</returns>
 private JToken JsonSchemaToJToken(string propName, JsonSchema jsonSchema)
 {
     return JsonSchemaToJTokenImpl(propName, jsonSchema, new Stack<string>());
 }
Exemple #7
0
 /// <summary>
 /// Add a new property to this JsonSchema, and then return this JsonSchema so that
 /// additional changes can be chained together.
 /// </summary>
 /// <param name="propertyName">The name of the property to add.</param>
 /// <param name="propertyDefinition">The JsonSchema definition of the property to add.</param>
 /// <returns></returns>
 public JsonSchema AddProperty(string propertyName, JsonSchema propertyDefinition)
 {
     return AddProperty(propertyName, propertyDefinition, false);
 }
        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);
                }
            }
        }
Exemple #9
0
 /// <summary>
 /// Add a new property to this JsonSchema, and then return this JsonSchema so that
 /// additional changes can be chained together.
 /// </summary>
 /// <param name="propertyName">The name of the property to add.</param>
 /// <param name="propertyDefinition">The JsonSchema definition of the property to add.</param>
 /// <returns></returns>
 public JsonSchema AddProperty(string propertyName, JsonSchema propertyDefinition)
 {
     return(AddProperty(propertyName, propertyDefinition, false));
 }
        /// <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));
        }
        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 void WriteDefinition(JsonWriter writer, JsonSchema definition)
        {
            if (definition == null)
            {
                throw new ArgumentNullException("definition");
            }

            writer.WriteStartObject();

            WriteProperty(writer, "type", definition.JsonType);
            WriteProperty(writer, "minimum", definition.Minimum);
            WriteProperty(writer, "maximum", definition.Maximum);
            WriteProperty(writer, "pattern", definition.Pattern);
            WriteProperty(writer, "minLength", definition.MinLength);
            WriteProperty(writer, "maxLength", definition.MaxLength);
            WriteStringArray(writer, "enum", definition.Enum);
            WriteDefinitionArray(writer, "oneOf", definition.OneOf);
            WriteProperty(writer, "format", definition.Format);
            WriteProperty(writer, "$ref", definition.Ref);
            WriteDefinition(writer, "items", definition.Items);
            WriteDefinition(writer, "additionalProperties", definition.AdditionalProperties);
            WriteDefinitionMap(writer, "properties", definition.Properties, addExpressionReferences: true);
            WriteStringArray(writer, "required", definition.Required);
            WriteProperty(writer, "description", definition.Description);

            writer.WriteEndObject();
        }
        public static void WriteDefinition(JsonWriter writer, string resourceName, JsonSchema definition)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            if (definition != null)
            {
                writer.WritePropertyName(resourceName);
                WriteDefinition(writer, definition);
            }
        }
Exemple #14
0
        private static JsonSchema ParseCompositeType(Property property, CompositeType compositeType, bool includeBaseModelTypeProperties, IDictionary <string, JsonSchema> definitions, IEnumerable <CompositeType> modelTypes)
        {
            string definitionName = compositeType.Name.RawValue;

            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.Name,
                                });

                                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.Name.RawValue, subPropertyDefinition, subProperty.IsRequired);
                    }
                }
            }

            JsonSchema result = new JsonSchema()
            {
                Ref = "#/definitions/" + definitionName
            };

            if (property != null)
            {
                result.Description = RemovePossibleValuesFromDescription(property.Documentation);
            }

            return(result);
        }
 private static JsonSchema Clone(JsonSchema toClone)
 => toClone?.Clone();
Exemple #16
0
        /// <summary>
        /// Add the provided definition JSON schema to this resourceh schema.
        /// </summary>
        /// <param name="definitionName">The name of the resource definition.</param>
        /// <param name="definition">The JSON schema that describes the resource.</param>
        public ResourceSchema AddDefinition(string definitionName, JsonSchema definition)
        {
            if (string.IsNullOrWhiteSpace(definitionName))
            {
                throw new ArgumentException("definitionName cannot be null or whitespace", "definitionName");
            }
            if (definition == null)
            {
                throw new ArgumentNullException("definition");
            }

            if (definitions.ContainsKey(definitionName))
            {
                throw new ArgumentException("A definition for \"" + definitionName + "\" already exists in this resource schema.", "definitionName");
            }

            definitions.Add(definitionName, definition);

            return this;
        }
        /// <summary>
        /// Returns the list of object type names that can be values for the specified schema.
        /// The returned list can be empty (i.e. the return values aren't objects).
        /// </summary>
        /// <param name="jsonSchema">The schema object for which to return the list of object type names.</param>
        /// <returns>A list of possible object names; can be an empty list.</returns>
        private IReadOnlyList<string> GetPossibleValueObjects(JsonSchema jsonSchema)
        {
            if (jsonSchema == null)
                throw new ArgumentNullException(nameof(jsonSchema));

            var values = new List<string>();

            if (jsonSchema.Ref != null)
            {
                values.Add(GetDefNameFromPointer(jsonSchema.Ref));
            }
            else if (jsonSchema.JsonType == "array" && jsonSchema.Items.Ref != null)
            {
                values.Add(GetDefNameFromPointer(jsonSchema.Items.Ref));
            }
            else if (jsonSchema.Items != null && jsonSchema.Items.OneOf != null)
            {
                foreach (var o in jsonSchema.Items.OneOf)
                {
                    var js = ResolveDefinitionRef(o.Ref);
                    if (js.JsonType == "object")
                    {
                        Debug.Assert(!string.IsNullOrEmpty(js.ResourceType));
                        values.Add(js.ResourceType);
                    }
                    else
                    {
                        Debug.Assert(false, "unhandled case");
                    }
                }
            }

            return values;
        }
Exemple #18
0
        /// <summary>
        /// Add the provided resource definition JSON schema to this resourceh schema.
        /// </summary>
        /// <param name="resourceName">The name of the resource definition.</param>
        /// <param name="resourceDefinition">The JSON schema that describes the resource.</param>
        public ResourceSchema AddResourceDefinition(string resourceName, JsonSchema resourceDefinition)
        {
            if (string.IsNullOrWhiteSpace(resourceName))
            {
                throw new ArgumentException("resourceName cannot be null or whitespace", "resourceName");
            }
            if (resourceDefinition == null)
            {
                throw new ArgumentNullException("resourceDefinition");
            }

            if (resourceDefinitions.ContainsKey(resourceName))
            {
                throw new ArgumentException("A resource definition for \"" + resourceName + "\" already exists in this resource schema.", "resourceName");
            }

            resourceDefinitions.Add(resourceName, resourceDefinition);

            return this;
        }
        /// <summary>
        /// Recursively creates a JToken hierarchy for the specified JsonSchema object (call JsonSchemaToJToken instead of this).
        /// </summary>
        /// <param name="propName">The property name or null if the specified schema object is not a property.</param>
        /// <param name="jsonSchema">The schema object to transform into a JToken object.</param>
        /// <param name="stack">Stack object used to detect cycles in the graph that would cause infinite recursion.</param>
        /// <returns>A JToken object.</returns>
        private JToken JsonSchemaToJTokenImpl(string propName, JsonSchema jsonSchema, Stack<string> stack)
        {
            if (jsonSchema == null)
                throw new ArgumentNullException(nameof(jsonSchema));

            if (jsonSchema.Ref != null)
            {
                // some definitions contain cycles which will lead to infinite recursion.
                // in this case return a JToken object with the referenced definition name.
                if (!stack.Contains(jsonSchema.Ref))
                {
                    stack.Push(jsonSchema.Ref);
                    var def = ResolveDefinitionRef(jsonSchema.Ref);
                    var result = JsonSchemaToJTokenImpl(propName, def, stack);
                    var popped = stack.Pop();
                    Debug.Assert(string.Compare(popped, jsonSchema.Ref, StringComparison.OrdinalIgnoreCase) == 0);
                    return result;
                }
                else
                {
                    var defName = GetDefNameFromPointer(jsonSchema.Ref);
                    if (propName != null)
                        return new JProperty(propName, defName);
                    else
                        return new JValue(defName);
                }
            }
            else if (jsonSchema.JsonType == "object")
            {
                var jobj = new JObject();
                if (jsonSchema.Properties != null)
                {
                    foreach (var prop in jsonSchema.Properties)
                        jobj.Add(JsonSchemaToJTokenImpl(prop.Key, prop.Value, stack));
                }

                if (propName != null)
                    return new JProperty(propName, jobj);
                else
                    return jobj;
            }
            else if (jsonSchema.JsonType == "array")
            {
                Debug.Assert(jsonSchema.Items != null);
                var jarr = new JArray();
                jarr.Add(JsonSchemaToJTokenImpl(null, jsonSchema.Items, stack));

                return new JProperty(propName, jarr);
            }
            else
            {
                string val = null;
                if (jsonSchema.Enum != null && jsonSchema.Enum.Count == 1)
                    val = jsonSchema.Enum[0];
                else
                    val = jsonSchema.JsonType;

                if (propName != null)
                    return new JProperty(propName, val);
                else
                    return new JValue(val);
            }
        }
Exemple #20
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) ||
                    !resourceMethodPrefixRx.IsMatch(method.Url) ||
                    !method.Url.EndsWith("}", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                string afterPrefix = method.Url.Substring(resourceMethodPrefixRx.Match(method.Url).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 (property.SerializedName != null && !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('/', '_');
                    if (string.IsNullOrEmpty(resourcePropertyName))
                    {
                        // System.Console.Error.WriteLine($"Skipping '{method.Url}' -- no resource type.");
                        continue;
                    }

                    if (resourceSchema.ResourceDefinitions.ContainsKey(resourcePropertyName))
                    {
                        // System.Console.Error.WriteLine($"Uh oh. '{resourcePropertyName}' -- duplicated?");
                        continue;
                    }

                    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);
        }
Exemple #21
0
        /// <summary>
        /// Add a new property to this JsonSchema, and then return this JsonSchema so that
        /// additional changes can be chained together.
        /// </summary>
        /// <param name="propertyName">The name of the property to add.</param>
        /// <param name="propertyDefinition">The JsonSchema definition of the property to add.</param>
        /// <param name="isRequired">Whether this property is required or not.</param>
        /// <returns></returns>
        public JsonSchema AddProperty(string propertyName, JsonSchema propertyDefinition, bool isRequired)
        {
            if (string.IsNullOrWhiteSpace(propertyName))
            {
                throw new ArgumentException("propertyName cannot be null or whitespace", "propertyName");
            }
            if (propertyDefinition == null)
            {
                throw new ArgumentNullException("propertyDefinition");
            }
            
            if (properties == null)
            {
                properties = new Dictionary<string, JsonSchema>();
            }

            if (properties.ContainsKey(propertyName))
            {
                throw new ArgumentException("A property with the name \"" + propertyName + "\" already exists in this JSONSchema", "propertyName");
            }

            properties[propertyName] = propertyDefinition;

            if (isRequired)
            {
                AddRequired(propertyName);
            }

            return this;
        }
Exemple #22
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);
        }
Exemple #23
0
 /// <summary>
 /// Create a new JsonSchema that is an exact copy of this one.
 /// </summary>
 /// <returns></returns>
 public JsonSchema Clone()
 {
     JsonSchema result = new JsonSchema();
     result.Ref = Ref;
     result.Items = Clone(Items);
     result.Description = Description;
     result.JsonType = JsonType;
     result.AdditionalProperties = Clone(AdditionalProperties);
     result.Minimum = Minimum;
     result.Maximum = Maximum;
     result.Pattern = Pattern;
     result.enumList = Clone(Enum);
     result.properties = Clone(Properties);
     result.requiredList = Clone(Required);
     result.oneOfList = Clone(OneOf);
     return result;
 }
Exemple #24
0
        private static JsonSchema ParsePrimaryType(Property property, PrimaryType primaryType)
        {
            JsonSchema result = new JsonSchema()
            {
                Format = primaryType.Format
            };

            switch (primaryType.KnownPrimaryType)
            {
            case KnownPrimaryType.Boolean:
                result.JsonType = "boolean";
                break;

            case KnownPrimaryType.Int:
            case KnownPrimaryType.Long:
                result.JsonType = "integer";
                break;

            case KnownPrimaryType.Double:
            case KnownPrimaryType.Decimal:
                result.JsonType = "number";
                break;

            case KnownPrimaryType.Object:
                result.JsonType = "object";
                break;

            case KnownPrimaryType.ByteArray:
                result.JsonType = "array";
                result.Items    = new JsonSchema()
                {
                    JsonType = "integer"
                };
                break;

            case KnownPrimaryType.Base64Url:
            case KnownPrimaryType.Date:
            case KnownPrimaryType.DateTime:
            case KnownPrimaryType.String:
            case KnownPrimaryType.TimeSpan:
                result.JsonType = "string";
                break;

            case KnownPrimaryType.Uuid:
                result.JsonType = "string";
                result.Pattern  = @"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$";
                break;

            default:
                Debug.Assert(false, "Unrecognized known property type: " + primaryType.KnownPrimaryType);
                break;
            }

            if (property != null)
            {
                result.Description = property.Documentation;

                if (property.DefaultValue != null)
                {
                    if (property.IsConstant)
                    {
                        result.AddEnum(property.DefaultValue);
                    }
                    else
                    {
                        result.Default = property.DefaultValue;
                    }
                }

                if (property.Constraints.Count > 0)
                {
                    foreach (KeyValuePair <Constraint, string> entry in property.Constraints)
                    {
                        switch (entry.Key)
                        {
                        case Constraint.InclusiveMinimum:
                            Debug.Assert(result.JsonType == "integer" || result.JsonType == "number", "Expected to only find an InclusiveMinimum constraint on an integer or number property.");
                            result.Minimum = Double.Parse(entry.Value, CultureInfo.CurrentCulture);
                            break;

                        case Constraint.InclusiveMaximum:
                            Debug.Assert(result.JsonType == "integer" || result.JsonType == "number", "Expected to only find an InclusiveMaximum constraint on an integer or number property.");
                            result.Maximum = Double.Parse(entry.Value, CultureInfo.CurrentCulture);
                            break;

                        case Constraint.Pattern:
                            Debug.Assert(result.JsonType == "string", "Expected to only find a Pattern constraint on a string property.");
                            result.Pattern = entry.Value;
                            break;

                        case Constraint.MinLength:
                            Debug.Assert(result.JsonType == "string" || result.JsonType == "array", "Expected to only find a MinLength constraint on a string or array property.");
                            result.MinLength = Double.Parse(entry.Value, CultureInfo.CurrentCulture);
                            break;

                        case Constraint.MaxLength:
                            Debug.Assert(result.JsonType == "string" || result.JsonType == "array", "Expected to only find a MaxLength constraint on a string or array property.");
                            result.MaxLength = Double.Parse(entry.Value, CultureInfo.CurrentCulture);
                            break;

                        default:
                            Debug.Fail("Unrecognized property Constraint: " + entry.Key);
                            break;
                        }
                    }
                }
            }

            return(result);
        }
Exemple #25
0
 public static JsonSchema CreateStringEnum(string enumValue, params string[] extraEnumValues)
 {
     JsonSchema result = new JsonSchema() { JsonType = "string" };
     result.AddEnum(enumValue);
     if (extraEnumValues != null)
     {
         foreach (string extraEnumValue in extraEnumValues)
         {
             result.AddEnum(extraEnumValue);
         }
     }
     return result;
 }
        /// <summary>
        /// Creates a markdown table that describes the specified JSON schema.
        /// </summary>
        /// <param name="header">The markdown header to place before the table.</param>
        /// <param name="jsonSchema">The JSON schema that is to be described by the table.</param>
        /// <param name="tableQueue">A queue containing the JSON schemas to be converted to table format.</param>
        /// <returns>A formatted table with sub-header.</returns>
        private MarkdownElement CreateTable(string header, JsonSchema jsonSchema)
        {
            if (string.IsNullOrEmpty(header))
                throw new ArgumentException(nameof(header));
            if (jsonSchema == null)
                throw new ArgumentNullException(nameof(jsonSchema));

            Debug.Assert(jsonSchema.Properties != null);

            // write the table header
            var paragraph = new Paragraph();
            paragraph.Append(new Anchor(header));
            paragraph.Append(Environment.NewLine);
            paragraph.Append(new Header(2, "{0} object", header));
            paragraph.Append(Environment.NewLine);

            var table = new Table(new[] { "Name", "Required", "Value" });

            // add a table row for each property in the schema
            foreach (var prop in jsonSchema.Properties)
            {
                // build the content that will appear in the value column.  it looks
                // something like the following (each entry is separated by a line break):

                // type name
                // values (might not be present depending on the type)
                // description

                // type name
                var sb = new StringBuilder();
                sb.Append(GetValueTypeName(prop.Value));
                sb.Append(new LineBreak());

                // required/optional
                var required = RequiredFalse;
                if (jsonSchema.Required != null && jsonSchema.Required.Contains(prop.Key))
                    required = RequiredTrue;

                // valuess
                var values = GetPossibleValues(prop.Value);
                if (values.Count > 0)
                {
                    if (values.Count == 1)
                        sb.Append(new Strong(values[0]));
                    else if (values.Count == 2)
                        sb.Append(string.Join(" or ", values.Select(v => { return new Strong(v); })));
                    else
                        sb.Append(string.Join(", ", values.Select(v => { return new Strong(v); })));
                    sb.Append(new LineBreak());
                }
                else if ((values = GetPossibleValueObjects(prop.Value)).Count > 0)
                {
                    if (values.Count == 1)
                        sb.AppendFormat(new InlineLink(values[0], "{0} object", values[0]).ToString());
                    else
                        sb.Append(string.Join(new LineBreak().ToString(), values.Select(
                            v =>
                            {
                                return new InlineLink(v, "{0} object", v).ToString();
                            })));

                    sb.Append(new LineBreak());
                }

                // description
                if (!string.IsNullOrWhiteSpace(prop.Value.Description))
                {
                    sb.Append(new LineBreak());
                    sb.Append(prop.Value.Description);
                }

                table.AddRow(new[] { prop.Key, required, sb.ToString() });
            }

            paragraph.Append(table);
            return paragraph;
        }
        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>
        /// Returns the name of the specified JSON type.
        /// </summary>
        /// <param name="jsonSchema">The schema object for which to return the type name.</param>
        /// <returns>The name of the type contained in the specified schema.</returns>
        private string GetValueTypeName(JsonSchema jsonSchema)
        {
            if (jsonSchema == null)
                throw new ArgumentNullException(nameof(jsonSchema));

            if (jsonSchema.Enum != null)
                return "enum";
            else if (jsonSchema.Ref != null)
                return "object";
            else
                return jsonSchema.JsonType;
        }
        private static JsonSchema ParseEnumType(Property property, EnumType enumType)
        {
            JsonSchema result = new JsonSchema()
            {
                JsonType = "string"
            };

            foreach (EnumValue enumValue in enumType.Values)
            {
                result.AddEnum(enumValue.Name);
            }

            if (property != null)
            {
                result.Description = RemovePossibleValuesFromDescription(property.Documentation);
            }

            return result;
        }
        /// <summary>
        /// Returns the list of values for the specified schema.
        /// E.g. for enums the list would contain all of the enum values.
        /// The returned list can be empty (typically for integral types).
        /// </summary>
        /// <param name="jsonSchema">The schema object for which to return the list of values.</param>
        /// <returns>A list of possible values; can be an empty list.</returns>
        private IReadOnlyList<string> GetPossibleValues(JsonSchema jsonSchema)
        {
            if (jsonSchema == null)
                throw new ArgumentNullException(nameof(jsonSchema));

            var values = new List<string>();

            if (jsonSchema.Enum != null)
            {
                foreach (var e in jsonSchema.Enum)
                    values.Add(e);
            }
            else if (jsonSchema.Items != null && jsonSchema.Items.Ref == null)
            {
                if (jsonSchema.Items.Enum != null)
                {
                    foreach (var e in jsonSchema.Items.Enum)
                        values.Add(e);
                }
                else if (jsonSchema.Items.JsonType != null)
                {
                    values.Add(jsonSchema.Items.JsonType);
                }
            }
            else if (jsonSchema.Format == "uuid")
            {
                values.Add("globally unique identifier");
            }

            return values;
        }
        private static JsonSchema ParseSequenceType(Property property, SequenceType sequenceType, IDictionary<string, JsonSchema> definitions, IEnumerable<CompositeType> modelTypes)
        {
            JsonSchema result = new JsonSchema()
            {
                JsonType = "array",
                Items = ParseType(null, sequenceType.ElementType, definitions, modelTypes)
            };

            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.RawValue;

            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.ModelType, definitions, modelTypes);
                    if (subPropertyDefinition != null)
                    {
                        definition.AddProperty(subProperty.Name.RawValue, 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.ModelType, definitions, modelTypes);
                                    if (subTypePropertyDefinition != null)
                                    {
                                        definition.AddProperty(subTypeProperty.Name.RawValue, 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.RawValue)),
                                compositeType.Name.RawValue);
                            throw new NotSupportedException(errorMessage);
                        }
                    }
                }
            }

            JsonSchema result = new JsonSchema()
            {
                Ref = "#/definitions/" + definitionName
            };

            if (property != null)
            {
                result.Description = RemovePossibleValuesFromDescription(property.Documentation);
            }

            return(result);
        }