Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f6cc9d8
Add webhooks property to OpenAPI document
MaggieKimani1 Oct 17, 2022
0678365
Deep copy the webhooks object in the copy constructor
MaggieKimani1 Oct 17, 2022
5bb8f44
Add serialization for the webhooks property
MaggieKimani1 Oct 17, 2022
7479780
Add logic to deserialize the webhooks property
MaggieKimani1 Oct 17, 2022
43e165c
Clean up project references
MaggieKimani1 Oct 17, 2022
9efc130
Add pathItem reference type and webhooks constant
MaggieKimani1 Oct 17, 2022
094a980
Add pathItems object to component object
MaggieKimani1 Oct 18, 2022
59fbec8
Add serialization tests for both Yaml and Json
MaggieKimani1 Oct 19, 2022
c79a4f9
Adds tests
MaggieKimani1 Oct 24, 2022
103f123
Adds 3.1 as a valid input OpenAPI version
MaggieKimani1 Oct 24, 2022
624bd0c
Adds a walker to visit the webhooks object and its child elements
MaggieKimani1 Oct 24, 2022
7a14575
Update the validation rule to exclude paths as a required field accor…
MaggieKimani1 Oct 24, 2022
8d004ff
Revert change
MaggieKimani1 Oct 25, 2022
661bd1a
Merge branch 'mk/add-webhooks-property' into mk/add-pathItems-to-comp…
MaggieKimani1 Oct 25, 2022
149175c
Update test with correct property type
MaggieKimani1 Oct 25, 2022
17b1c2d
Add the validation for Paths as a required field in 3.0 during parsing
MaggieKimani1 Oct 26, 2022
c79bd11
Update spec version
MaggieKimani1 Oct 26, 2022
7f30fdf
Merge branch 'mk/add-webhooks-property' into mk/add-pathItems-to-comp…
MaggieKimani1 Oct 26, 2022
b7e3e48
Add more validation for empty paths and missing paths/webhooks for 3.1
MaggieKimani1 Oct 26, 2022
26c61d1
Merge branch 'mk/add-webhooks-property' into mk/add-pathItems-to-comp…
MaggieKimani1 Oct 26, 2022
846fb0e
Use Any() instead of count
MaggieKimani1 Oct 27, 2022
01fefe9
Merge branch 'mk/add-webhooks-property' into mk/add-pathItems-to-comp…
MaggieKimani1 Oct 27, 2022
2299117
Code clean up
MaggieKimani1 Oct 27, 2022
21e19a0
Add negation operator
MaggieKimani1 Oct 27, 2022
7f14ba1
Merge branch 'mk/add-webhooks-property' into mk/add-pathItems-to-comp…
MaggieKimani1 Oct 27, 2022
6152336
Change reference type to pathItem
MaggieKimani1 Oct 27, 2022
4901a65
Merge branch 'mk/add-webhooks-property' into mk/add-pathItems-to-comp…
MaggieKimani1 Oct 27, 2022
86594a8
Reuse LoadPaths() logic to avoid creating a root reference object in …
MaggieKimani1 Oct 31, 2022
243eb23
Update test
MaggieKimani1 Oct 31, 2022
093f761
Merge branch 'mk/add-webhooks-property' into mk/add-pathItems-to-comp…
MaggieKimani1 Oct 31, 2022
7e77070
Get the $ref pointer to a pathItem object and resolve the reference b…
MaggieKimani1 Oct 31, 2022
cd392b7
Clean up
MaggieKimani1 Oct 31, 2022
4eb9a13
Add test
MaggieKimani1 Oct 31, 2022
0223425
Remove whitespace
MaggieKimani1 Oct 31, 2022
deae283
Merge if with the outer else if statement
MaggieKimani1 Nov 1, 2022
07f8f08
Add string extension methods to validate spec versions for easy reuse
MaggieKimani1 Nov 1, 2022
0d51847
Clean up tests
MaggieKimani1 Nov 1, 2022
2595c94
Update API interface
MaggieKimani1 Nov 1, 2022
1d35f0b
Add OrdinalIgnoreCase for string comparison
MaggieKimani1 Nov 1, 2022
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
61 changes: 61 additions & 0 deletions src/Microsoft.OpenApi.Readers/OpenApiVersionExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;

