Skip to content

Commit

Permalink
Rest - Step 1 - support loop reference (#453)
Browse files Browse the repository at this point in the history
* Rest - Step 1 - support loop reference

* add cache and remove extra definitions

* fix spaces
  • Loading branch information
hellosnow authored Jul 4, 2016
1 parent 7a0f35f commit 2dcdeb1
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Microsoft.DocAsCode.Build.Common
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DocAsCode.Build.Common
{
using System;
using System.Collections.Generic;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Compile Include="Swagger\InfoObject.cs" />
<Compile Include="Swagger\Internals\SwaggerArray.cs" />
<Compile Include="Swagger\Internals\SwaggerJsonBuilder.cs" />
<Compile Include="Swagger\Internals\SwaggerLoopReferenceObject.cs" />
<Compile Include="Swagger\Internals\SwaggerObject.cs" />
<Compile Include="Swagger\Internals\SwaggerObjectBase.cs" />
<Compile Include="Swagger\Internals\SwaggerObjectConverter.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
namespace Microsoft.DocAsCode.Build.RestApi.Swagger.Internals
{
using System.Collections.Generic;
using System.Linq;

internal class SwaggerArray : SwaggerObjectBase
{
public override SwaggerObjectType ObjectType => SwaggerObjectType.Array;

public List<SwaggerObjectBase> Array { get; set; } = new List<SwaggerObjectBase>();

public override SwaggerObjectBase Clone()
{
var clone = (SwaggerArray)MemberwiseClone();
clone.Array = Array.Select(a => a.Clone()).ToList();
clone.ReferencesResolved = false;
return clone;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ namespace Microsoft.DocAsCode.Build.RestApi.Swagger.Internals
internal class SwaggerJsonBuilder
{
private IDictionary<string, SwaggerObject> _documentObjectCache;
private IDictionary<string, SwaggerObject> _resolvedObjectCache;
private const string DefinitionsKey = "definitions";
private const string ParametersKey = "parameters";

public SwaggerObjectBase Read(JsonReader reader)
{
_documentObjectCache = new Dictionary<string, SwaggerObject>();
_resolvedObjectCache = new Dictionary<string, SwaggerObject>();
var token = JToken.ReadFrom(reader);
var swagger = Build(token);
return ResolveReferences(swagger);
RemoveReferenceDefinitions((SwaggerObject)swagger);
return ResolveReferences(swagger, new Stack<string>());
}

private SwaggerObjectBase Build(JToken token)
Expand Down Expand Up @@ -92,7 +97,20 @@ private SwaggerObjectBase Build(JToken token)
};
}

private SwaggerObjectBase ResolveReferences(SwaggerObjectBase swaggerBase)
private static void RemoveReferenceDefinitions(SwaggerObject root)
{
// Remove definitions and parameters which has been added into _documentObjectCache
if (root.Dictionary.ContainsKey(DefinitionsKey))
{
root.Dictionary.Remove(DefinitionsKey);
}
if (root.Dictionary.ContainsKey(ParametersKey))
{
root.Dictionary.Remove(ParametersKey);
}
}

private SwaggerObjectBase ResolveReferences(SwaggerObjectBase swaggerBase, Stack<string> refStack)
{
if (swaggerBase.ReferencesResolved)
{
Expand All @@ -118,7 +136,27 @@ private SwaggerObjectBase ResolveReferences(SwaggerObjectBase swaggerBase)
throw new JsonException($"Could not resolve reference '{swagger.DeferredReference}' in the document.");
}

swagger.Reference = referencedObject;
if (refStack.Contains(referencedObject.Location))
{
return new SwaggerLoopReferenceObject();
}

SwaggerObject existingObject;
if (_resolvedObjectCache.TryGetValue(swagger.DeferredReference, out existingObject))
{
return existingObject;
}

// Clone to avoid change the reference object in _documentObjectCache
refStack.Push(referencedObject.Location);
swagger.Reference = (SwaggerObject)ResolveReferences(referencedObject.Clone(), refStack);
refStack.Pop();

if (refStack.Count == 0)
{
_resolvedObjectCache.Add(swagger.DeferredReference, swagger.Reference);
}

}
return swagger;
}
Expand All @@ -127,7 +165,7 @@ private SwaggerObjectBase ResolveReferences(SwaggerObjectBase swaggerBase)
var swagger = (SwaggerObject)swaggerBase;
foreach (var key in swagger.Dictionary.Keys.ToList())
{
swagger.Dictionary[key] = ResolveReferences(swagger.Dictionary[key]);
swagger.Dictionary[key] = ResolveReferences(swagger.Dictionary[key], refStack);
}
return swagger;
}
Expand All @@ -136,7 +174,7 @@ private SwaggerObjectBase ResolveReferences(SwaggerObjectBase swaggerBase)
var swagger = (SwaggerArray)swaggerBase;
for (int i = 0; i < swagger.Array.Count; i++)
{
swagger.Array[i] = ResolveReferences(swagger.Array[i]);
swagger.Array[i] = ResolveReferences(swagger.Array[i], refStack);
}
return swagger;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.DocAsCode.Build.RestApi.Swagger.Internals
{
internal class SwaggerLoopReferenceObject : SwaggerObject
{
public override SwaggerObjectType ObjectType => SwaggerObjectType.LoopReference;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@
namespace Microsoft.DocAsCode.Build.RestApi.Swagger.Internals
{
using System.Collections.Generic;
using System.Linq;

internal class SwaggerObject : SwaggerObjectBase
{
public override SwaggerObjectType ObjectType => SwaggerObjectType.Object;

public Dictionary<string, SwaggerObjectBase> Dictionary { get; set; } = new Dictionary<string, SwaggerObjectBase>();

public string Location { get; set; }

public override SwaggerObjectBase Clone()
{
var clone = (SwaggerObject)MemberwiseClone();
clone.Dictionary = Dictionary.ToDictionary(k => k.Key, k => k.Value.Clone());
clone.ReferencesResolved = false;
return clone;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@

namespace Microsoft.DocAsCode.Build.RestApi.Swagger.Internals
{
using Microsoft.DocAsCode.Build.Common;

internal abstract class SwaggerObjectBase
{
public abstract SwaggerObjectType ObjectType { get; }

public bool ReferencesResolved { get; set; }

public abstract SwaggerObjectBase Clone();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
{
var swagger = (SwaggerObject)swaggerBase;
var jObject = new JObject();
foreach (var i in swagger.Dictionary)
foreach (var i in swagger.Dictionary.Where(p => !(p.Value is SwaggerLoopReferenceObject)))
{
jObject.Add(i.Key, JToken.FromObject(i.Value, serializer));
}
Expand All @@ -55,7 +55,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
case SwaggerObjectType.Array:
{
var swagger = (SwaggerArray)swaggerBase;
var jArray = JArray.FromObject(swagger.Array.Select(s => JToken.FromObject(s, serializer)));
var jArray = JArray.FromObject(swagger.Array.Where(s => !(s is SwaggerLoopReferenceObject)).Select(s => JToken.FromObject(s, serializer)));
jArray.WriteTo(writer);
}
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ internal enum SwaggerObjectType
/// <summary>
/// Value type similar to JValue
/// </summary>
ValueType
ValueType,

/// <summary>
/// Loop reference type, to indicate to ignore when serializing
/// </summary>
LoopReference
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ namespace Microsoft.DocAsCode.Build.RestApi.Swagger.Internals
internal class SwaggerReferenceObject : SwaggerObjectBase
{
public override SwaggerObjectType ObjectType => SwaggerObjectType.ReferenceObject;

public string DeferredReference { get; set; }

public JObject Token { get; set; }

public SwaggerObject Reference { get; set; }

public override SwaggerObjectBase Clone()
{
var clone = (SwaggerReferenceObject)MemberwiseClone();
clone.Reference = (SwaggerObject)Reference?.Clone();
clone.ReferencesResolved = false;
return clone;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ namespace Microsoft.DocAsCode.Build.RestApi.Swagger.Internals
internal class SwaggerValue : SwaggerObjectBase
{
public override SwaggerObjectType ObjectType => SwaggerObjectType.ValueType;

public JToken Token { get; set; }

public override SwaggerObjectBase Clone()
{
var clone = (SwaggerValue)MemberwiseClone();
clone.ReferencesResolved = false;
return clone;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ public static SwaggerModel Parse(string json)
{
using (JsonReader reader = new JsonTextReader(new StringReader(json)))
{
// Deserialize to internal swagger model
var builder = new SwaggerJsonBuilder();
var swagger = builder.Read(reader);

// Serialze to JToken
var token = JToken.FromObject(swagger, _serializer.Value);

// Convert to swagger model
return token.ToObject<SwaggerModel>(_serializer.Value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Microsoft.DocAsCode.Build.RestApi.Tests
{
using System.IO;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;

Expand Down Expand Up @@ -113,10 +114,19 @@ public void ParseSwaggerJsonWithPathParametersShouldSucceed()
}

[Fact]
public void ParseSwaggerJsonWithLoopReferenceShouldFail()
public void ParseSwaggerJsonWithLoopReferenceShouldSucceed()
{
var swaggerFile = @"TestData\swagger\loopref_swagger2.json";
Assert.Throws<Newtonsoft.Json.JsonSerializationException>(() => SwaggerJsonParser.Parse(File.ReadAllText(swaggerFile)));
const string swaggerFile = @"TestData\swagger\loopref_swagger2.json";
var swagger = SwaggerJsonParser.Parse(File.ReadAllText(swaggerFile));

Assert.Equal(1, swagger.Paths.Values.Count);
var actionJObject = swagger.Paths["/contacts"].Metadata["patch"] as JObject;
Assert.NotNull(actionJObject);
var action = actionJObject.ToObject<OperationObject>();
var schemaJObject = (JObject)action.Parameters[0].Metadata["schema"];
var schemaObj = schemaJObject.ToString(Formatting.None);
Assert.Equal(@"{""properties"":{""provisioningErrors"":{""type"":""array"",""items"":{""properties"":{""errorDetail"":{""type"":""array""}}},""readOnly"":true}},""example"":{""department"":""Sales"",""jobTitle"":""Sales Rep""}}",
schemaObj);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"swagger": "2.0",
"info": {
"title": "Contacts",
"version": "1.0"
"version": "1.0"
},
"paths": {
"/contacts": {
Expand Down Expand Up @@ -45,11 +45,9 @@
"provisioningErrors": {
"type": "array",
"items": {
"schema": {
"$ref": "#/definitions/ProvisioningError"
}
"$ref": "#/definitions/ProvisioningError"
},
"readOnly": "true"
"readOnly": true
}
}
},
Expand All @@ -58,9 +56,7 @@
"errorDetail": {
"type": "array",
"items": {
"schema": {
"$ref": "#/definitions/contact"
}
"$ref": "#/definitions/contact"
}
}
}
Expand Down

0 comments on commit 2dcdeb1

Please sign in to comment.