private Dictionary <string, ParameterData> GetParameterMetadata(string parameterPath, JsonPathFinder parameter, Dictionary <string, RuntimeTypeData> definitionsCache, Dictionary <string, Dictionary <string, ParameterData> > parameterCache, JsonPathFinder rootDoc) { if (parameterCache.ContainsKey(parameterPath)) { // We've already transformed this parameter into one or more parameters and found the correct module name return(parameterCache[parameterPath]); } Dictionary <string, ParameterData> ret = new Dictionary <string, ParameterData>(); parameterCache[parameterPath] = ret; // 1. Check if it's a ref JsonPathFinder refNode = parameter.Find(new JsonQueryBuilder().Property("$ref")).SingleOrDefault(); if (refNode != null) { // A direct ref should reference a parameter object string refParameterPath = refNode.GetValue <string>(); return(GetParameterMetadata(refParameterPath.ToLowerInvariant(), rootDoc.Find(new JsonQueryBuilder(refParameterPath)).Single(), definitionsCache, parameterCache, rootDoc)); } else { // 2. Get the name and jsonName string jsonName = String.Empty; string clientName = String.Empty; JsonPathFinder jsonNameNode = parameter.Find(new JsonQueryBuilder().Property("name")).SingleOrDefault(); JsonPathFinder clientNameNode = parameter.Find(new JsonQueryBuilder().Property("x-ms-client-name")).SingleOrDefault(); if (jsonNameNode != null) { jsonName = jsonNameNode.GetValue <string>().ToLowerInvariant(); } if (clientNameNode != null) { clientName = clientNameNode.GetValue <string>().ToLowerInvariant(); } // 2. Check if it's a flatten JsonPathFinder flattenNode = parameter.Find(new JsonQueryBuilder().Property("x-ms-client-flatten")).SingleOrDefault(); JsonPathFinder schemaRefNode = parameter.Find(new JsonQueryBuilder().RecursiveDescent().Property("$ref")).SingleOrDefault(); if (flattenNode != null) { // Get the ref node under the schema node - using recursive descent from the parameter node should work // This should be a definitions object string definitionPath = schemaRefNode.GetValue <string>(); RuntimeTypeData definitionData = definitionsCache[definitionPath]; // Don't think there will be a flatten of an object that's just an array? foreach (string propertyName in definitionData.Properties.Keys) { ParameterData data = definitionData.Properties[propertyName]; if (!String.IsNullOrEmpty(data.Name)) { ret[data.Name] = data; } else { ret[data.JsonName] = data; } } } else { // 3. Just a regular parameter - check if it defines a definition as the type ParameterData data = new ParameterData(); data.Name = clientName; data.JsonName = jsonName; if (schemaRefNode != null) { string definitionPath = schemaRefNode.GetValue <string>().ToLowerInvariant(); data.Type = definitionsCache[definitionPath]; } else { // Type with no properties, and the Type itself will be filled in later data.Type = new RuntimeTypeData(); } if (!String.IsNullOrEmpty(clientName)) { ret[clientName] = data; } else { ret[jsonName] = data; } } } return(ret); }
private RuntimeTypeData GetRuntimeTypeDataFromDefinition(string definitionName, JsonPathFinder definitionNode, Dictionary <string, RuntimeTypeData> cache, JsonPathFinder rootDoc) { // 0. Check if the type already exists if (cache.ContainsKey(definitionName)) { return(cache[definitionName]); } RuntimeTypeData data = new RuntimeTypeData(); cache[definitionName] = data; // 1. Get properties node JsonPathFinder propertiesNode = definitionNode.Find(new JsonQueryBuilder().Property("properties")).SingleOrDefault(); if (propertiesNode != null) { // 2. Read flat properties foreach (JsonPathFinder propertyContainerNode in propertiesNode.Children()) { JsonPathFinder propertyNode = propertyContainerNode.OpenContainer(); // First things first - check if the property is renamed JsonPathFinder clientNameNode = propertyNode.Find(new JsonQueryBuilder().Property("x-ms-client-name")).SingleOrDefault(); string clientName = String.Empty; if (clientNameNode != null) { clientName = clientNameNode.GetValue <string>().ToLowerInvariant(); } string jsonName = propertyNode.Key.ToLowerInvariant(); string rawJsonName = propertyNode.Key; // 5 cases: // 1: Property contains a $ref node AND property contains an x-ms-client-flatten node // 2: Property contains only a $ref node // 3: Property is of type object and contains additionalProperties (not sure how to handle this yet) // 4: Property is of type array (decode the individual items type) // 5: Property is a simple property JsonPathFinder refNode = propertyNode.Find(new JsonQueryBuilder().Property("$ref")).SingleOrDefault(); JsonPathFinder flattenNode = propertyNode.Find(new JsonQueryBuilder().Property("x-ms-client-flatten")).SingleOrDefault(); JsonPathFinder typeNode = propertyNode.Find(new JsonQueryBuilder().Property("type")).SingleOrDefault(); JsonPathFinder additionalPropertiesNode = propertyNode.Find(new JsonQueryBuilder().Property("additionalProperties")).SingleOrDefault(); JsonPathFinder arrayItemsRefNode = propertyNode.Find(new JsonQueryBuilder().Property("items").Property("$ref")).SingleOrDefault(); JsonPathFinder propertiesSubNode = propertyNode.Find(new JsonQueryBuilder().Property("properties")).SingleOrDefault(); if (refNode != null) { JsonPathFinder subDefinition = rootDoc.Find(new JsonQueryBuilder(refNode.GetValue <string>())).Single(); RuntimeTypeData referencedTypeData = GetRuntimeTypeDataFromDefinition(subDefinition.Path, subDefinition, cache, rootDoc); if (flattenNode != null && flattenNode.GetValue <bool>()) { // Push the properties of the referenced type to this type // If it's flattened, the jsonName should actually be flatJsonName foreach (string propertyName in referencedTypeData.Properties.Keys) { data.Properties[propertyName] = referencedTypeData.Properties[propertyName].Clone(); data.Properties[propertyName].SubNodeName = "properties"; } } else { // The referenced type is simply the type of this property data.Properties[jsonName] = new ParameterData() { Name = clientName, JsonName = jsonName, RawJsonName = rawJsonName, Type = referencedTypeData }; } } else if (typeNode != null && typeNode.GetValue <string>().ToLowerInvariant().Equals("object") && additionalPropertiesNode != null) { // No converter required, this will probably be a Dictionary<string, something> data.Properties[jsonName] = new ParameterData() { Name = clientName, JsonName = jsonName, RawJsonName = rawJsonName }; // TODO: Support complex object as the additionalProperties type. Don't have a good example if this yet... } else if (typeNode != null && typeNode.GetValue <string>().ToLowerInvariant().Equals("array")) { // No converter required data.Properties[jsonName] = new ParameterData() { Name = clientName, JsonName = jsonName, RawJsonName = rawJsonName }; // When this is an array, we should add the type of the items in the array so that a converter is registered later if (arrayItemsRefNode != null) { JsonPathFinder subDefinition = rootDoc.Find(new JsonQueryBuilder(arrayItemsRefNode.GetValue <string>())).Single(); data.Properties[jsonName].Type = new RuntimeTypeData(); data.Properties[jsonName].Type.CollectionTypes.Add(GetRuntimeTypeDataFromDefinition(subDefinition.Path, subDefinition, cache, rootDoc)); } } else if (propertiesSubNode != null) { // Not sure what to do with definition name yet // The actual Type of the anonymous type will be filled in by postprocessing // TODO: The properties of the subtype might be prefixed with "properties." data.Properties[jsonName] = new ParameterData() { Name = clientName, JsonName = jsonName, RawJsonName = rawJsonName, Type = GetRuntimeTypeDataFromDefinition(Guid.NewGuid().ToString(), propertyNode, cache, rootDoc) }; } else { // The last case should be the case of a simple property - assign the name and jsonName and let the type be inferred later data.Properties[jsonName] = new ParameterData() { Name = clientName, JsonName = jsonName, RawJsonName = rawJsonName, Type = new RuntimeTypeData() }; } } } // 3. Read allOf node JsonPathFinder allOfNode = definitionNode.Find(new JsonQueryBuilder().Property("allOf")).SingleOrDefault(); if (allOfNode != null) { // Get all $ref's foreach (JsonPathFinder refNode in allOfNode.Find(new JsonQueryBuilder().RecursiveDescent().Property("$ref"))) { JsonPathFinder subDefinition = rootDoc.Find(new JsonQueryBuilder(refNode.GetValue <string>())).Single(); RuntimeTypeData referencedTypeData = GetRuntimeTypeDataFromDefinition(subDefinition.Path, subDefinition, cache, rootDoc); // Push the properties of the referenced type to this type foreach (string propertyName in referencedTypeData.Properties.Keys) { data.Properties[propertyName] = referencedTypeData.Properties[propertyName]; } } } return(data); }
private OperationData ParseOperationForMetadata(JsonPathFinder path, Dictionary <string, RuntimeTypeData> definitionsCache, Dictionary <string, Dictionary <string, ParameterData> > parameterCache, JsonPathFinder rootDoc) { JsonPathFinder parametersNode = path.Find(new JsonQueryBuilder().Property("parameters")).SingleOrDefault(); JsonPathFinder operationIdNode = path.Find(new JsonQueryBuilder().Property("operationId")).SingleOrDefault(); if (operationIdNode == null) { return(null); } string operationId = operationIdNode.GetValue <string>(); if (!this.Operations.ContainsKey(operationId.ToLowerInvariant())) { Log(String.Format(CultureInfo.InvariantCulture, "Skipping operation '{0}' because no matching command was found.", operationId)); return(null); } OperationData operationData = this.Operations[operationId.ToLowerInvariant()]; Log(String.Format(CultureInfo.InvariantCulture, "Loading metadata for operation '{0}' and command '{1}'", operationId, operationData.Command)); if (parametersNode != null) { foreach (JsonPathFinder parameterNode in parametersNode.Children()) { Dictionary <string, ParameterData> parameters = GetParameterMetadata(parameterNode.Path, parameterNode, definitionsCache, parameterCache, rootDoc); foreach (string parameterName in parameters.Keys) { if (operationData.Parameters.ContainsKey(parameterName.ToLowerInvariant())) { Log(String.Format(CultureInfo.InvariantCulture, "Found parameter with name '{0}' and JSON name '{1}'", parameterName, parameters[parameterName].JsonName)); operationData.Parameters[parameterName.ToLowerInvariant()].MergeWith(parameters[parameterName]); } } } } // Find the first response in the [200-299] range, as that's all PSSwagger supports right now JsonPathFinder responsesNode = path.Find(new JsonQueryBuilder().Property("responses")).SingleOrDefault(); string responseDefinitionPath = null; if (responsesNode != null && operationData.ResponseType != null) { foreach (JsonPathFinder response in responsesNode.Children()) { int statusCode; if (Int32.TryParse(response.Key, out statusCode) && statusCode >= 200 && statusCode <= 299) { // For now we only support ref'd schema - need to fix that JsonPathFinder schemaRefNode = response.Find(new JsonQueryBuilder().RecursiveDescent().Property("$ref")).SingleOrDefault(); if (schemaRefNode != null) { responseDefinitionPath = schemaRefNode.GetValue <string>(); string definitionPathCaseInsensitive = responseDefinitionPath.ToLowerInvariant(); if (definitionsCache.ContainsKey(definitionPathCaseInsensitive)) { operationData.ResponseType.SpecificationData.Add(definitionsCache[definitionPathCaseInsensitive]); break; } } } } } // Check for x-ms-pageable and set the page type if a response type exists JsonPathFinder pageableExtension = path.Find(new JsonQueryBuilder().Property("x-ms-pageable")).SingleOrDefault(); if (pageableExtension != null && operationData.ResponseType != null) { string nextLinkJsonName = "nextLink"; string itemsJsonName = "value"; JsonPathFinder nextLinkName = pageableExtension.Find(new JsonQueryBuilder().RecursiveDescent().Property("nextLinkName")).SingleOrDefault(); JsonPathFinder itemName = pageableExtension.Find(new JsonQueryBuilder().RecursiveDescent().Property("itemName")).SingleOrDefault(); if (nextLinkName != null) { nextLinkJsonName = nextLinkName.GetValue <string>(); } if (itemName != null) { itemsJsonName = itemName.GetValue <string>(); } // Assuming the module is already loaded, find the correct .NET type for the Page foreach (Type candidateType in AppDomain.CurrentDomain.GetAssemblies().SelectMany(asm => asm.GetTypes())) { PropertyInfo itemsProperty = candidateType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(pi => pi.Name.Equals("Items")).FirstOrDefault(); PropertyInfo nextPageLinkProperty = candidateType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(pi => pi.Name.Equals("NextPageLink")).FirstOrDefault(); bool candidate = true; if (itemsProperty != null) { JsonPropertyAttribute jsonPropertyAttribute = (JsonPropertyAttribute)itemsProperty.GetCustomAttribute(typeof(JsonPropertyAttribute)); if (jsonPropertyAttribute == null || !jsonPropertyAttribute.PropertyName.Equals(itemsJsonName, StringComparison.OrdinalIgnoreCase)) { candidate = false; } } else { candidate = false; } if (candidate && nextPageLinkProperty != null) { JsonPropertyAttribute jsonPropertyAttribute = (JsonPropertyAttribute)nextPageLinkProperty.GetCustomAttribute(typeof(JsonPropertyAttribute)); if (jsonPropertyAttribute == null || !jsonPropertyAttribute.PropertyName.Equals(nextLinkJsonName, StringComparison.OrdinalIgnoreCase)) { candidate = false; } } else { candidate = false; } if (candidate) { operationData.ResponseType.PageType = candidateType; } } // Find the correct specification data. If x-ms-pageable is used, the referenced output type is probably an array of items // And PSSwagger will output the individual item type rather than the array type // Set the output type of the individual item type if (!String.IsNullOrEmpty(responseDefinitionPath)) { JsonPathFinder responseDefinitionNode = rootDoc.Find(new JsonQueryBuilder(responseDefinitionPath)).SingleOrDefault(); if (responseDefinitionNode != null) { JsonPathFinder itemType = responseDefinitionNode.Find(new JsonQueryBuilder().RecursiveDescent().Property("$ref")).FirstOrDefault(); if (itemType != null) { operationData.ResponseType.SpecificationData.Add(definitionsCache[itemType.GetValue <string>().ToLowerInvariant()]); } } } } return(operationData); }