namespace Microsoft.OpenApi.Readers
{
/// <summary>
/// Generates custom extension methods for the version string type
/// </summary>
public static class OpenApiVersionExtensionMethods
{
/// <summary>
/// Extension method for Spec version 2.0
/// </summary>
/// <param name="version"></param>
/// <returns></returns>
public static bool is2_0(this string version)
{
bool result = false;
if (version.Equals("2.0", StringComparison.OrdinalIgnoreCase))
{
result = true;
}

return result;
}

/// <summary>
/// Extension method for Spec version 3.0
/// </summary>
/// <param name="version"></param>
/// <returns></returns>
public static bool is3_0(this string version)
{
bool result = false;
if (version.StartsWith("3.0", StringComparison.OrdinalIgnoreCase))
{
result = true;
}

return result;
}

/// <summary>
/// Extension method for Spec version 3.1
/// </summary>
/// <param name="version"></param>
/// <returns></returns>
public static bool is3_1(this string version)
{
bool result = false;
if (version.StartsWith("3.1", StringComparison.OrdinalIgnoreCase))
{
result = true;
}

return result;
}
}
}
19 changes: 17 additions & 2 deletions src/Microsoft.OpenApi.Readers/ParsingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,18 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument)

switch (inputVersion)
{
case string version when version == "2.0":
case string version when version.is2_0():
VersionService = new OpenApiV2VersionService(Diagnostic);
doc = VersionService.LoadDocument(RootNode);
this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi2_0;
ValidateRequiredFields(doc, version);
break;

case string version when version.StartsWith("3.0"):
case string version when version.is3_0() || version.is3_1():
VersionService = new OpenApiV3VersionService(Diagnostic);
doc = VersionService.LoadDocument(RootNode);
this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_0;
ValidateRequiredFields(doc, version);
break;

default:
Expand Down Expand Up @@ -244,5 +246,18 @@ public void PopLoop(string loopid)
}
}

