Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e4bc14a
experiements with workspace
darrelmiller Feb 7, 2018
c82c3cf
Fixed build
darrelmiller Feb 10, 2018
4b8b343
Merged with latest fixes for local references
darrelmiller Feb 10, 2018
dffad37
Skeleton code for OpenApiWorkspace
darrelmiller Feb 12, 2018
3d37908
Added comment
darrelmiller Feb 13, 2018
8c1493b
Merged latest changes from ref-overflow
darrelmiller Feb 13, 2018
b332d86
Merge remote-tracking branch 'origin/master' into dm/openapiworkspace
darrelmiller Mar 16, 2018
60ef50e
Workspace reference
darrelmiller Mar 18, 2018
d8df45b
Merge master
darrelmiller Mar 28, 2018
a5667a0
Who knows what is in here!
darrelmiller Mar 31, 2018
d42bd94
Resolved merge conflicts
darrelmiller Apr 8, 2018
e5893b6
Moved reference resolution into the OpenApi model project
darrelmiller Apr 8, 2018
af0b5fc
More workspace fixes
darrelmiller Apr 11, 2018
8b95987
Merge remote-tracking branch 'origin/master' into dm/openapiworkspace
darrelmiller Apr 17, 2018
1e735f4
Fixed merge conflict
darrelmiller May 20, 2018
24bb7f4
Merge remote-tracking branch 'origin/vnext' into dm/openapiworkspace
darrelmiller May 20, 2018
806c067
Fixed merge errors
darrelmiller May 20, 2018
78e7eee
Work on baseURL for workspace
darrelmiller May 21, 2018
2888256
Merge remote-tracking branch 'origin/master' into dm/openapiworkspace
darrelmiller May 24, 2018
f64e23e
Fixed location normalization
darrelmiller Oct 29, 2018
a09875d
Disabled smoke tests, all tests pass
darrelmiller May 17, 2020
46df7d2
Merged vnext
darrelmiller May 17, 2020
ccbef09
WIP; add OpenApiReferencableExtensions
simon-pearson-lmk Nov 8, 2020
b7c37fc
Referencable elements can resolve a reference with an empty JSON poin…
simon-pearson-lmk Nov 8, 2020
afcbce9
Resolve reference to 'schema' property on Parameter element
simon-pearson-lmk Nov 8, 2020
6c69605
Throw exception when we try to reference a type that cannot be refere…
simon-pearson-lmk Nov 8, 2020
7db3ea7
Refactoring of OpenApiReferencable units; add parameterised tests rep…
simon-pearson-lmk Nov 11, 2020
4eb1c6c
Add resolving references to child properties of a Header fragment
simon-pearson-lmk Nov 11, 2020
4b50cc5
Further unit tests covering ResolveReference method on IOpenApiRefere…
simon-pearson-lmk Nov 11, 2020
c86fd89
Resolve references to child properties of Response fragments
simon-pearson-lmk Nov 11, 2020
23c8417
Update implementation of OpenApiWorkspace.ResolveReference to support…
simon-pearson-lmk Sep 5, 2020
56f4524
Improve comments in OpenApiReferencableExtensions
simon-pearson-lmk Nov 11, 2020
e3ca80e
Refactoring in OpenApiReferencableExtensions to reduce duplicated code
simon-pearson-lmk Nov 12, 2020
dda0fe4
Change IOpenApiReferenceable.ResolveReference method to using the Jso…
simon-pearson-lmk Nov 13, 2020
62b7e63
The OpenApiStreamReader.ReadFragment method may only read fragments t…
simon-pearson-lmk Nov 13, 2020
60869f6
Merge pull request #520 from simon-pearson/feature/openapi-workspace-…
darrelmiller Nov 21, 2020
35a9fc5
Merged master
darrelmiller Nov 22, 2020
2ddc0a7
Updated openapiworkspace from vnext
darrelmiller Dec 19, 2020
1debbf2
Updated OpenApiWorkspaceLoader to support loading external references
darrelmiller Dec 21, 2020
d229688
Moved OpenAPIWorkspace to sync loading
darrelmiller Jan 30, 2021
65b70f2
Enabled async read for workspace
darrelmiller Feb 22, 2021
6b4e2ce
Merge branch 'vnext' into dm/openapiworkspace
darrelmiller Feb 28, 2021
5090c01
Support loading an OpenApiDocument with remote references.
darrelmiller Mar 3, 2021
36515c4
Added comments
darrelmiller Mar 3, 2021
672508e
Fixed warnings and updated to just .net standard
darrelmiller Mar 14, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#03323C",
"titleBar.activeBackground": "#054754",
"titleBar.activeForeground": "#F0FCFE"
}
}
30 changes: 30 additions & 0 deletions src/Microsoft.OpenApi.Readers/Interface/IStreamLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.OpenApi.Models;

