Skip to content

Commit f64e23e

Browse files
committed
Fixed location normalization
1 parent 2888256 commit f64e23e

File tree

3 files changed

+153
-27
lines changed

3 files changed

+153
-27
lines changed

src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,102 @@ public IEnumerable<OpenApiDocument> Documents {
4545
/// </summary>
4646
public IEnumerable<Stream> Artifacts { get; }
4747

48+
/// <summary>
49+
/// Initialize workspace pointing to a base URL to allow resolving relative document locations. Use a file:// url to point to a folder
50+
/// </summary>
51+
/// <param name="baseUrl"></param>
52+
public OpenApiWorkspace(Uri baseUrl)
53+
{
54+
BaseUrl = baseUrl;
55+
}
56+
57+
/// <summary>
58+
/// Initialize workspace using current directory as the default location.
59+
/// </summary>
60+
public OpenApiWorkspace()
61+
{
62+
BaseUrl = new Uri("file://" + Environment.CurrentDirectory + "\\" );
63+
}
64+
65+
/// <summary>
66+
/// Verify if workspace contains a document based on its URL.
67+
/// </summary>
68+
/// <param name="location">A relative or absolute URL of the file. Use file:// for folder locations.</param>
69+
/// <returns>Returns true if a matching document is found.</returns>
4870
public bool Contains(string location)
4971
{
50-
return _documents.ContainsKey(ToLocationUrl(location));
72+
Uri key = ToLocationUrl(location);
73+
return _documents.ContainsKey(key) || _fragments.ContainsKey(key) || _artifacts.ContainsKey(key);
5174
}
5275

76+
/// <summary>
77+
/// Add an OpenApiDocument to the workspace.
78+
/// </summary>
79+
/// <param name="location"></param>
80+
/// <param name="document"></param>
5381
public void AddDocument(string location, OpenApiDocument document)
5482
{
5583
document.Workspace = this;
5684
_documents.Add(ToLocationUrl(location), document);
5785
}
5886

59-
public void AddFragment(string location, IOpenApiElement fragment)
87+
/// <summary>
88+
/// Adds a fragment of an OpenApiDocument to the workspace.
89+
/// </summary>
90+
/// <param name="location"></param>
91+
/// <param name="fragment"></param>
92+
/// <remarks>Not sure how this is going to work. Does the reference just point to the fragment as a whole, or do we need to
93+
/// to be able to point into the fragment. Keeping it private until we figure it out.
94+
/// </remarks>
95+
private void AddFragment(string location, IOpenApiElement fragment)
6096
{
6197
_fragments.Add(ToLocationUrl(location), fragment);
6298
}
6399

100+
/// <summary>
101+
/// Add a stream based artificat to the workspace. Useful for images, examples, alternative schemas.
102+
/// </summary>
103+
/// <param name="location"></param>
104+
/// <param name="artifact"></param>
64105
public void AddArtifact(string location, Stream artifact)
65106
{
66107
_artifacts.Add(ToLocationUrl(location), artifact);
67108
}
68109

110+
/// <summary>
111+
/// Returns the target of an OpenApiReference from within the workspace.
112+
/// </summary>
113+
/// <param name="reference">An instance of an OpenApiReference</param>
114+
/// <returns></returns>
69115
public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
70116
{
71-
if (!_documents.TryGetValue(new Uri(reference.ExternalResource,UriKind.RelativeOrAbsolute),out var doc))
117+
if (_documents.TryGetValue(new Uri(BaseUrl,reference.ExternalResource),out var doc))
72118
{
73-
return null;
119+
return doc.ResolveReference(reference, true);
74120
}
75-
76-
return doc.ResolveReference(reference,true);
121+
else if (_fragments.TryGetValue(new Uri(BaseUrl, reference.ExternalResource), out var fragment))
122+
{
123+
var frag = fragment as IOpenApiReferenceable;
124+
if (frag != null)
125+
{
126+
return null; // frag.ResolveReference(reference, true); // IOpenApiElement needs to implement ResolveReference
127+
}
128+
else
129+
{
130+
return null;
131+
}
132+
}
133+
return null;
134+
}
135+
136+
/// <summary>
137+
///
138+
/// </summary>
139+
/// <param name="location"></param>
140+
/// <returns></returns>
141+
public Stream GetArtifact(string location)
142+
{
143+
return _artifacts[ToLocationUrl(location)];
77144
}
78145

79146
private Uri ToLocationUrl(string location)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Microsoft.OpenApi.Models;
2+
using Microsoft.OpenApi.Readers;
3+
using Microsoft.OpenApi.Services;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net;
8+
using System.Net.Http;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Xunit;
12+
using Xunit.Abstractions;
13+
14+
namespace Microsoft.OpenApi.SmokeTests
15+
{
16+
public class GraphTests
17+
{
18+
OpenApiDocument _graphOpenApi;
19+
HttpClient _httpClient;
20+
private readonly ITestOutputHelper _output;
21+
const string graphOpenApiUrl = "https://github.com/microsoftgraph/microsoft-graph-openapi/blob/master/v1.0.json?raw=true";
22+
23+
public GraphTests(ITestOutputHelper output)
24+
{
25+
_output = output;
26+
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
27+
_httpClient = new HttpClient(new HttpClientHandler()
28+
{ AutomaticDecompression = DecompressionMethods.GZip
29+
});
30+
_httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
31+
_httpClient.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("OpenApi.Net.Tests", "1.0"));
32+
33+
var response = _httpClient.GetAsync(graphOpenApiUrl)
34+
.GetAwaiter().GetResult();
35+
36+
if (!response.IsSuccessStatusCode)
37+
{
38+
_output.WriteLine($"Couldn't load graph openapi");
39+
return;
40+
}
41+
42+
var stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); ;
43+
44+
var reader = new OpenApiStreamReader();
45+
_graphOpenApi = reader.Read(stream, out var diagnostic);
46+
47+
if (diagnostic.Errors.Count > 0)
48+
{
49+
_output.WriteLine($"Errors parsing");
50+
_output.WriteLine(String.Join("\n", diagnostic.Errors));
51+
// Assert.True(false); // Uncomment to identify descriptions with errors.
52+
}
53+
54+
55+
}
56+
57+
[Fact]
58+
public void LoadOpen()
59+
{
60+
var operations = new[] { "foo","bar" };
61+
var workspace = new OpenApiWorkspace();
62+
workspace.AddDocument(graphOpenApiUrl, _graphOpenApi);
63+
var subset = new OpenApiDocument();
64+
workspace.AddDocument("subset", subset);
65+
subset.
66+
67+
Assert.NotNull(_graphOpenApi);
68+
}
69+
}
70+
}

