1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT license.
3
3
4
- using System ;
4
+ using System . Text . Json . Nodes ;
5
+ using Microsoft . OpenApi . Reader ;
5
6
6
7
namespace Microsoft . OpenApi
7
8
{
@@ -23,9 +24,108 @@ public static class OpenApiDocumentRules
23
24
if ( item . Info == null )
24
25
{
25
26
context . CreateError ( nameof ( OpenApiDocumentFieldIsMissing ) ,
26
- String . Format ( SRResource . Validation_FieldIsRequired , "info" , "document" ) ) ;
27
+ string . Format ( SRResource . Validation_FieldIsRequired , "info" , "document" ) ) ;
27
28
}
28
29
context . Exit ( ) ;
29
30
} ) ;
31
+
32
+ /// <summary>
33
+ /// All references in the OpenAPI document must be valid.
34
+ /// </summary>
35
+ public static ValidationRule < OpenApiDocument > OpenApiDocumentReferencesAreValid =>
36
+ new ( nameof ( OpenApiDocumentReferencesAreValid ) ,
37
+ static ( context , item ) =>
38
+ {
39
+ const string RuleName = nameof ( OpenApiDocumentReferencesAreValid ) ;
40
+
41
+ JsonNode document ;
42
+
43
+ using ( var textWriter = new System . IO . StringWriter ( ) )
44
+ {
45
+ var writer = new OpenApiJsonWriter ( textWriter ) ;
46
+
47
+ item . SerializeAsV31 ( writer ) ;
48
+
49
+ var json = textWriter . ToString ( ) ;
50
+
51
+ document = JsonNode . Parse ( json ) ! ;
52
+ }
53
+
54
+ var visitor = new OpenApiSchemaReferenceVisitor ( RuleName , context , document ) ;
55
+ var walker = new OpenApiWalker ( visitor ) ;
56
+
57
+ walker . Walk ( item ) ;
58
+ } ) ;
59
+
60
+ private sealed class OpenApiSchemaReferenceVisitor (
61
+ string ruleName ,
62
+ IValidationContext context ,
63
+ JsonNode document ) : OpenApiVisitorBase
64
+ {
65
+ public override void Visit ( IOpenApiReferenceHolder referenceHolder )
66
+ {
67
+ if ( referenceHolder is OpenApiSchemaReference { Reference . IsLocal : true } reference )
68
+ {
69
+ ValidateSchemaReference ( reference ) ;
70
+ }
71
+ }
72
+
73
+ public override void Visit ( IOpenApiSchema schema )
74
+ {
75
+ if ( schema is OpenApiSchemaReference { Reference . IsLocal : true } reference )
76
+ {
77
+ ValidateSchemaReference ( reference ) ;
78
+ }
79
+ }
80
+
81
+ private void ValidateSchemaReference ( OpenApiSchemaReference reference )
82
+ {
83
+ var id = reference . Reference . ReferenceV3 ;
84
+
85
+ if ( id is { Length : > 0 } && ! IsValidSchemaReference ( id , document ) )
86
+ {
87
+ var isValid = false ;
88
+
89
+ // Sometimes ReferenceV3 is not a JSON valid JSON pointer, but the $ref
90
+ // associated with it still points to a valid location in the document.
91
+ // In these cases, we need to find it manually to verify that fact before
92
+ // generating a warning that the schema reference is indeed invalid.
93
+ // TODO Why is this, and can it be avoided?
94
+ var parent = Find ( PathString , document ) ;
95
+
96
+ if ( parent ? [ "$ref" ] is { } @ref &&
97
+ @ref . GetValueKind ( ) is System . Text . Json . JsonValueKind . String &&
98
+ @ref . GetValue < string > ( ) is { Length : > 0 } refId )
99
+ {
100
+ id = refId ;
101
+ isValid = IsValidSchemaReference ( id , document ) ;
102
+ }
103
+
104
+ if ( ! isValid )
105
+ {
106
+ // Trim off the leading "#/" as the context is already at the root of the document
107
+ var segment =
108
+ #if NET8_0_OR_GREATER
109
+ PathString [ 2 ..] ;
110
+ #else
111
+ PathString . Substring ( 2 ) ;
112
+ #endif
113
+
114
+ context. Enter ( segment ) ;
115
+ context . CreateWarning ( ruleName , string . Format ( SRResource . Validation_SchemaReferenceDoesNotExist , id ) ) ;
116
+ context . Exit ( ) ;
117
+ }
118
+ }
119
+
120
+ static bool IsValidSchemaReference ( string id , JsonNode baseNode )
121
+ => Find ( id , baseNode ) is not null ;
122
+
123
+ static JsonNode ? Find ( string id , JsonNode baseNode )
124
+ {
125
+ var pointer = new JsonPointer ( id . Replace ( "#/" , "/" ) ) ;
126
+ return pointer . Find ( baseNode ) ;
127
+ }
128
+ }
129
+ }
30
130
}
31
131
}
0 commit comments