Skip to content

Commit 663b1ab

Browse files
authored
OpenApiDocuments Diff Preview (#303)
- Updated model to store OpenApiDifference - Added various OpenApiComparer's to compare fragments of OpenApiDocument
1 parent 3c40598 commit 663b1ab

15 files changed

+1288
-4
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace Microsoft.OpenApi.Services
8+
{
9+
/// <summary>
10+
/// A class encapsulating the comparision context.
11+
/// </summary>
12+
public class ComparisonContext
13+
{
14+
private readonly IList<OpenApiDifference> _openApiDifferences = new List<OpenApiDifference>();
15+
private readonly Stack<string> _path = new Stack<string>();
16+
internal OpenApiComparerFactory OpenApiComparerFactory;
17+
18+
/// <summary>
19+
/// Creates instance of <see cref="ComparisonContext"/>.
20+
/// </summary>
21+
/// <param name="openApiComparerFactory"></param>
22+
public ComparisonContext(OpenApiComparerFactory openApiComparerFactory)
23+
{
24+
OpenApiComparerFactory = openApiComparerFactory;
25+
}
26+
27+
/// <summary>
28+
/// Gets the list of open api differences.
29+
/// </summary>
30+
public IEnumerable<OpenApiDifference> OpenApiDifferences => _openApiDifferences;
31+
32+
/// <summary>
33+
/// Pointer to the source of difference in the document.
34+
/// </summary>
35+
public string PathString => "#/" + string.Join("/", _path.Reverse());
36+
37+
/// <summary>
38+
/// Adds an open api difference.
39+
/// </summary>
40+
/// <param name="openApiDifference">The open api difference to add.</param>
41+
public void AddOpenApiDifference(OpenApiDifference openApiDifference)
42+
{
43+
if (openApiDifference == null)
44+
{
45+
throw Error.ArgumentNull(nameof(openApiDifference));
46+
}
47+
48+
_openApiDifferences.Add(openApiDifference);
49+
}
50+
51+
/// <summary>
52+
/// Allow Rule to indicate difference occured at a deeper context level.
53+
/// </summary>
54+
/// <param name="segment">Identifier for the context.</param>
55+
public void Enter(string segment)
56+
{
57+
_path.Push(segment);
58+
}
59+
60+
/// <summary>
61+
/// Exit from path context level. Enter and Exit calls should be matched.
62+
/// </summary>
63+
public void Exit()
64+
{
65+
_path.Pop();
66+
}
67+
68+
/// <summary>
69+
/// Gets the comparer instance for the requested type.
70+
/// </summary>
71+
/// <typeparam name="T">Type of requested comparer.</typeparam>
72+
/// <returns>Comparer instance to use when comparing requested type.</returns>
73+
internal OpenApiComparerBase<T> GetComparer<T>()
74+
{
75+
return OpenApiComparerFactory.GetComparer<T>();
76+
}
77+
}
78+
}

src/Microsoft.OpenApi/Services/OpenApiComparer.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,23 @@ public static class OpenApiComparer
1414
/// <summary>
1515
/// Compares two <see cref="OpenApiDocument"/>s and returns a list of differences.
1616
/// </summary>
17-
public static List<OpenApiDifference> Compare(OpenApiDocument source, OpenApiDocument target)
17+
public static IEnumerable<OpenApiDifference> Compare(OpenApiDocument source, OpenApiDocument target)
1818
{
19-
var diffs = new List<OpenApiDifference>();
20-
return diffs;
19+
if (source == null)
20+
{
21+
throw Error.ArgumentNull(nameof(source));
22+
}
23+
24+
if (target == null)
25+
{
26+
throw Error.ArgumentNull(nameof(target));
27+
}
28+
29+
var comparisionContext = new ComparisonContext(new OpenApiComparerFactory());
30+
31+
new OpenApiDocumentComparer().Compare(source, target, comparisionContext);
32+
33+
return comparisionContext.OpenApiDifferences;
2134
}
2235
}
2336
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.OpenApi.Services
7+
{
8+
/// <summary>
9+
/// Defines behavior for comparing parts of <see cref="OpenAPiDocument"/> class.
10+
/// </summary>
11+
/// <typeparam name="T">Type of class to compare.</typeparam>
12+
public abstract class OpenApiComparerBase<T>
13+
{
14+
/// <summary>
15+
/// Validates a fragment of <see cref="OpenApiDocument"/>.
16+
/// </summary>
17+
/// <param name="sourceFragment">The source fragment.</param>
18+
/// <param name="targetFragment">The target fragment.</param>
19+
/// <param name="comparisonContext">Context under which to compare fragment.</param>
20+
public abstract void Compare(T sourceFragment, T targetFragment, ComparisonContext comparisonContext);
21+
22+
/// <summary>
23+
/// Compares two string object.
24+
/// </summary>
25+
/// <param name="source">The source string.</param>
26+
/// <param name="target">The target string.</param>
27+
/// <param name="comparisonContext">The context under which to compare the objects.</param>
28+
internal void Compare(string source, string target, ComparisonContext comparisonContext)
29+
{
30+
if (string.IsNullOrWhiteSpace(source) && string.IsNullOrWhiteSpace(target))
31+
{
32+
return;
33+
}
34+
35+
if (string.Compare(source, target, StringComparison.CurrentCultureIgnoreCase) != 0)
36+
{
37+
comparisonContext.AddOpenApiDifference(new OpenApiDifference
38+
{
39+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
40+
OpenApiComparedElementType = typeof(string),
41+
SourceValue = source,
42+
TargetValue = target,
43+
Pointer = comparisonContext.PathString
44+
});
45+
}
46+
}
47+
48+
/// <summary>
49+
/// Compares two boolean object.
50+
/// </summary>
51+
/// <param name="source">The source.</param>
52+
/// <param name="target">The target.</param>
53+
/// <param name="comparisonContext">The context under which to compare the objects.</param>
54+
internal void Compare(bool? source, bool? target, ComparisonContext comparisonContext)
55+
{
56+
if (source == null && target == null)
57+
{
58+
return;
59+
}
60+
61+
if (source != target)
62+
{
63+
comparisonContext.AddOpenApiDifference(new OpenApiDifference
64+
{
65+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
66+
OpenApiComparedElementType = typeof(bool),
67+
SourceValue = source,
68+
TargetValue = target,
69+
Pointer = comparisonContext.PathString
70+
});
71+
}
72+
}
73+
74+
/// <summary>
75+
/// Adds a segment to the context path to enable pointing to the current location in the document.
76+
/// </summary>
77+
/// <param name="comparisonContext">The context under which to compare the objects.</param>
78+
/// <param name="segment">An identifier for the segment.</param>
79+
/// <param name="openApiDifference">The open api difference to add.</param>
80+
internal void WalkAndAddOpenApiDifference(
81+
ComparisonContext comparisonContext,
82+
string segment,
83+
OpenApiDifference openApiDifference)
84+
{
85+
comparisonContext.Enter(segment.Replace("/", "~1"));
86+
openApiDifference.Pointer = comparisonContext.PathString;
87+
comparisonContext.AddOpenApiDifference(openApiDifference);
88+
comparisonContext.Exit();
89+
}
90+
91+
/// <summary>
92+
/// Adds a segment to the context path to enable pointing to the current location in the document.
93+
/// </summary>
94+
/// <param name="comparisonContext">The context under which to compare the objects.</param>
95+
/// <param name="segment">An identifier for the segment.</param>
96+
/// <param name="compare">An action that compares objects within the context.</param>
97+
protected virtual void WalkAndCompare(
98+
ComparisonContext comparisonContext,
99+
string segment,
100+
Action compare)
101+
{
102+
comparisonContext.Enter(segment.Replace("/", "~1"));
103+
compare();
104+
comparisonContext.Exit();
105+
}
106+
}
107+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.OpenApi.Models;
7+
8+
namespace Microsoft.OpenApi.Services
9+
{
10+
/// <summary>
11+
/// Defines behavior for registering specific comparer instances and encapsulates default comparers.
12+
/// </summary>
13+
public class OpenApiComparerFactory
14+
{
15+
private static readonly Dictionary<Type, object> TypeToDefaultComparerMap = new Dictionary<Type, object>
16+
{
17+
{typeof(OpenApiPaths), new OpenApiPathsComparer()},
18+
{typeof(OpenApiPathItem), new OpenApiPathItemComparer()},
19+
{typeof(OpenApiOperation), new OpenApiOperationComparer()},
20+
{typeof(IDictionary<OperationType, OpenApiOperation>), new OpenApiOperationsComparer()},
21+
{typeof(IList<OpenApiParameter>), new OpenApiParametersComparer()},
22+
{typeof(OpenApiParameter), new OpenApiParameterComparer()}
23+
};
24+
25+
private readonly Dictionary<Type, object> _typeToComparerMap = new Dictionary<Type, object>();
26+
27+
/// <summary>
28+
/// Adds a comparer instance to this registry.
29+
/// </summary>
30+
/// <typeparam name="T">Type of the comparer instance.</typeparam>
31+
/// <param name="comparer">Instance of <see cref="OpenApiComparerBase{T}"/> to register.</param>
32+
protected void AddComparer<T>(OpenApiComparerBase<T> comparer)
33+
{
34+
if (comparer == null)
35+
{
36+
throw new ArgumentNullException(nameof(comparer));
37+
}
38+
39+
_typeToComparerMap.Add(typeof(T), comparer);
40+
}
41+
42+
/// <summary>
43+
/// Gets a registered comparer instance for the requested type.
44+
/// </summary>
45+
/// <typeparam name="T">Type of the comparer.</typeparam>
46+
/// <returns>The comparer instance corresponding to the type requested.</returns>
47+
internal OpenApiComparerBase<T> GetComparer<T>()
48+
{
49+
var requestedComparerType = typeof(T);
50+
51+
if (_typeToComparerMap.TryGetValue(requestedComparerType, out object comparerInstance))
52+
{
53+
return (OpenApiComparerBase<T>) comparerInstance;
54+
}
55+
56+
if (!TypeToDefaultComparerMap.TryGetValue(requestedComparerType, out comparerInstance))
57+
{
58+
throw Error.NotSupported(
59+
$"No comparer is registered for type {requestedComparerType.Name}.");
60+
}
61+
62+
return (OpenApiComparerBase<T>) comparerInstance;
63+
}
64+
}
65+
}
Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,39 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4+
using System;
45
using Microsoft.OpenApi.Models;
56

67
namespace Microsoft.OpenApi.Services
78
{
89
/// <summary>
9-
/// Difference point between two <see cref="OpenApiDocument"/>
10+
/// Difference point between two <see cref="OpenApiDocument"/>.
1011
/// </summary>
1112
public class OpenApiDifference
1213
{
14+
/// <summary>
15+
/// The type of the element for which difference found.
16+
/// </summary>
17+
public Type OpenApiComparedElementType { get; set; }
18+
19+
/// <summary>
20+
/// The open api difference operation.
21+
/// </summary>
22+
public OpenApiDifferenceOperation OpenApiDifferenceOperation { get; set; }
23+
24+
/// <summary>
25+
/// Pointer to the location of the difference.
26+
/// </summary>
27+
public string Pointer { get; set; }
28+
29+
/// <summary>
30+
/// The source value.
31+
/// </summary>
32+
public object SourceValue { get; set; }
33+
34+
/// <summary>
35+
/// The target value.
36+
/// </summary>
37+
public object TargetValue { get; set; }
1338
}
1439
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
namespace Microsoft.OpenApi.Services
5+
{
6+
/// <summary>
7+
/// The open api difference operation.
8+
/// </summary>
9+
public enum OpenApiDifferenceOperation
10+
{
11+
Add,
12+
Remove,
13+
Update
14+
}
15+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using Microsoft.OpenApi.Models;
5+
6+
namespace Microsoft.OpenApi.Services
7+
{
8+
/// <summary>
9+
/// Defines behavior for comparing properties of <see cref="OpenApiDocument"/>.
10+
/// </summary>
11+
public class OpenApiDocumentComparer : OpenApiComparerBase<OpenApiDocument>
12+
{
13+
/// <summary>
14+
/// Executes comparision against source and target <see cref="OpenApiDocument"/>.
15+
/// </summary>
16+
/// <param name="sourceDocument">The source.</param>
17+
/// <param name="targetDocument">The target</param>
18+
/// <param name="comparisonContext">Context under which to compare the source and target.</param>
19+
public override void Compare(
20+
OpenApiDocument sourceDocument,
21+
OpenApiDocument targetDocument,
22+
ComparisonContext comparisonContext)
23+
{
24+
comparisonContext.GetComparer<OpenApiPaths>().Compare(
25+
sourceDocument.Paths,
26+
targetDocument.Paths,
27+
comparisonContext);
28+
29+
// To Do Compare Info
30+
// To Do Compare Servers
31+
// To Do Compare Components
32+
// To Do Compare Security Requirements
33+
// To Do Compare Tags
34+
// To Do Compare External Docs
35+
// To Do Compare Extensions
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)