Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support parameters in path, to be applicable for all the operations under this path #413

Merged
merged 3 commits into from
Jun 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ namespace Microsoft.DocAsCode.Build.RestApi.Swagger
[Serializable]
public class OperationObject
{
/// <summary>
/// Docfx Added: Operation name, e.g. get, put, post, delete, options, head, patch
/// </summary>
[YamlMember(Alias = "name")]
[JsonProperty("name")]
public string OperationName { get; set; }

/// <summary>
/// Unique string used to identify the operation. The id MUST be unique among all operations described in the API. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is recommended to follow common programming naming conventions.
/// </summary>
Expand Down
11 changes: 10 additions & 1 deletion src/Microsoft.DocAsCode.Build.RestApi/Swagger/PathItemObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@ namespace Microsoft.DocAsCode.Build.RestApi.Swagger
using System.Collections.Generic;

using Newtonsoft.Json;
using YamlDotNet.Serialization;

using Microsoft.DocAsCode.YamlSerialization;

/// <summary>
/// TODO: need a converter
/// </summary>
[Serializable]
public class PathItemObject : Dictionary<string, OperationObject>
public class PathItemObject
{
/// <summary>
/// A list of parameters that are applicable for all the operations described under this path.
/// These parameters can be overridden at the operation level, but cannot be removed there.
/// </summary>
[YamlMember(Alias = "parameters")]
[JsonProperty("parameters")]
public List<ParameterObject> Parameters { get; set; }

[ExtensibleMember]
[JsonExtensionData]
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
Expand Down
7 changes: 0 additions & 7 deletions src/Microsoft.DocAsCode.Build.RestApi/Swagger/PathsObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,8 @@ namespace Microsoft.DocAsCode.Build.RestApi.Swagger
using System;
using System.Collections.Generic;

using Newtonsoft.Json;

using Microsoft.DocAsCode.YamlSerialization;

[Serializable]
public class PathsObject : Dictionary<string, PathItemObject>
{
[ExtensibleMember]
[JsonExtensionData]
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ namespace Microsoft.DocAsCode.Build.RestApi.ViewModels
using YamlDotNet.Serialization;

using Microsoft.DocAsCode.DataContracts.Common;
using Microsoft.DocAsCode.YamlSerialization;

[Serializable]
public class RestApiChildItemViewModel : RestApiItemViewModelBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ namespace Microsoft.DocAsCode.Build.RestApi.ViewModels
using System.Text.RegularExpressions;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using YamlDotNet.Serialization;

using Microsoft.DocAsCode.Build.RestApi.Swagger;
using Microsoft.DocAsCode.Common;
using Microsoft.DocAsCode.DataContracts.Common;
using Microsoft.DocAsCode.Utility.EntityMergers;
using Microsoft.DocAsCode.YamlSerialization;

[Serializable]
public class RestApiRootItemViewModel : RestApiItemViewModelBase
{
private const string TagText = "tag";
private static readonly string[] OperationNames = { "get", "put", "post", "delete", "options", "head", "patch" };

/// <summary>
/// The original swagger.json cpntent
Expand All @@ -38,7 +40,7 @@ public class RestApiRootItemViewModel : RestApiItemViewModelBase
[JsonProperty("children")]
public List<RestApiChildItemViewModel> Children { get; set; }

public static RestApiRootItemViewModel FromSwaggerModel(Swagger.SwaggerModel swagger)
public static RestApiRootItemViewModel FromSwaggerModel(SwaggerModel swagger)
{
var uid = GetUid(swagger);
var vm = new RestApiRootItemViewModel
Expand Down Expand Up @@ -67,43 +69,60 @@ public static RestApiRootItemViewModel FromSwaggerModel(Swagger.SwaggerModel swa
});
}
}
foreach (var path in swagger.Paths)
if (swagger.Paths != null)
{
foreach (var op in path.Value)
foreach (var path in swagger.Paths)
{
var itemUid = GetUidForOperation(uid, op.Value);
var itemVm = new RestApiChildItemViewModel
var commonParameters = path.Value.Parameters;
foreach (var op in path.Value.Metadata)
{
Path = path.Key,
OperationName = op.Key,
OperationId = op.Value.OperationId,
HtmlId = GetHtmlId(itemUid),
Uid = itemUid,
Metadata = op.Value.Metadata,
Description = op.Value.Description,
Summary = op.Value.Summary,
Parameters = op.Value.Parameters?.Select(s => new RestApiParameterViewModel
// fetch operations from metadata
if (OperationNames.Contains(op.Key, StringComparer.OrdinalIgnoreCase))
{
Description = s.Description,
Metadata = s.Metadata
}).ToList(),
Responses = op.Value.Responses?.Select(s => new RestApiResponseViewModel
{
Metadata = s.Value.Metadata,
Description = s.Value.Description,
Summary = s.Value.Summary,
HttpStatusCode = s.Key,
Examples = s.Value.Examples?.Select(example => new RestApiResponseExampleViewModel
var opJObject = op.Value as JObject;
if (opJObject == null)
{
throw new InvalidOperationException($"Value of {op.Key} should be JObject");
}

// convert operation from JObject to OperationObject
var operation = opJObject.ToObject<OperationObject>();
var parameters = GetParametersForOperation(operation.Parameters, commonParameters);
var itemUid = GetUidForOperation(uid, operation);
var itemVm = new RestApiChildItemViewModel
{
MimeType = example.Key,
Content = example.Value != null ? JsonUtility.Serialize(example.Value) : null,
}).ToList(),
}).ToList(),
};

// TODO: line number
itemVm.Metadata[Constants.PropertyName.Source] = swagger.Metadata[Constants.PropertyName.Source];
vm.Children.Add(itemVm);
Path = path.Key,
OperationName = op.Key,
OperationId = operation.OperationId,
HtmlId = GetHtmlId(itemUid),
Uid = itemUid,
Metadata = operation.Metadata,
Description = operation.Description,
Summary = operation.Summary,
Parameters = parameters?.Select(s => new RestApiParameterViewModel
{
Description = s.Description,
Metadata = s.Metadata
}).ToList(),
Responses = operation.Responses?.Select(s => new RestApiResponseViewModel
{
Metadata = s.Value.Metadata,
Description = s.Value.Description,
Summary = s.Value.Summary,
HttpStatusCode = s.Key,
Examples = s.Value.Examples?.Select(example => new RestApiResponseExampleViewModel
{
MimeType = example.Key,
Content = example.Value != null ? JsonUtility.Serialize(example.Value) : null,
}).ToList(),
}).ToList(),
};

// TODO: line number
itemVm.Metadata[Constants.PropertyName.Source] = swagger.Metadata[Constants.PropertyName.Source];
vm.Children.Add(itemVm);
}
}
}
}