private void ValidateRequiredFields(OpenApiDocument doc, string version)
{
if ((version.is2_0() || version.is3_0()) && (doc.Paths == null || !doc.Paths.Any()))
{
// paths is a required field in OpenAPI 3.0 but optional in 3.1
RootNode.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}"));
}
else if (version.is3_1() && (doc.Paths == null || !doc.Paths.Any()) && (doc.Webhooks == null || !doc.Webhooks.Any()))
{
RootNode.Context.Diagnostic.Errors.Add(new OpenApiError(
"", $"The document MUST contain either a Paths or Webhooks field at {RootNode.Context.GetLocation()}"));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ internal static partial class OpenApiV3Deserializer
{
private static FixedFieldMap<OpenApiComponents> _componentsFixedFields = new FixedFieldMap<OpenApiComponents>
{
{
"schemas", (o, n) => o.Schemas = n.CreateMapWithReference(ReferenceType.Schema, LoadSchema)
},
{"schemas", (o, n) => o.Schemas = n.CreateMapWithReference(ReferenceType.Schema, LoadSchema)},
{"responses", (o, n) => o.Responses = n.CreateMapWithReference(ReferenceType.Response, LoadResponse)},
{"parameters", (o, n) => o.Parameters = n.CreateMapWithReference(ReferenceType.Parameter, LoadParameter)},
{"examples", (o, n) => o.Examples = n.CreateMapWithReference(ReferenceType.Example, LoadExample)},
Expand All @@ -28,9 +26,9 @@ internal static partial class OpenApiV3Deserializer
{"securitySchemes", (o, n) => o.SecuritySchemes = n.CreateMapWithReference(ReferenceType.SecurityScheme, LoadSecurityScheme)},
{"links", (o, n) => o.Links = n.CreateMapWithReference(ReferenceType.Link, LoadLink)},
{"callbacks", (o, n) => o.Callbacks = n.CreateMapWithReference(ReferenceType.Callback, LoadCallback)},
{"pathItems", (o, n) => o.PathItems = n.CreateMapWithReference(ReferenceType.PathItem, LoadPathItem)}
};


private static PatternFieldMap<OpenApiComponents> _componentsPatternFields =
new PatternFieldMap<OpenApiComponents>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Collections.Generic;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers.ParseNodes;

Expand All @@ -26,6 +23,7 @@ internal static partial class OpenApiV3Deserializer
{"info", (o, n) => o.Info = LoadInfo(n)},
{"servers", (o, n) => o.Servers = n.CreateList(LoadServer)},
{"paths", (o, n) => o.Paths = LoadPaths(n)},
{"webhooks", (o, n) => o.Webhooks = LoadPaths(n)},
{"components", (o, n) => o.Components = LoadComponents(n)},
{"tags", (o, n) => {o.Tags = n.CreateList(LoadTag);
foreach (var tag in o.Tags)
Expand All @@ -50,7 +48,6 @@ internal static partial class OpenApiV3Deserializer
public static OpenApiDocument LoadOpenApi(RootNode rootNode)
{
var openApidoc = new OpenApiDocument();

var openApiNode = rootNode.GetMap();

ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields);
Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ public static OpenApiPathItem LoadPathItem(ParseNode node)
{
var mapNode = node.CheckMapNode("PathItem");

var pointer = mapNode.GetReferencePointer();

if (pointer != null)
{
return new OpenApiPathItem()
{
UnresolvedReference = true,
Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.PathItem)
};
}

var pathItem = new OpenApiPathItem();

ParseMap(mapNode, pathItem, _pathItemFixedFields, _pathItemPatternFields);
Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ private OpenApiReference ParseLocalReference(string localReference)
if (segments[1] == "components")
{
var referenceType = segments[2].GetEnumFromDisplayName<ReferenceType>();
return new OpenApiReference { Type = referenceType, Id = segments[3] };
var refId = segments[3];
if (segments[2] == "pathItems")
{
refId = "/" + segments[3];
};
return new OpenApiReference { Type = referenceType, Id = refId };
}
}

Expand Down
28 changes: 25 additions & 3 deletions src/Microsoft.OpenApi/Models/OpenApiComponents.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Writers;

Expand Down Expand Up @@ -63,6 +61,11 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public IDictionary<string, OpenApiCallback> Callbacks { get; set; } = new Dictionary<string, OpenApiCallback>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiPathItem"/> Object.
/// </summary>
public IDictionary<string, OpenApiPathItem> PathItems { get; set; } = new Dictionary<string, OpenApiPathItem>();

/// <summary>
/// This object MAY be extended with Specification Extensions.
/// </summary>
Expand All @@ -87,6 +90,7 @@ public OpenApiComponents(OpenApiComponents components)
SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary<string, OpenApiSecurityScheme>(components.SecuritySchemes) : null;
Links = components?.Links != null ? new Dictionary<string, OpenApiLink>(components.Links) : null;
Callbacks = components?.Callbacks != null ? new Dictionary<string, OpenApiCallback>(components.Callbacks) : null;
PathItems = components?.PathItems != null ? new Dictionary<string, OpenApiPathItem>(components.PathItems) : null;
Extensions = components?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(components.Extensions) : null;
}

Expand Down Expand Up @@ -288,7 +292,25 @@ public void SerializeAsV3(IOpenApiWriter writer)
component.SerializeAsV3(w);
}
});


// pathItems
writer.WriteOptionalMap(
OpenApiConstants.PathItems,
PathItems,
(w, key, component) =>
{
if (component.Reference != null &&
component.Reference.Type == ReferenceType.Schema &&
component.Reference.Id == key)
{
component.SerializeAsV3WithoutReference(w);
}
else
{
component.SerializeAsV3(w);
}
});

// extensions
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_0);

Expand Down
10 changes: 10 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public static class OpenApiConstants
/// </summary>
public const string Info = "info";

/// <summary>
/// Field: Webhooks
/// </summary>
public const string Webhooks = "webhooks";

/// <summary>
/// Field: Title
/// </summary>
Expand Down Expand Up @@ -75,6 +80,11 @@ public static class OpenApiConstants
/// </summary>
public const string Components = "components";

/// <summary>
/// Field: PathItems
/// </summary>
public const string PathItems = "pathItems";

/// <summary>
/// Field: Security
/// </summary>
Expand Down
31 changes: 30 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
/// </summary>
public OpenApiPaths Paths { get; set; }

