Skip to content

Commit 8c0875d

Browse files
authored
Merge pull request #15 from MobileTeleSystems/feature/14_external_ref
External reference not resolve
2 parents 918596f + 716c086 commit 8c0875d

File tree

10 files changed

+150
-16
lines changed

10 files changed

+150
-16
lines changed

src/ApiCodeGenerator.AsyncApi/ApiCodeGenerator.AsyncApi.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828
<ItemGroup>
2929
<PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="$(NJsonSchemaVersion)" />
30-
<PackageReference Include="YamlDotNet" Version="11.2.1" />
30+
<PackageReference Include="NJsonSchema.Yaml" Version="$(NJsonSchemaVersion)" />
31+
<PackageReference Include="YamlDotNet" Version="16.2.0" />
3132
</ItemGroup>
3233

3334
<ItemGroup>

src/ApiCodeGenerator.AsyncApi/AsyncApiContentGenerator.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,23 @@ private static async Task<AsyncApiDocument> LoadDocumentAsync(GeneratorContext c
9292
var data = await context.DocumentReader!.ReadToEndAsync();
9393
data = InvokePreprocessors<string>(data, context.Preprocessors, context.DocumentPath, context.Logger);
9494

95-
var documentTask = data.StartsWith("{")
96-
? AsyncApiDocument.FromJsonAsync(data)
97-
: AsyncApiDocument.FromYamlAsync(data);
98-
var document = await documentTask.ConfigureAwait(false);
95+
AsyncApiDocument document;
96+
try
97+
{
98+
document = await AsyncApiDocument.FromJsonAsync(data, context.DocumentPath).ConfigureAwait(false);
99+
}
100+
catch (JsonException ex)
101+
{
102+
try
103+
{
104+
document = await AsyncApiDocument.FromYamlAsync(data, context.DocumentPath).ConfigureAwait(false);
105+
}
106+
catch (YamlDotNet.Core.YamlException ex2)
107+
{
108+
throw new InvalidOperationException(
109+
$"Can not read document as JSON ({ex.Message}) or YAML ({ex2.Message}).");
110+
}
111+
}
99112

100113
document = InvokePreprocessors<AsyncApiDocument>(document, context.Preprocessors, context.DocumentPath, context.Logger);
101114
return document;

src/ApiCodeGenerator.AsyncApi/DOM/AsyncApiDocument.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
using Newtonsoft.Json.Linq;
33
using NJsonSchema;
44
using NJsonSchema.Generation;
5+
using NJsonSchema.Yaml;
56
using YamlDotNet.Serialization;
67

78
namespace ApiCodeGenerator.AsyncApi.DOM
89
{
910
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
10-
public class AsyncApiDocument
11+
public class AsyncApiDocument : IDocumentPathProvider
1112
{
1213
private static readonly JsonSerializerSettings JSONSERIALIZERSETTINGS = new()
1314
{
@@ -41,14 +42,27 @@ public class AsyncApiDocument
4142
[JsonProperty("externalDocs", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
4243
public ICollection<ExternalDocumentation>? ExternalDocs { get; set; }
4344

45+
[JsonIgnore]
46+
public string? DocumentPath { get; set; }
47+
4448
/// <summary>
4549
/// Load document from JSON text.
4650
/// </summary>
4751
/// <param name="data">JSON text.</param>
4852
/// <returns>AsyncApi document object model.</returns>
4953
public static Task<AsyncApiDocument> FromJsonAsync(string data)
54+
=> FromJsonAsync(data, null);
55+
56+
/// <summary>
57+
/// Load document from JSON text.
58+
/// </summary>
59+
/// <param name="data">JSON text.</param>
60+
/// <param name="documentPath"> Path to document. </param>
61+
/// <returns>AsyncApi document object model.</returns>
62+
public static Task<AsyncApiDocument> FromJsonAsync(string data, string? documentPath)
5063
{
5164
var document = JsonConvert.DeserializeObject<AsyncApiDocument>(data, JSONSERIALIZERSETTINGS)!;
65+
document.DocumentPath = documentPath;
5266
return UpdateSchemaReferencesAsync(document);
5367
}
5468

@@ -58,6 +72,15 @@ public static Task<AsyncApiDocument> FromJsonAsync(string data)
5872
/// <param name="data">YAML text.</param>
5973
/// <returns>AsyncApi document object model.</returns>
6074
public static Task<AsyncApiDocument> FromYamlAsync(string data)
75+
=> FromYamlAsync(data, null);
76+
77+
/// <summary>
78+
/// Load document from YAML text.
79+
/// </summary>
80+
/// <param name="data">YAML text.</param>
81+
/// <param name="documentPath"> Path to document. </param>
82+
/// <returns>AsyncApi document object model.</returns>
83+
public static Task<AsyncApiDocument> FromYamlAsync(string data, string? documentPath)
6184
{
6285
var deserializer = new DeserializerBuilder().Build();
6386
using var reader = new StringReader(data);
@@ -66,14 +89,17 @@ public static Task<AsyncApiDocument> FromYamlAsync(string data)
6689
var jObject = JObject.FromObject(yamlDocument)!;
6790
var serializer = JsonSerializer.Create(JSONSERIALIZERSETTINGS);
6891
var doc = jObject.ToObject<AsyncApiDocument>(serializer)!;
92+
doc.DocumentPath = documentPath;
6993
return UpdateSchemaReferencesAsync(doc);
7094
}
7195

72-
private static Task<AsyncApiDocument> UpdateSchemaReferencesAsync(AsyncApiDocument document)
73-
=> JsonSchemaReferenceUtilities.UpdateSchemaReferencesAsync(
74-
document,
75-
new(new AsyncApiSchemaResolver(document, new SystemTextJsonSchemaGeneratorSettings())))
76-
.ContinueWith(t => document);
96+
private static async Task<AsyncApiDocument> UpdateSchemaReferencesAsync(AsyncApiDocument document)
97+
{
98+
await JsonSchemaReferenceUtilities.UpdateSchemaReferencesAsync(
99+
document,
100+
new JsonAndYamlReferenceResolver(new AsyncApiSchemaResolver(document, new SystemTextJsonSchemaGeneratorSettings())));
101+
return document;
102+
}
77103
}
78104
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
79105
}

src/ApiCodeGenerator.OpenApi/ApiCodeGenerator.OpenApi.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
<ItemGroup>
1717
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="$(NswagVersion)" />
1818
<PackageReference Include="NSwag.Core.Yaml" Version="$(NswagVersion)" />
19+
<PackageReference Include="YamlDotNet" Version="16.2.0" />
1920
</ItemGroup>
2021

2122
<ItemGroup>
2223
<ProjectReference Include="..\ApiCodeGenerator.Abstraction\ApiCodeGenerator.Abstraction.csproj" />
2324
</ItemGroup>
24-
</Project>
25+
</Project>

src/ApiCodeGenerator.OpenApi/ContentGeneratorBase.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using NSwag;
1212
using NSwag.CodeGeneration;
1313
using NSwag.CodeGeneration.CSharp;
14+
using YamlDotNet.Core;
1415

1516
namespace ApiCodeGenerator.OpenApi
1617
{
@@ -126,9 +127,24 @@ protected static async Task<OpenApiDocument> ReadAndProcessOpenApiDocument(Gener
126127
var documentStr = context.DocumentReader!.ReadToEnd();
127128
documentStr = InvokePreprocessors<string>(documentStr, context.Preprocessors, context.DocumentPath, context.Logger);
128129

129-
var openApiDocument = !(documentStr.StartsWith("{") && documentStr.EndsWith("}"))
130-
? await OpenApiYamlDocument.FromYamlAsync(documentStr)
131-
: await OpenApiDocument.FromJsonAsync(documentStr);
130+
OpenApiDocument openApiDocument;
131+
132+
try
133+
{
134+
openApiDocument = await OpenApiDocument.FromJsonAsync(documentStr, context.DocumentPath);
135+
}
136+
catch (JsonException ex)
137+
{
138+
try
139+
{
140+
openApiDocument = await OpenApiYamlDocument.FromYamlAsync(documentStr, context.DocumentPath);
141+
}
142+
catch (YamlException ex2)
143+
{
144+
throw new InvalidOperationException(
145+
$"Can not read document as JSON ({ex.Message}) or YAML ({ex2.Message}).");
146+
}
147+
}
132148

133149
openApiDocument = InvokePreprocessors<OpenApiDocument>(openApiDocument, context.Preprocessors, context.DocumentPath, context.Logger);
134150
return openApiDocument;

test/ApiCodeGenerator.AsyncApi.Tests/AsyncApiContentGeneratorTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,22 @@ public async Task LoadApiDocument_WithModelPreprocess()
164164
Assert.That(sch, Is.EqualTo("{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"properties\":{\"processedModel\":{}}}"));
165165
}
166166

167+
[TestCase("externalRef.json")]
168+
[TestCase("externalRef.yaml")]
169+
public async Task LoadApiDocument_WithExternalRef(string documentPath)
170+
{
171+
var settingsJson = new JObject();
172+
var context = CreateContext(settingsJson);
173+
context.DocumentReader = await TestHelpers.LoadApiDocumentAsync(documentPath);
174+
context.DocumentPath = documentPath;
175+
176+
var contentGenerator = (FakeContentGenerator)await FakeContentGenerator.CreateAsync(context);
177+
178+
var document = contentGenerator.Document;
179+
180+
Assert.NotNull(document.Components?.Messages["lightMeasured"].Reference);
181+
}
182+
167183
private static Func<Type, Newtonsoft.Json.JsonSerializer?, IReadOnlyDictionary<string, string>?, object?> GetSettingsFactory(string json)
168184
=> (t, s, v) => (s ?? new()).Deserialize(new StringReader(json), t);
169185

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"asyncapi": "2.6.0",
3+
"info": {
4+
"title": "Document with reference to external schema",
5+
"version": "1.0"
6+
},
7+
"channels": {},
8+
"components": {
9+
"messages": {
10+
"lightMeasured": {
11+
"$ref": "asyncapi.json#/components/messages/lightMeasured"
12+
}
13+
}
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
asyncapi: "2.6.0"
2+
info:
3+
title: Document with reference to external schema
4+
version: "1.0"
5+
channels: {}
6+
components:
7+
messages:
8+
lightMeasured:
9+
$ref: asyncapi.yml#/components/messages/lightMeasured

test/ApiCodeGenerator.OpenApi.Tests/ContentGeneratorFactoryTests.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,32 @@ public async Task LoadOpenApiDocument_WithModelPreprocess()
158158
public async Task LoadOpenApiDocument_FromYaml()
159159
{
160160
var context = CreateContext(new());
161-
var schemaText = await File.ReadAllTextAsync(TestHelpers.GetSwaggerPath("testSchema.yaml"));
161+
context.DocumentPath = TestHelpers.GetSwaggerPath("testSchema.yaml");
162+
var schemaText = await File.ReadAllTextAsync(context.DocumentPath);
162163
context.DocumentReader = new StringReader(schemaText);
163164

164165
var gen = (CSharpClientContentGenerator)await CSharpClientContentGenerator.CreateAsync(context);
165166

166167
var openApiDocument = GetDocument(gen.Generator);
167168

168169
Assert.NotNull(openApiDocument);
170+
Assert.NotNull(openApiDocument!.DocumentPath);
171+
}
172+
173+
[TestCase("externalRef.json")]
174+
public async Task LoadOpenApiDocument_ExternalRef(string documentPath)
175+
{
176+
var context = CreateContext(new());
177+
context.DocumentPath = TestHelpers.GetSwaggerPath(documentPath);
178+
var documentContent = await File.ReadAllTextAsync(context.DocumentPath);
179+
context.DocumentReader = new StringReader(documentContent);
180+
181+
var gen = (CSharpClientContentGenerator)await CSharpClientContentGenerator.CreateAsync(context);
182+
183+
var openApiDocument = GetDocument(gen.Generator);
184+
185+
Assert.NotNull(openApiDocument);
186+
Assert.NotNull(openApiDocument!.Definitions["test"].AllOf.Single().Reference);
169187
}
170188

171189
private GeneratorContext CreateContext(JObject settingsJson, Core.ExtensionManager.Extensions? extension = null)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"openapi": "3.0.0",
3+
"components": {
4+
"schemas": {
5+
"test": {
6+
"allOf": [
7+
{
8+
"$ref": "testSchema.json#/definitions/testOperResponse"
9+
}
10+
],
11+
"properties": {
12+
"prop": {
13+
"type": "integer"
14+
}
15+
}
16+
}
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)