namespace Microsoft.OpenApi.Readers.Interface
{
/// <summary>
/// Interface for service that translates a URI into a stream that can be loaded by a Reader
/// </summary>
public interface IStreamLoader
{
/// <summary>
/// Use Uri to locate data and convert into an input object.
/// </summary>
/// <param name="uri">Identifier of some source of an OpenAPI Description</param>
/// <returns>A data objext that can be processed by a reader to generate an <see cref="OpenApiDocument"/></returns>
Task<Stream> LoadAsync(Uri uri);

/// <summary>
/// Use Uri to locate data and convert into an input object.
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
Stream Load(Uri uri);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net46; netstandard2.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288890</PackageIconUrl>
<PackageProjectUrl>https://github.com/Microsoft/OpenAPI.NET</PackageProjectUrl>
<PackageLicenseUrl>https://raw.githubusercontent.com/Microsoft/OpenAPI.NET/master/LICENSE</PackageLicenseUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Authors>Microsoft</Authors>
<Company>Microsoft</Company>
Expand All @@ -26,7 +26,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SharpYaml" Version="1.6.5" />
<PackageReference Include="SharpYaml" Version="1.6.6" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Readers.Interface;
using Microsoft.OpenApi.Readers.ParseNodes;
using Microsoft.OpenApi.Readers.Services;
using Microsoft.OpenApi.Validations;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -56,5 +59,13 @@ public class OpenApiReaderSettings
/// URL where relative references should be resolved from if the description does not contain Server definitions
/// </summary>
public Uri BaseUrl { get; set; }

/// <summary>
/// Function used to provide an alternative loader for accessing external references.
/// </summary>
/// <remarks>
/// Default loader will attempt to dereference http(s) urls and file urls.
/// </remarks>
public IStreamLoader CustomExternalLoader { get; set; }
}
}
29 changes: 28 additions & 1 deletion src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.

using System.IO;
using System.Threading.Tasks;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers.Interface;
Expand Down Expand Up @@ -38,14 +39,40 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic)
}
}

/// <summary>
/// Reads the stream input and parses it into an Open API document.
/// </summary>
/// <param name="input">Stream containing OpenAPI description to parse.</param>
/// <returns>Instance result containing newly created OpenApiDocument and diagnostics object from the process</returns>
public async Task<ReadResult> ReadAsync(Stream input)
{
MemoryStream bufferedStream;
if (input is MemoryStream)
{
bufferedStream = (MemoryStream)input;
}
else
{
// Buffer stream so that OpenApiTextReaderReader can process it synchronously
// YamlDocument doesn't support async reading.
bufferedStream = new MemoryStream();
await input.CopyToAsync(bufferedStream);
bufferedStream.Position = 0;
}

var reader = new StreamReader(bufferedStream);

return await new OpenApiTextReaderReader(_settings).ReadAsync(reader);
}