/// <summary>
/// The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement.
/// A map of requests initiated other than by an API call, for example by an out of band registration.
/// The key name is a unique string to refer to each webhook, while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider and the expected responses
/// </summary>
public IDictionary<string, OpenApiPathItem> Webhooks { get; set; } = new Dictionary<string, OpenApiPathItem>();

/// <summary>
/// An element to hold various schemas for the specification.
/// </summary>
Expand Down Expand Up @@ -84,6 +91,7 @@ public OpenApiDocument(OpenApiDocument document)
Info = document?.Info != null ? new(document?.Info) : null;
Servers = document?.Servers != null ? new List<OpenApiServer>(document.Servers) : null;
Paths = document?.Paths != null ? new(document?.Paths) : null;
Webhooks = document?.Webhooks != null ? new Dictionary<string, OpenApiPathItem>(document.Webhooks) : null;
Components = document?.Components != null ? new(document?.Components) : null;
SecurityRequirements = document?.SecurityRequirements != null ? new List<OpenApiSecurityRequirement>(document.SecurityRequirements) : null;
Tags = document?.Tags != null ? new List<OpenApiTag>(document.Tags) : null;
Expand Down Expand Up @@ -115,6 +123,24 @@ public void SerializeAsV3(IOpenApiWriter writer)
// paths
writer.WriteRequiredObject(OpenApiConstants.Paths, Paths, (w, p) => p.SerializeAsV3(w));

// webhooks
writer.WriteOptionalMap(
OpenApiConstants.Webhooks,
Webhooks,
(w, key, component) =>
{
if (component.Reference != null &&
component.Reference.Type == ReferenceType.PathItem &&
component.Reference.Id == key)
{
component.SerializeAsV3WithoutReference(w);
}
else
{
component.SerializeAsV3(w);
}
});

// components
writer.WriteOptionalObject(OpenApiConstants.Components, Components, (w, c) => c.SerializeAsV3(w));

Expand Down Expand Up @@ -479,7 +505,10 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool
{
case ReferenceType.Schema:
return this.Components.Schemas[reference.Id];


case ReferenceType.PathItem:
return this.Components.PathItems[reference.Id];

case ReferenceType.Response:
return this.Components.Responses[reference.Id];

Expand Down
7 changes: 6 additions & 1 deletion src/Microsoft.OpenApi/Models/ReferenceType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public enum ReferenceType
/// <summary>
/// Tags item.
/// </summary>
[Display("tags")] Tag
[Display("tags")] Tag,

/// <summary>
/// Path item.
/// </summary>
[Display("pathItems")] PathItem
}
}
14 changes: 12 additions & 2 deletions src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
Expand Down Expand Up @@ -70,6 +70,7 @@ public override void Visit(OpenApiComponents components)
ResolveMap(components.Callbacks);
ResolveMap(components.Examples);
ResolveMap(components.Schemas);
ResolveMap(components.PathItems);
ResolveMap(components.SecuritySchemes);
ResolveMap(components.Headers);
}
Expand All @@ -83,6 +84,15 @@ public override void Visit(IDictionary<string, OpenApiCallback> callbacks)
ResolveMap(callbacks);
}

/// <summary>
/// Resolves all references used in webhooks
/// </summary>
/// <param name="webhooks"></param>
public override void Visit(IDictionary<string, OpenApiPathItem> webhooks)
{
ResolveMap(webhooks);
}

/// <summary>
/// Resolve all references used in an operation
/// </summary>
Expand Down Expand Up @@ -301,7 +311,7 @@ private void ResolveTags(IList<OpenApiTag> tags)

private bool IsUnresolvedReference(IOpenApiReferenceable possibleReference)
{
return (possibleReference != null && possibleReference.UnresolvedReference);
return possibleReference != null && possibleReference.UnresolvedReference;
}
}
}
7 changes: 7 additions & 0 deletions src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ public virtual void Visit(OpenApiPaths paths)
{
}

/// <summary>
/// Visits Webhooks>
/// </summary>
public virtual void Visit(IDictionary<string, OpenApiPathItem> webhooks)
{
}

/// <summary>
/// Visits <see cref="OpenApiPathItem"/>
/// </summary>
Expand Down
Loading