test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -125,34 +125,25 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short()
125125
});
126126

127127
workspace.AddDocument("root", doc);
128-
129128
workspace.AddDocument("common", CreateCommonDocument());
130-
131129
doc.ResolveReferences(true);
132130

133131
var schema = doc.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema;
134132
Assert.False(schema.UnresolvedReference);
135133
}
136134

137-
138135
[Fact]
139136
public void OpenApiWorkspacesShouldNormalizeDocumentLocations()
140137
{
141-
// what does normalize mean?
142-
// If we use Urls as locators then normalization happens automatically.
143-
144-
// How do we set a base location for a workspace?
145-
// A base could be a folder. Should we use file://
146-
// A base could be a root url
147-
// Are absolute locations allowed?
148-
// Can a base URI change once a workspace has been created?
149-
// What should be the default base URL?
150-
// Can we infer it from a root document?
151-
// Is the root document the first document loaded?
152-
// Can we load multiple APIs into a Workspace? Does root document make sense?
153-
// What data type should "location" really be? Is it a Uri?
154-
//
155-
Assert.True(false);
138+
var workspace = new OpenApiWorkspace();
139+
workspace.AddDocument("hello", new OpenApiDocument());
140+
workspace.AddDocument("hi", new OpenApiDocument());
141+
142+
Assert.True(workspace.Contains("./hello"));
143+
Assert.True(workspace.Contains("./foo/../hello"));
144+
Assert.True(workspace.Contains("file://" + Environment.CurrentDirectory + "/./foo/../hello"));
145+
146+
Assert.False(workspace.Contains("./goodbye"));
156147
}
157148

158149
// Enable Workspace to load from any reader, not just streams.
@@ -163,6 +154,7 @@ public void OpenApiWorkspacesShouldLoadDocumentFragments()
163154
Assert.True(false);
164155
}
165156

157+
166158
// Test artifacts
167159

168160
private static OpenApiDocument CreateCommonDocument()
@@ -182,9 +174,6 @@ private static OpenApiDocument CreateCommonDocument()
182174
}
183175
}
184176

185-
186-
187-
188177
public static class OpenApiFactoryExtensions {
189178

190179
public static OpenApiDocument CreatePathItem(this OpenApiDocument document, string path, Action<OpenApiPathItem> config)

0 commit comments

Comments
 (0)