-
Notifications
You must be signed in to change notification settings - Fork 276
Expand file tree
/
Copy pathOpenApiDocumentRules.cs
More file actions
126 lines (114 loc) · 4.52 KB
/
OpenApiDocumentRules.cs
File metadata and controls
126 lines (114 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.OpenApi
{
/// <summary>
/// The validation rules for <see cref="OpenApiDocument"/>.
/// </summary>
[OpenApiRule]
public static class OpenApiDocumentRules
{
/// <summary>
/// The Info field is required.
/// </summary>
public static ValidationRule<OpenApiDocument> OpenApiDocumentFieldIsMissing =>
new(nameof(OpenApiDocumentFieldIsMissing),
(context, item) =>
{
// info
if (item.Info == null)
{
context.Enter("info");
context.CreateError(nameof(OpenApiDocumentFieldIsMissing),
string.Format(SRResource.Validation_FieldIsRequired, "info", "document"));
context.Exit();
}
});
/// <summary>
/// All references in the OpenAPI document must be valid.
/// </summary>
public static ValidationRule<OpenApiDocument> OpenApiDocumentReferencesAreValid =>
new(nameof(OpenApiDocumentReferencesAreValid),
static (context, item) =>
{
const string RuleName = nameof(OpenApiDocumentReferencesAreValid);
var visitor = new OpenApiSchemaReferenceVisitor(RuleName, context);
var walker = new OpenApiWalker(visitor);
walker.Walk(item);
});
private sealed class OpenApiSchemaReferenceVisitor(
string ruleName,
IValidationContext context) : OpenApiVisitorBase
{
public override void Visit(IOpenApiReferenceHolder referenceHolder)
{
if (referenceHolder is OpenApiSchemaReference reference)
{
ValidateSchemaReference(reference);
}
}
public override void Visit(IOpenApiSchema schema)
{
if (schema is OpenApiSchemaReference reference)
{
ValidateSchemaReference(reference);
}
}
private void ValidateSchemaReference(OpenApiSchemaReference reference)
{
if (!reference.Reference.IsLocal)
{
return;
}
try
{
if (reference.RecursiveTarget is null)
{
var segments = GetSegments().ToArray();
EnterSegments(segments);
// The reference was not followed to a valid schema somewhere in the document
context.CreateWarning(ruleName, string.Format(SRResource.Validation_SchemaReferenceDoesNotExist, reference.Reference.ReferenceV3));
ExitSegments(segments.Length);
}
}
catch (InvalidOperationException ex)
{
var segments = GetSegments().ToArray();
EnterSegments(segments);
context.CreateWarning(ruleName, ex.Message);
ExitSegments(segments.Length);
}
void ExitSegments(int length)
{
for (var i = 0; i < length; i++)
{
context.Exit();
}
}
void EnterSegments(string[] segments)
{
foreach (var segment in segments)
{
context.Enter(segment);
}
}
IEnumerable<string> GetSegments()
{
foreach (var segment in this.PathString.Substring(2).Split('/'))
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP1_0_OR_GREATER
yield return segment.Replace("~1", "/", StringComparison.OrdinalIgnoreCase).Replace("~0", "~", StringComparison.OrdinalIgnoreCase);
#else
yield return segment.Replace("~1", "/").Replace("~0", "~");
#endif
}
yield return "$ref";
// Trim off the leading "#/" as the context is already at the root of the document
}
}
}
}
}