Skip to content

Commit 0c86ee2

Browse files
authored
Open api schema diff (#316)
- Updated model to store OpenApiDifference - Added various OpenApiComparer's to compare fragments of OpenApiDocument - Added comparer for Schema, RequestBody, Responses, MediaType, Parameter, Headers, Servers, encoding
1 parent 212b6ac commit 0c86ee2

32 files changed

+6413
-146
lines changed

src/Microsoft.OpenApi/Services/ComparisonContext.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Linq;
6+
using Microsoft.OpenApi.Models;
67

78
namespace Microsoft.OpenApi.Services
89
{
@@ -13,15 +14,23 @@ public class ComparisonContext
1314
{
1415
private readonly IList<OpenApiDifference> _openApiDifferences = new List<OpenApiDifference>();
1516
private readonly Stack<string> _path = new Stack<string>();
17+
internal readonly OpenApiDocument SourceDocument;
18+
internal readonly Stack<OpenApiSchema> SourceSchemaLoop = new Stack<OpenApiSchema>();
19+
internal readonly OpenApiDocument TargetDocument;
20+
internal readonly Stack<OpenApiSchema> TargetSchemaLoop = new Stack<OpenApiSchema>();
1621
internal OpenApiComparerFactory OpenApiComparerFactory;
1722

1823
/// <summary>
1924
/// Creates instance of <see cref="ComparisonContext"/>.
2025
/// </summary>
21-
/// <param name="openApiComparerFactory"></param>
22-
public ComparisonContext(OpenApiComparerFactory openApiComparerFactory)
26+
public ComparisonContext(
27+
OpenApiComparerFactory openApiComparerFactory,
28+
OpenApiDocument sourceDocument,
29+
OpenApiDocument targetDocument)
2330
{
2431
OpenApiComparerFactory = openApiComparerFactory;
32+
SourceDocument = sourceDocument;
33+
TargetDocument = targetDocument;
2534
}
2635

2736
/// <summary>

src/Microsoft.OpenApi/Services/OpenApiComparer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public static IEnumerable<OpenApiDifference> Compare(OpenApiDocument source, Ope
2626
throw Error.ArgumentNull(nameof(target));
2727
}
2828

29-
var comparisionContext = new ComparisonContext(new OpenApiComparerFactory());
29+
var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), source, target);
3030

31-
new OpenApiDocumentComparer().Compare(source, target, comparisionContext);
31+
new OpenApiDocumentComparer().Compare(source, target, comparisonContext);
3232

33-
return comparisionContext.OpenApiDifferences;
33+
return comparisonContext.OpenApiDifferences;
3434
}
3535
}
3636
}

src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// Licensed under the MIT license.
33

44
using System;
5+
using Microsoft.OpenApi.Models;
56

