11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT license.
33
4- using System ;
4+ using System . Text . Json . Nodes ;
5+ using Microsoft . OpenApi . Reader ;
56
67namespace Microsoft . OpenApi
78{
@@ -23,9 +24,108 @@ public static class OpenApiDocumentRules
2324 if ( item . Info == null )
2425 {
2526 context . CreateError ( nameof ( OpenApiDocumentFieldIsMissing ) ,
26- String . Format ( SRResource . Validation_FieldIsRequired , "info" , "document" ) ) ;
27+ string . Format ( SRResource . Validation_FieldIsRequired , "info" , "document" ) ) ;
2728 }
2829 context . Exit ( ) ;
2930 } ) ;
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+ }
30130 }
31131}
0 commit comments