private static JsonSchema Clone(JsonSchema toClone) { JsonSchema result = null; if (toClone != null) { result = toClone.Clone(); } 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; }
/// <summary> /// Parse a ResourceSchemaModel from the provided ServiceClient. /// </summary> /// <param name="serviceClient"></param> /// <returns></returns> public static IDictionary <string, ResourceSchema> Parse(CodeModel serviceClient, string version) { if (serviceClient == null) { throw new ArgumentNullException(nameof(serviceClient)); } Dictionary <string, ResourceSchema> resourceSchemas = new Dictionary <string, ResourceSchema>(); foreach (Method method in serviceClient.Methods.Where(method => method.Parameters.FirstOrDefault(p => p.SerializedName == "api-version")?.DefaultValue.Value == version || version == serviceClient.ApiVersion)) { if (method.HttpMethod != HttpMethod.Put || string.IsNullOrWhiteSpace(method.Url) || !method.Url.Value.StartsWith(resourceMethodPrefix, StringComparison.OrdinalIgnoreCase) || !method.Url.Value.EndsWith("}", StringComparison.OrdinalIgnoreCase)) { continue; } string afterPrefix = method.Url.Value.Substring(resourceMethodPrefix.Length); int forwardSlashIndexAfterProvider = afterPrefix.IndexOf('/'); string resourceProvider = afterPrefix.Substring(0, forwardSlashIndexAfterProvider); if (IsPathVariable(resourceProvider)) { // If the resourceProvider is a path variable, such as {someValue}, then this // is not a create resource method. Skip it. continue; } // extract API version string apiVersion = serviceClient.ApiVersion.Else(method.Parameters.FirstOrDefault(p => p.SerializedName == "api-version")?.DefaultValue); if (string.IsNullOrWhiteSpace(apiVersion)) { throw new ArgumentException("No API version is provided in the swagger document or the method."); } ResourceSchema resourceSchema; if (!resourceSchemas.ContainsKey(resourceProvider)) { resourceSchema = new ResourceSchema(); resourceSchema.Id = string.Format(CultureInfo.InvariantCulture, "https://schema.management.azure.com/schemas/{0}/{1}.json#", apiVersion, resourceProvider); resourceSchema.Title = resourceProvider; resourceSchema.Description = resourceProvider.Replace('.', ' ') + " Resource Types"; resourceSchema.Schema = "http://json-schema.org/draft-04/schema#"; resourceSchemas.Add(resourceProvider, resourceSchema); } else { resourceSchema = resourceSchemas[resourceProvider]; } string methodUrlPathAfterProvider = afterPrefix.Substring(forwardSlashIndexAfterProvider + 1); string[] resourceTypes = ParseResourceTypes(resourceProvider, methodUrlPathAfterProvider, method); foreach (string resourceType in resourceTypes) { JsonSchema resourceDefinition = new JsonSchema(); resourceDefinition.JsonType = "object"; resourceDefinition.ResourceType = resourceType; // get the resource name parameter, e.g. {fooName} var resNameParam = methodUrlPathAfterProvider.Substring(methodUrlPathAfterProvider.LastIndexOf('/') + 1); if (IsPathVariable(resNameParam)) { // strip the enclosing braces resNameParam = resNameParam.Trim(new[] { '{', '}' }); // look up the type var param = method.Parameters.Where(p => p.SerializedName == resNameParam).FirstOrDefault(); if (param != null) { // create a schema for it var nameParamSchema = ParseType(param.ClientProperty, param.ModelType, resourceSchema.Definitions, serviceClient.ModelTypes); nameParamSchema.ResourceType = resNameParam; // add it as the name property resourceDefinition.AddProperty("name", nameParamSchema, true); } } resourceDefinition.AddProperty("type", JsonSchema.CreateStringEnum(resourceType), true); resourceDefinition.AddProperty("apiVersion", JsonSchema.CreateStringEnum(apiVersion), true); if (method.Body != null) { CompositeType body = method.Body.ModelType as CompositeType; // Debug.Assert(body != null, "The create resource method's body must be a CompositeType and cannot be null."); if (body != null) { foreach (Property property in body.ComposedProperties) { if (!resourceDefinition.Properties.Keys.Contains(property.SerializedName)) { JsonSchema propertyDefinition = ParseType(property, property.ModelType, resourceSchema.Definitions, serviceClient.ModelTypes); if (propertyDefinition != null) { resourceDefinition.AddProperty(property.SerializedName, propertyDefinition, property.IsRequired || property.SerializedName == "properties"); } } } } } resourceDefinition.Description = resourceType; string resourcePropertyName = resourceType.Substring(resourceProvider.Length + 1).Replace('/', '_'); Debug.Assert(!resourceSchema.ResourceDefinitions.ContainsKey(resourcePropertyName)); resourceSchema.AddResourceDefinition(resourcePropertyName, resourceDefinition); } } // This loop adds child resource schemas to their parent resource schemas. We can't do // this until we're done adding all resources as top level resources, though, because // it's possible that we will parse a child resource before we parse the parent // resource. foreach (ResourceSchema resourceSchema in resourceSchemas.Values) { // By iterating over the reverse order of the defined resource definitions, I'm // counting on the resource definitions being in sorted order. That way I'm // guaranteed to visit child resource definitions before I visit their parent // resource definitions. By doing this, I've guaranteed that grandchildren resource // definitions will be added to their grandparent (and beyond) ancestor // resource definitions. foreach (string resourcePropertyName in resourceSchema.ResourceDefinitions.Keys.Reverse()) { JsonSchema resourceDefinition = resourceSchema.ResourceDefinitions[resourcePropertyName]; string resourceType = resourceDefinition.ResourceType; int lastSlashIndex = resourceType.LastIndexOf('/'); string parentResourceType = resourceType.Substring(0, lastSlashIndex); JsonSchema parentResourceDefinition = resourceSchema.GetResourceDefinitionByResourceType(parentResourceType); if (parentResourceDefinition != null) { string childResourceType = resourceType.Substring(lastSlashIndex + 1); JsonSchema childResourceDefinition = resourceDefinition.Clone(); childResourceDefinition.ResourceType = childResourceType; string childResourceDefinitionPropertyName = string.Join("_", resourcePropertyName, "childResource"); resourceSchema.AddDefinition(childResourceDefinitionPropertyName, childResourceDefinition); JsonSchema childResources; if (parentResourceDefinition.Properties.ContainsKey("resources")) { childResources = parentResourceDefinition.Properties["resources"]; } else { childResources = new JsonSchema() { JsonType = "array", Items = new JsonSchema() }; parentResourceDefinition.AddProperty("resources", childResources); } childResources.Items.AddOneOf(new JsonSchema() { Ref = "#/definitions/" + childResourceDefinitionPropertyName, }); } } } return(resourceSchemas); }
private static void WriteDefinitionMap(JsonWriter writer, string definitionMapName, IDictionary <string, JsonSchema> definitionMap, bool sortDefinitions = false, bool addExpressionReferences = false) { if (definitionMap != null && definitionMap.Count > 0) { writer.WritePropertyName(definitionMapName); writer.WriteStartObject(); IEnumerable <string> definitionNames = definitionMap.Keys; if (sortDefinitions) { definitionNames = definitionNames.OrderBy(key => key); } foreach (string definitionName in definitionNames) { JsonSchema definition = definitionMap[definitionName]; bool shouldAddExpressionReference = addExpressionReferences; if (shouldAddExpressionReference) { switch (definition.JsonType) { case "object": shouldAddExpressionReference = !definition.IsEmpty(); break; case "string": shouldAddExpressionReference = (definition.Enum != null && definition.Enum.Any() && definitionName != "type" && definitionName != "apiVersion" // api versions are templated in some templates. No idea why. ) || definition.Pattern != null; break; case "array": shouldAddExpressionReference = definitionName != "resources"; break; default: break; } } if (!shouldAddExpressionReference) { WriteDefinition(writer, definitionName, definition); } else { string definitionDescription = null; writer.WritePropertyName(definitionName); writer.WriteStartObject(); writer.WritePropertyName(definition.JsonType == "object" && definition.IsEmpty() ? "anyOf" : "oneOf"); // hack, until MultiType thing is enforced across the specs repo! writer.WriteStartArray(); if (definition.Description != null) { definitionDescription = definition.Description; definition = definition.Clone(); definition.Description = null; } WriteDefinition(writer, definition); WriteDefinition(writer, new JsonSchema() { Ref = "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" }); writer.WriteEndArray(); WriteProperty(writer, "description", definitionDescription); writer.WriteEndObject(); } } writer.WriteEndObject(); } }
private static JsonSchema Clone(JsonSchema toClone) { JsonSchema result = null; if (toClone != null) { result = toClone.Clone(); } return result; }
private static JsonSchema Clone(JsonSchema toClone) => toClone?.Clone();
private static void WriteDefinitionMap(JsonWriter writer, string definitionMapName, IDictionary <string, JsonSchema> definitionMap, bool sortDefinitions = false, bool addExpressionReferences = false) { if (definitionMap != null && definitionMap.Count > 0) { writer.WritePropertyName(definitionMapName); writer.WriteStartObject(); IEnumerable <string> definitionNames = definitionMap.Keys; if (sortDefinitions) { definitionNames = definitionNames.OrderBy(key => key); } foreach (string definitionName in definitionNames) { JsonSchema definition = definitionMap[definitionName]; bool shouldAddExpressionReference = addExpressionReferences && (definition.JsonType != "string" || (definition.Enum != null && definition.Enum.Count() > 0 && definitionName != "type" && definitionName != "apiVersion") || (definition.Pattern != null)) && (definition.JsonType != "array" || (definitionName != "resources")); if (!shouldAddExpressionReference) { WriteDefinition(writer, definitionName, definition); } else { string definitionDescription = null; writer.WritePropertyName(definitionName); writer.WriteStartObject(); writer.WritePropertyName("oneOf"); writer.WriteStartArray(); if (definition.Description != null) { definitionDescription = definition.Description; definition = definition.Clone(); definition.Description = null; } WriteDefinition(writer, definition); WriteDefinition(writer, new JsonSchema() { Ref = "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" }); writer.WriteEndArray(); WriteProperty(writer, "description", definitionDescription); writer.WriteEndObject(); } } writer.WriteEndObject(); } }