Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webhooks property to the root document #1046

Merged
merged 21 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 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
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
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
b7e3e48
Add more validation for empty paths and missing paths/webhooks for 3.1
MaggieKimani1 Oct 26, 2022
846fb0e
Use Any() instead of count
MaggieKimani1 Oct 27, 2022
2299117
Code clean up
MaggieKimani1 Oct 27, 2022
21e19a0
Add negation operator
MaggieKimani1 Oct 27, 2022
6152336
Change reference type to pathItem
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
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
20 changes: 19 additions & 1 deletion src/Microsoft.OpenApi.Readers/ParsingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,14 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument)
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.StartsWith("3.0") || version.StartsWith("3.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,21 @@ public void PopLoop(string loopid)
}
}

private void ValidateRequiredFields(OpenApiDocument doc, string version)
{
if ((version == "2.0" || version.StartsWith("3.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.StartsWith("3.1"))
{
if ((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
@@ -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,7 @@ 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
5 changes: 5 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
26 changes: 26 additions & 0 deletions 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
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("pathItem")] PathItem,
}
}
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
23 changes: 23 additions & 0 deletions src/Microsoft.OpenApi/Services/OpenApiWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public void Walk(OpenApiDocument doc)
Walk(OpenApiConstants.Info, () => Walk(doc.Info));
Walk(OpenApiConstants.Servers, () => Walk(doc.Servers));
Walk(OpenApiConstants.Paths, () => Walk(doc.Paths));
Walk(OpenApiConstants.Webhooks, () => Walk(doc.Webhooks));
Walk(OpenApiConstants.Components, () => Walk(doc.Components));
Walk(OpenApiConstants.Security, () => Walk(doc.SecurityRequirements));
Walk(OpenApiConstants.ExternalDocs, () => Walk(doc.ExternalDocs));
Expand Down Expand Up @@ -221,6 +222,28 @@ internal void Walk(OpenApiPaths paths)
}
}

/// <summary>
/// Visits Webhooks and child objects
/// </summary>
internal void Walk(IDictionary<string, OpenApiPathItem> webhooks)
{
if (webhooks == null)
{
return;
}

_visitor.Visit(webhooks);

// Visit Webhooks
if (webhooks != null)
{
foreach (var pathItem in webhooks)
{
Walk(pathItem.Key, () => Walk(pathItem.Value));
}
}
}

/// <summary>
/// Visits list of <see cref="OpenApiServer"/> and child objects
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ public static class OpenApiDocumentRules
String.Format(SRResource.Validation_FieldIsRequired, "info", "document"));
}
context.Exit();

// paths
context.Enter("paths");
if (item.Paths == null)
{
context.CreateError(nameof(OpenApiDocumentFieldIsMissing),
String.Format(SRResource.Validation_FieldIsRequired, "paths", "document"));
}
context.Exit();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@
<EmbeddedResource Include="V3Tests\Samples\OpenApiDocument\minimalDocument.yaml">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="V3Tests\Samples\OpenApiDocument\documentWithWebhooks.yaml">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="V3Tests\Samples\OpenApiDocument\apiWithFullHeaderComponent.yaml">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
Expand Down
Loading