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; }
/// <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; }
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>()); }
/// <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); } } }
/// <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); } }
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();
/// <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; }
/// <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); } }
/// <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); }
/// <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; }
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); }
/// <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; }
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); }
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); }