Expand All @@ -125,17 +144,17 @@ private static string GetHtmlId(string id)
return HtmlEncodeRegex.Replace(id, "_");
}

private static string GetUid(Swagger.SwaggerModel swagger)
private static string GetUid(SwaggerModel swagger)
{
return GenerateUid(swagger.Host, swagger.BasePath, swagger.Info.Title, swagger.Info.Version);
}

private static string GetUidForOperation(string parentUid, Swagger.OperationObject item)
private static string GetUidForOperation(string parentUid, OperationObject item)
{
return GenerateUid(parentUid, item.OperationId);
}

private static string GetUidForTag(string parentUid, Swagger.TagItemObject tag)
private static string GetUidForTag(string parentUid, TagItemObject tag)
{
return GenerateUid(parentUid, TagText, tag.Name);
}
Expand All @@ -150,6 +169,56 @@ private static string GenerateUid(params string[] segments)
return string.Join("/", segments.Where(s => !string.IsNullOrEmpty(s)).Select(s => s.Trim('/')));
}

/// <summary>
/// Merge operation's parameters with path's parameters.
/// </summary>
/// <param name="operationParameters">Operation's parameters</param>
/// <param name="pathParameters">Path's parameters</param>
/// <returns></returns>
private static IEnumerable<ParameterObject> GetParametersForOperation(List<ParameterObject> operationParameters, List<ParameterObject> pathParameters)
{
if (pathParameters == null || pathParameters.Count == 0)
{
return operationParameters;
}
if (operationParameters == null || operationParameters.Count == 0)
{
return pathParameters;
}

// Path parameters can be overridden at the operation level.
var uniquePathParams = pathParameters.Where(
p => !operationParameters.Any(o => IsParameterEquals(p, o))).ToList();

return operationParameters.Union(uniquePathParams).ToList();
}

/// <summary>
/// Judge whether two ParameterObject equal to each other. according to value of 'name' and 'in'
/// Define 'Equals' here instead of inside ParameterObject, since ParameterObject is either self defined or referenced object which 'name' and 'in' needs to be resolved.
/// </summary>
/// <param name="left">Fist ParameterObject</param>
/// <param name="right">Second ParameterObject</param>
private static bool IsParameterEquals(ParameterObject left, ParameterObject right)
{
if (left == null || right == null)
{
return false;
}
return string.Equals(GetMetadataStringValue(left, "name"), GetMetadataStringValue(right, "name")) &&
string.Equals(GetMetadataStringValue(left, "in"), GetMetadataStringValue(right, "in"));
}