67
namespace Microsoft.OpenApi.Services
78
{
89
/// <summary>
9-
/// Defines behavior for comparing parts of <see cref="OpenAPiDocument"/> class.
10+
/// Defines behavior for comparing parts of <see cref="OpenApiDocument"/> class.
1011
/// </summary>
1112
/// <typeparam name="T">Type of class to compare.</typeparam>
1213
public abstract class OpenApiComparerBase<T>
@@ -63,7 +64,73 @@ internal void Compare(bool? source, bool? target, ComparisonContext comparisonCo
6364
comparisonContext.AddOpenApiDifference(new OpenApiDifference
6465
{
6566
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
66-
OpenApiComparedElementType = typeof(bool),
67+
OpenApiComparedElementType = typeof(bool?),
68+
SourceValue = source,
69+
TargetValue = target,
70+
Pointer = comparisonContext.PathString
71+
});
72+
}
73+
}
74+
75+
/// <summary>
76+
/// Compares two decimal object.
77+
/// </summary>
78+
/// <param name="source">The source.</param>
79+
/// <param name="target">The target.</param>
80+
/// <param name="comparisonContext">The context under which to compare the objects.</param>
81+
internal void Compare(decimal? source, decimal? target, ComparisonContext comparisonContext)
82+
{
83+
if (source == null && target == null)
84+
{
85+
return;
86+
}
87+
88+
if (source != target)
89+
{
90+
comparisonContext.AddOpenApiDifference(new OpenApiDifference
91+
{
92+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
93+
OpenApiComparedElementType = typeof(decimal?),
94+
SourceValue = source,
95+
TargetValue = target,
96+
Pointer = comparisonContext.PathString
97+
});
98+
}
99+
}
100+
101+
/// <summary>
102+
/// Compares Enum.
103+
/// </summary>
104+
/// <param name="source">The source.</param>
105+
/// <param name="target">The target.</param>
106+
/// <param name="comparisonContext">The context under which to compare the objects.</param>
107+
internal void Compare<TEnum>(Enum source, Enum target, ComparisonContext comparisonContext)
108+
{
109+
if (source == null && target == null)
110+
{
111+
return;
112+
}
113+
114+
if (source == null || target == null)
115+
{
116+
comparisonContext.AddOpenApiDifference(new OpenApiDifference
117+
{
118+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
119+
OpenApiComparedElementType = typeof(TEnum),
120+
SourceValue = source,
121+
TargetValue = target,
122+
Pointer = comparisonContext.PathString
123+
});
124+
125+
return;
126+
}
127+
128+
if (!source.Equals(target))
129+
{
130+
comparisonContext.AddOpenApiDifference(new OpenApiDifference
131+
{
132+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
133+
OpenApiComparedElementType = typeof(TEnum),
67134
SourceValue = source,
68135
TargetValue = target,
69136
Pointer = comparisonContext.PathString
@@ -82,7 +149,7 @@ internal void WalkAndAddOpenApiDifference(
82149
string segment,
83150
OpenApiDifference openApiDifference)
84151
{
85-
comparisonContext.Enter(segment.Replace("/", "~1"));
152+
comparisonContext.Enter(segment.Replace("~", "~0").Replace("/", "~1"));
86153
openApiDifference.Pointer = comparisonContext.PathString;
87154
comparisonContext.AddOpenApiDifference(openApiDifference);
88155
comparisonContext.Exit();
@@ -99,7 +166,7 @@ protected virtual void WalkAndCompare(
99166
string segment,
100167
Action compare)
101168
{
102-
comparisonContext.Enter(segment.Replace("/", "~1"));
169+
comparisonContext.Enter(segment.Replace("~", "~0").Replace("/", "~1"));
103170
compare();
104171
comparisonContext.Exit();
105172
}

src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,28 @@ public class OpenApiComparerFactory
1919
{typeof(OpenApiOperation), new OpenApiOperationComparer()},
2020
{typeof(IDictionary<OperationType, OpenApiOperation>), new OpenApiOperationsComparer()},
2121
{typeof(IList<OpenApiParameter>), new OpenApiParametersComparer()},
22-
{typeof(OpenApiParameter), new OpenApiParameterComparer()}
22+
{typeof(OpenApiParameter), new OpenApiParameterComparer()},
23+
{typeof(OpenApiSchema), new OpenApiSchemaComparer()},
24+
{typeof(OpenApiMediaType), new OpenApiMediaTypeComparer()},
25+
{typeof(IDictionary<string, OpenApiMediaType>), new OpenApiDictionaryComparer<OpenApiMediaType>()},
26+
{typeof(IDictionary<string, OpenApiResponse>), new OpenApiDictionaryComparer<OpenApiResponse>()},
27+
{typeof(IDictionary<string, OpenApiHeader>), new OpenApiDictionaryComparer<OpenApiHeader>()},
28+
{typeof(IDictionary<string, OpenApiEncoding>), new OpenApiDictionaryComparer<OpenApiEncoding>()},
29+
{
30+
typeof(IDictionary<string, OpenApiServerVariable>),
31+
new OpenApiDictionaryComparer<OpenApiServerVariable>()
32+
},
33+
{typeof(IDictionary<string, OpenApiParameter>), new OpenApiDictionaryComparer<OpenApiParameter>()},
34+
{typeof(IDictionary<string, OpenApiRequestBody>), new OpenApiDictionaryComparer<OpenApiRequestBody>()},
35+
{typeof(IDictionary<string, OpenApiSchema>), new OpenApiDictionaryComparer<OpenApiSchema>()},
36+
{typeof(OpenApiHeader), new OpenApiHeaderComparer()},
37+
{typeof(OpenApiRequestBody), new OpenApiRequestBodyComparer()},
38+
{typeof(OpenApiResponse), new OpenApiResponseComparer()},
39+
{typeof(OpenApiComponents), new OpenApiComponentsComparer()},
40+
{typeof(OpenApiEncoding), new OpenApiEncodingComparer()},
41+
{typeof(IList<OpenApiServer>), new OpenApiServersComparer()},
42+
{typeof(OpenApiServer), new OpenApiServerComparer()},
43+
{typeof(OpenApiServerVariable), new OpenApiServerVariableComparer()}
2344
};
2445

2546
private readonly Dictionary<Type, object> _typeToComparerMap = new Dictionary<Type, object>();
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.OpenApi.Models;
6+
7+
namespace Microsoft.OpenApi.Services
8+
{
9+
/// <summary>
10+
/// Defines behavior for comparing properties of <see cref="OpenApiComponents"/>.
11+
/// </summary>
12+
public class OpenApiComponentsComparer : OpenApiComparerBase<OpenApiComponents>
13+
{
14+
/// <summary>
15+
/// Executes comparision against source and target <see cref="OpenApiComponents"/>.
16+
/// </summary>
17+
/// <param name="sourceComponents">The source.</param>
18+
/// <param name="targetComponents">The target.</param>
19+
/// <param name="comparisonContext">Context under which to compare the source and target.</param>
20+
public override void Compare(
21+
OpenApiComponents sourceComponents,
22+
OpenApiComponents targetComponents,
23+
ComparisonContext comparisonContext)
24+
{
25+
if (sourceComponents == null && targetComponents == null)
26+
{
27+
return;
28+
}
29+
30+
if (sourceComponents == null || targetComponents == null)
31+
{
32+
comparisonContext.AddOpenApiDifference(
33+
new OpenApiDifference
34+
{
35+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
36+
SourceValue = sourceComponents,
37+
TargetValue = targetComponents,
38+
OpenApiComparedElementType = typeof(OpenApiComponents),
39+
Pointer = comparisonContext.PathString
40+
});
41+
42+
return;
43+
}
44+
45+
WalkAndCompare(
46+
comparisonContext,
47+
OpenApiConstants.Parameters,
48+
() => comparisonContext
49+
.GetComparer<IDictionary<string, OpenApiParameter>>()
50+
.Compare(sourceComponents.Parameters, targetComponents.Parameters, comparisonContext));
51+
52+
WalkAndCompare(
53+
comparisonContext,
54+
OpenApiConstants.RequestBodies,
55+
() => comparisonContext
56+
.GetComparer<IDictionary<string, OpenApiRequestBody>>()
57+
.Compare(sourceComponents.RequestBodies, targetComponents.RequestBodies, comparisonContext));
58+
59+
WalkAndCompare(
60+
comparisonContext,
61+
OpenApiConstants.Responses,
62+
() => comparisonContext
63+
.GetComparer<IDictionary<string, OpenApiResponse>>()
64+
.Compare(sourceComponents.Responses, targetComponents.Responses, comparisonContext));
65+
66+
WalkAndCompare(
67+
comparisonContext,
68+
OpenApiConstants.Schemas,
69+
() => comparisonContext
70+
.GetComparer<IDictionary<string, OpenApiSchema>>()
71+
.Compare(sourceComponents.Schemas, targetComponents.Schemas, comparisonContext));
72+
73+
WalkAndCompare(
74+
comparisonContext,
75+
OpenApiConstants.Headers,
76+
() => comparisonContext
77+
.GetComparer<IDictionary<string, OpenApiHeader>>()
78+
.Compare(sourceComponents.Headers, targetComponents.Headers, comparisonContext));
79+
80+
// To Do compare Examples
81+
// To Do compare SecuritySchemes
82+
// To Do compare Links
83+
// To Do compare Callbacks
84+
// To Do compare Extensions
85+
}
86+
}
87+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
using Microsoft.OpenApi.Interfaces;
7+
8+
namespace Microsoft.OpenApi.Services
9+
{
10+
/// <summary>
11+
/// Defines behavior for comparing <see cref="IDictionary{TKey,TValue}"/> where TKey is <see cref="string"/>
12+
/// and TValue is <see cref="IOpenApiSerializable"/>.
13+
/// </summary>
14+
public class OpenApiDictionaryComparer<T> : OpenApiComparerBase<IDictionary<string, T>>
15+
where T : IOpenApiSerializable
16+
{
17+
/// <summary>
18+
/// Executes comparision against source and target <see cref="IDictionary{TKey, TValue}"/>
19+
/// where TKey is <see cref="string"/> and TValue is <see cref="IOpenApiSerializable"/>.
20+
/// </summary>
21+
/// <param name="sourceFragment">The source.</param>
22+
/// <param name="targetFragment">The target.</param>
23+
/// <param name="comparisonContext">Context under which to compare the source and target.</param>
24+
public override void Compare(
25+
IDictionary<string, T> sourceFragment,
26+
IDictionary<string, T> targetFragment,
27+
ComparisonContext comparisonContext)
28+
{
29+
if (sourceFragment == null && targetFragment == null)
30+
{
31+
return;
32+
}
33+
34+
if (sourceFragment == null || targetFragment == null)
35+
{
36+
comparisonContext.AddOpenApiDifference(
37+
new OpenApiDifference
38+
{
39+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update,
40+
SourceValue = sourceFragment,
41+
TargetValue = targetFragment,
42+
OpenApiComparedElementType = typeof(IDictionary<string, T>),
43+
Pointer = comparisonContext.PathString
44+
});
45+
46+
return;
47+
}
48+
49+
var newKeysInTarget = targetFragment.Keys.Except(sourceFragment.Keys).ToList();
50+
51+
foreach (var newKeyInTarget in newKeysInTarget)
52+
{
53+
WalkAndAddOpenApiDifference(
54+
comparisonContext,
55+
newKeyInTarget,
56+
new OpenApiDifference
57+
{
58+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add,
59+
TargetValue = new KeyValuePair<string, T>(
60+
newKeyInTarget,
61+
targetFragment[newKeyInTarget]),
62+
OpenApiComparedElementType = typeof(KeyValuePair<string, T>)
63+
});
64+
}
65+
66+
foreach (var source in sourceFragment)
67+
{
68+
if (targetFragment.Keys.Contains(source.Key))
69+
{
70+
WalkAndCompare(comparisonContext, source.Key,
71+
() => comparisonContext
72+
.GetComparer<T>()
73+
.Compare(source.Value, targetFragment[source.Key], comparisonContext));
74+
}
75+
else
76+
{
77+
WalkAndAddOpenApiDifference(
78+
comparisonContext,
79+
source.Key,
80+
new OpenApiDifference
81+
{
82+
OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove,
83+
SourceValue = source,
84+
OpenApiComparedElementType = typeof(KeyValuePair<string, T>)
85+
});
86+
}
87+
}
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)