/// <summary>
/// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element.
/// </summary>
/// <param name="input">Stream containing OpenAPI description to parse.</param>
/// <param name="version">Version of the OpenAPI specification that the fragment conforms to.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
/// <returns>Instance of newly created OpenApiDocument</returns>
public T ReadFragment<T>(Stream input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement
public T ReadFragment<T>(Stream input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiReferenceable
{
using (var reader = new StreamReader(input))
{
Expand Down
31 changes: 31 additions & 0 deletions src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers.Interface;
Expand Down Expand Up @@ -51,6 +52,36 @@ public OpenApiDocument Read(TextReader input, out OpenApiDiagnostic diagnostic)

return new OpenApiYamlDocumentReader(this._settings).Read(yamlDocument, out diagnostic);
}

/// <summary>
/// Reads the content of the TextReader. If there are references to external documents then they will be read asynchronously.
/// </summary>
/// <param name="input">TextReader containing OpenAPI description to parse.</param>
/// <returns>A ReadResult instance that contains the resulting OpenApiDocument and a diagnostics instance.</returns>
public async Task<ReadResult> ReadAsync(TextReader input)
{
YamlDocument yamlDocument;

// Parse the YAML/JSON text in the TextReader into the YamlDocument
try
{
yamlDocument = LoadYamlDocument(input);
}
catch (YamlException ex)
{
var diagnostic = new OpenApiDiagnostic();
diagnostic.Errors.Add(new OpenApiError($"#line={ex.Start.Line}", ex.Message));
return new ReadResult
{
OpenApiDocument = null,
OpenApiDiagnostic = diagnostic
};
}

return await new OpenApiYamlDocumentReader(this._settings).ReadAsync(yamlDocument);
}


/// <summary>
/// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element.
/// </summary>
Expand Down
117 changes: 101 additions & 16 deletions src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.OpenApi.Exceptions;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
Expand Down Expand Up @@ -50,24 +53,43 @@ public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic
// Parse the OpenAPI Document
document = context.Parse(input);

// Resolve References if requested
switch (_settings.ReferenceResolution)
ResolveReferences(diagnostic, document);
}
catch (OpenApiException ex)
{
diagnostic.Errors.Add(new OpenApiError(ex));
}

// Validate the document
if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0)
{
var errors = document.Validate(_settings.RuleSet);
foreach (var item in errors)
{
case ReferenceResolutionSetting.ResolveAllReferences:
throw new ArgumentException(Properties.SRResource.CannotResolveRemoteReferencesSynchronously);
case ReferenceResolutionSetting.ResolveLocalReferences:
var resolver = new OpenApiReferenceResolver(document);
var walker = new OpenApiWalker(resolver);
walker.Walk(document);
foreach (var item in resolver.Errors)
{
diagnostic.Errors.Add(item);
}
break;
case ReferenceResolutionSetting.DoNotResolveReferences:
break;
diagnostic.Errors.Add(item);
}
}

return document;
}

public async Task<ReadResult> ReadAsync(YamlDocument input)
{
var diagnostic = new OpenApiDiagnostic();
var context = new ParsingContext(diagnostic)
{
ExtensionParsers = _settings.ExtensionParsers,
BaseUrl = _settings.BaseUrl
};

OpenApiDocument document = null;
try
{
// Parse the OpenAPI Document
document = context.Parse(input);

await ResolveReferencesAsync(diagnostic, document);
}
catch (OpenApiException ex)
{
diagnostic.Errors.Add(new OpenApiError(ex));
Expand All @@ -83,8 +105,71 @@ public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic
}
}

return document;
return new ReadResult()
{
OpenApiDocument = document,
OpenApiDiagnostic = diagnostic
};
}


private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document)
{
// Resolve References if requested
switch (_settings.ReferenceResolution)
{
case ReferenceResolutionSetting.ResolveAllReferences:
throw new ArgumentException("Cannot resolve all references via a synchronous call. Use ReadAsync.");
case ReferenceResolutionSetting.ResolveLocalReferences:
var errors = document.ResolveReferences(false);

foreach (var item in errors)
{
diagnostic.Errors.Add(item);
}
break;
case ReferenceResolutionSetting.DoNotResolveReferences:
break;
}
}

private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document)
{
List<OpenApiError> errors = new List<OpenApiError>();

// Resolve References if requested
switch (_settings.ReferenceResolution)
{
case ReferenceResolutionSetting.ResolveAllReferences:

// Create workspace for all documents to live in.
var openApiWorkSpace = new OpenApiWorkspace();

// Load this root document into the workspace
var streamLoader = new DefaultStreamLoader();
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings);
await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document);

// Resolve all references in all the documents loaded into the OpenApiWorkspace
foreach (var doc in openApiWorkSpace.Documents)
{
errors.AddRange(doc.ResolveReferences(true));
}
break;
case ReferenceResolutionSetting.ResolveLocalReferences:
errors.AddRange(document.ResolveReferences(false));
break;
case ReferenceResolutionSetting.DoNotResolveReferences:
break;
}

foreach (var item in errors)
{
diagnostic.Errors.Add(item);
}
}


/// <summary>
/// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element.
/// </summary>
Expand Down
27 changes: 27 additions & 0 deletions src/Microsoft.OpenApi.Readers/ReadResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.OpenApi.Models;

namespace Microsoft.OpenApi.Readers
{
/// <summary>
/// Container object used for returning the result of reading an OpenAPI description.
/// </summary>
public class ReadResult
{
/// <summary>
/// The parsed OpenApiDocument. Null will be returned if the document could not be parsed.
/// </summary>
public OpenApiDocument OpenApiDocument { set; get; }
/// <summary>
/// OpenApiDiagnostic contains the Errors reported while parsing
/// </summary>
public OpenApiDiagnostic OpenApiDiagnostic { set; get; }
}
}
Loading