private static string GetMetadataStringValue(ParameterObject parameter, string metadataName)
{
object metadataValue;
if (parameter.Metadata.TryGetValue(metadataName, out metadataValue))
{
return (string)metadataValue;
}
return null;
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
<None Include="TestData\swagger\ref_swagger2.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="TestData\swagger\pathParameters_swagger2.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="TestData\swagger\tag_swagger2.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ public void ProcessSwaggerShouldSucceed()
var model = JsonUtility.Deserialize<RestApiRootItemViewModel>(outputRawModelPath);
Assert.Equal("graph.windows.net/myorganization/Contacts/1.0", model.Uid);
Assert.Equal("graph_windows_net_myorganization_Contacts_1_0", model.HtmlId);
Assert.Equal(9, model.Children.Count);
Assert.Equal(10, model.Children.Count);
Assert.Equal("Hello world!", model.Metadata["meta"]);

// Verify $ref in path
var item1 = model.Children[0];
Assert.Equal("graph.windows.net/myorganization/Contacts/1.0/get contacts", item1.Uid);
Assert.Equal("<p>You can get a collection of contacts from your tenant.</p>\n", item1.Summary);
Expand Down Expand Up @@ -86,6 +88,33 @@ public void ProcessSwaggerShouldSucceed()
Assert.Equal("http://swagger.io", externalDocs["url"]);
var tag2 = model.Tags[1];
Assert.Equal("pet_store", tag2.HtmlId);

// Verify path parameters
// Path parameter applicable for get operation
Assert.Equal(2, item2.Parameters.Count);
Assert.Equal("object_id", item2.Parameters[0].Metadata["name"]);
Assert.Equal("api-version", item2.Parameters[1].Metadata["name"]);
Assert.Equal(true, item2.Parameters[1].Metadata["required"]);

// Override ""api-version" parameters by $ref for patch opearation
var item3 = model.Children[2];
Assert.Equal(3, item3.Parameters.Count);
Assert.Equal("object_id", item3.Parameters[0].Metadata["name"]);
Assert.Equal("api-version", item3.Parameters[1].Metadata["name"]);
Assert.Equal(false, item3.Parameters[1].Metadata["required"]);

// Override ""api-version" parameters by self definition for delete opearation
var item4 = model.Children[3];
Assert.Equal(2, item4.Parameters.Count);
Assert.Equal("object_id", item4.Parameters[0].Metadata["name"]);
Assert.Equal("api-version", item4.Parameters[1].Metadata["name"]);
Assert.Equal(false, item4.Parameters[1].Metadata["required"]);

// When operation parameters is not set, inherit from th parameters for post opearation
var item5 = model.Children[4];
Assert.Equal(1, item5.Parameters.Count);
Assert.Equal("api-version", item5.Parameters[0].Metadata["name"]);
Assert.Equal(true, item5.Parameters[0].Metadata["required"]);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ public void ParseSimpleSwaggerJsonShouldSucceed()
var swaggerFile = @"TestData\swagger\simple_swagger2.json";
var swagger = SwaggerJsonParser.Parse(File.ReadAllText(swaggerFile));

Assert.Equal(1, swagger.Paths.Count);
Assert.Equal(1, swagger.Paths["/contacts"].Count);
var action = swagger.Paths["/contacts"]["get"];
Assert.Equal(1, swagger.Paths.Values.Count);
var actionJObject = swagger.Paths["/contacts"].Metadata["get"] as JObject;
Assert.NotNull(actionJObject);
var action = actionJObject.ToObject<OperationObject>();
var parameters = action.Parameters;
Assert.Equal(1, parameters.Count);
Assert.Equal("query", parameters[0].Metadata["in"]);
Expand All @@ -40,11 +41,11 @@ public void ParseSwaggerJsonWithReferenceShouldSucceed()
var swagger = SwaggerJsonParser.Parse(File.ReadAllText(swaggerFile));

Assert.Equal(1, swagger.Paths.Count);
Assert.Equal(1, swagger.Paths["/contacts"].Count);
var action = swagger.Paths["/contacts"]["patch"];
Assert.Equal(1, swagger.Paths["/contacts"].Metadata.Count);
var actionJObject = swagger.Paths["/contacts"].Metadata["patch"] as JObject;
Assert.NotNull(actionJObject);
var action = actionJObject.ToObject<OperationObject>();
var parameters = action.Parameters;
Assert.Equal(2, parameters.Count);
Assert.Equal("body", parameters[0].Metadata["in"]);
var schema = parameters[0].Metadata["schema"] as JObject;
Assert.NotNull(schema);
Assert.Equal("Sales", schema["example"]["department"].ToString());
Expand Down Expand Up @@ -89,6 +90,28 @@ public void ParseSwaggerJsonWithTagShouldSucceed()
Assert.Equal("http://swagger.io", externalDocs["url"]);
}


[Fact]
public void ParseSwaggerJsonWithPathParametersShouldSucceed()
{
const string swaggerFile = @"TestData\swagger\pathParameters_swagger2.json";
var swagger = SwaggerJsonParser.Parse(File.ReadAllText(swaggerFile));

Assert.Equal(1, swagger.Paths.Values.Count);
var parameters = swagger.Paths["/contacts"].Parameters;
Assert.Equal(2, parameters.Count);

// $ref parameter
Assert.Equal("api-version", parameters[0].Metadata["name"]);
Assert.Equal(false, parameters[0].Metadata["required"]);
Assert.Equal("api version description", parameters[0].Description);

// self defined parameter
Assert.Equal("subscriptionId", parameters[1].Metadata["name"]);
Assert.Equal(true, parameters[1].Metadata["required"]);
Assert.Equal("subscription id", parameters[1].Description);
}

[Fact]
public void ParseSwaggerJsonWithLoopReferenceShouldFail()
{
Expand Down
Loading