Skip to content

Commit

Permalink
Merge for #60 by resolving conflicts (#96)
Browse files Browse the repository at this point in the history
  • Loading branch information
justinyoo authored Jul 19, 2020
1 parent 2139ef2 commit c3decc3
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public SampleHttpTrigger(ISampleHttpFunction function)
[OpenApiParameter(name: "category", In = ParameterLocation.Path, Required = true, Type = typeof(string), Summary = "The category parameter", Visibility = OpenApiVisibilityType.Advanced)]
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Summary = "The name query key", Visibility = OpenApiVisibilityType.Advanced)]
[OpenApiParameter(name: "limit", In = ParameterLocation.Query, Required = false, Type = typeof(int), Description = "The number of samples to return")]
[OpenApiResponseBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(SampleResponseModel), Summary = "Sample response")]
[OpenApiResponseBody(statusCode: HttpStatusCode.Created, contentType: "application/json", bodyType: typeof(List<SampleItemModel>), Summary = "Sample response of a List")]
[OpenApiResponseBody(statusCode: HttpStatusCode.Accepted, contentType: "application/json", bodyType: typeof(IList<SampleResponseModel>), Summary = "Sample response of a IList")]
[OpenApiResponseBody(statusCode: HttpStatusCode.NonAuthoritativeInformation, contentType: "application/json", bodyType: typeof(SampleResponseModel[]), Summary = "Sample response of a Array")]
Expand All @@ -59,6 +58,8 @@ public SampleHttpTrigger(ISampleHttpFunction function)
[OpenApiResponseBody(statusCode: HttpStatusCode.InternalServerError, contentType: "application/json", bodyType: typeof(List<string>), Summary = "Sample response of a List")]
[OpenApiResponseBody(statusCode: HttpStatusCode.NotImplemented, contentType: "application/json", bodyType: typeof(Dictionary<string, int>), Summary = "Sample response of a Dictionary")]
[OpenApiResponseBody(statusCode: HttpStatusCode.BadGateway, contentType: "application/json", bodyType: typeof(SampleGenericResponseModel<SampleResponseModel>), Summary = "Sample response of a Generic")]
//[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.NoContent)]
//[OpenApiResponseWithoutBody(statusCode: HttpStatusCode.RequestTimeout, Description = "Sample response with only status code", Summary = "Sample summary")]
public async Task<IActionResult> GetSample(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "samples/{id:int}/categories/{category:regex(^[a-z]{{3,}}$)}")] HttpRequest req,
int id,
Expand Down Expand Up @@ -93,4 +94,4 @@ public async Task<IActionResult> PostSample(
return result;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;

namespace Aliencube.AzureFunctions.Extensions.OpenApi.Attributes
{
Expand All @@ -17,5 +17,10 @@ public OpenApiRequestBodyAttribute(string contentType, Type bodyType)
: base(contentType, bodyType)
{
}

/// <summary>
/// Gets or sets the value indicating whether the request body is required or not.
/// </summary>
public virtual bool Required { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using System;
using System.Net;

namespace Aliencube.AzureFunctions.Extensions.OpenApi.Attributes
{
/// <summary>
/// This represents the attribute entity for HTTP triggers to define response body payload.
/// </summary>

[Obsolete("This class is obsolete from 2.0.0. Use OpenApiResponseWithBodyAttribute instead", error: true)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class OpenApiResponseBodyAttribute : OpenApiPayloadAttribute
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Net;

namespace Aliencube.AzureFunctions.Extensions.OpenApi.Attributes
{
/// <summary>
/// This represents the attribute entity for HTTP triggers to define response body payload.
/// </summary>

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class OpenApiResponseWithBodyAttribute : OpenApiPayloadAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiResponseWithBodyAttribute"/> class.
/// </summary>
/// <param name="statusCode">HTTP status code.</param>
/// <param name="contentType">Content type.</param>
/// <param name="bodyType">Type of payload.</param>
public OpenApiResponseWithBodyAttribute(HttpStatusCode statusCode, string contentType, Type bodyType)
: base(contentType, bodyType)
{
this.StatusCode = statusCode;
}

/// <summary>
/// Gets the HTTP status code value.
/// </summary>
public virtual HttpStatusCode StatusCode { get; }

/// <summary>
/// Gets or sets the summary.
/// </summary>
public virtual string Summary { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Net;

namespace Aliencube.AzureFunctions.Extensions.OpenApi.Attributes
{
/// <summary>
/// This represents the attribute entity for HTTP triggers to define response body payload.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class OpenApiResponseWithoutBodyAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiResponseWithoutBodyAttribute"/> class.
/// </summary>
/// <param name="statusCode">HTTP status code.</param>
public OpenApiResponseWithoutBodyAttribute(HttpStatusCode statusCode)
{
this.StatusCode = statusCode;
}

/// <summary>
/// Gets the HTTP status code value.
/// </summary>
public virtual HttpStatusCode StatusCode { get; }

/// <summary>
/// Gets or sets the summary.
/// </summary>
public virtual string Summary { get; set; }

/// <summary>
/// Gets or sets the description.
/// </summary>
public virtual string Description { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public IDocument Build(Assembly assembly, NamingStrategy namingStrategy = null)

operation.Parameters = this._helper.GetOpenApiParameters(method, trigger);
operation.RequestBody = this._helper.GetOpenApiRequestBody(method);
operation.Responses = this._helper.GetOpenApiResponseBody(method);
operation.Responses = this._helper.GetOpenApiResponses(method);

operations[verb] = operation;
item.Operations = operations;
Expand Down
47 changes: 38 additions & 9 deletions src/Aliencube.AzureFunctions.Extensions.OpenApi/DocumentHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -125,6 +125,7 @@ public List<OpenApiParameter> GetOpenApiParameters(MethodInfo element, HttpTrigg
.Select(p => p.ToOpenApiParameter())
.ToList();

// This needs to be provided separately.
if (trigger.AuthLevel != AuthorizationLevel.Anonymous)
{
parameters.AddOpenApiParameter<string>("code", @in: ParameterLocation.Query, required: false);
Expand All @@ -136,23 +137,51 @@ public List<OpenApiParameter> GetOpenApiParameters(MethodInfo element, HttpTrigg
/// <inheritdoc />
public OpenApiRequestBody GetOpenApiRequestBody(MethodInfo element, NamingStrategy namingStrategy = null)
{
var contents = element.GetCustomAttributes<OpenApiRequestBodyAttribute>(inherit: false)
.ToDictionary(p => p.ContentType, p => p.ToOpenApiMediaType());
var attributes = element.GetCustomAttributes<OpenApiRequestBodyAttribute>(inherit: false);
if (!attributes.Any())
{
return null;
}

var contents = attributes.ToDictionary(p => p.ContentType, p => p.ToOpenApiMediaType());

if (contents.Any())
{
return new OpenApiRequestBody() { Content = contents };
return new OpenApiRequestBody()
{
Content = contents,
Required = attributes.First().Required
};
}

return null;
}

/// <inheritdoc />
[Obsolete("This method is obsolete from 2.0.0. Use GetOpenApiResponses instead", error: true)]
public OpenApiResponses GetOpenApiResponseBody(MethodInfo element, NamingStrategy namingStrategy = null)
{
var responses = element.GetCustomAttributes<OpenApiResponseBodyAttribute>(inherit: false)
.ToDictionary(p => ((int)p.StatusCode).ToString(), p => p.ToOpenApiResponse(namingStrategy))
.ToOpenApiResponses();
return this.GetOpenApiResponses(element, namingStrategy);

//var responses = element.GetCustomAttributes<OpenApiResponseBodyAttribute>(inherit: false)
// .ToDictionary(p => ((int)p.StatusCode).ToString(), p => p.ToOpenApiResponse(namingStrategy))
// .ToOpenApiResponses();

//return responses;
}

/// <inheritdoc />
public OpenApiResponses GetOpenApiResponses(MethodInfo element, NamingStrategy namingStrategy = null)
{
var responsesWithBody = element.GetCustomAttributes<OpenApiResponseWithBodyAttribute>(inherit: false)
.Select(p => new { StatusCode = p.StatusCode, Response = p.ToOpenApiResponse(namingStrategy) });

var responsesWithoutBody = element.GetCustomAttributes<OpenApiResponseWithoutBodyAttribute>(inherit: false)
.Select(p => new { StatusCode = p.StatusCode, Response = p.ToOpenApiResponse(namingStrategy) });

var responses = responsesWithBody.Concat(responsesWithoutBody)
.ToDictionary(p => ((int)p.StatusCode).ToString(), p => p.Response)
.ToOpenApiResponses();

return responses;
}
Expand All @@ -162,7 +191,7 @@ public Dictionary<string, OpenApiSchema> GetOpenApiSchemas(List<MethodInfo> elem
{
var requests = elements.SelectMany(p => p.GetCustomAttributes<OpenApiRequestBodyAttribute>(inherit: false))
.Select(p => p.BodyType);
var responses = elements.SelectMany(p => p.GetCustomAttributes<OpenApiResponseBodyAttribute>(inherit: false))
var responses = elements.SelectMany(p => p.GetCustomAttributes<OpenApiResponseWithBodyAttribute>(inherit: false))
.Select(p => p.BodyType);
var types = requests.Union(responses)
.Select(p => p.IsOpenApiArray() || p.IsOpenApiDictionary() ? p.GetOpenApiSubType() : p)
Expand Down Expand Up @@ -204,4 +233,4 @@ private string FilterRoute(string route)
return string.Join("/", segments);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;

using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes;

Expand All @@ -10,24 +10,24 @@
namespace Aliencube.AzureFunctions.Extensions.OpenApi.Extensions
{
/// <summary>
/// This represents the extension entity for <see cref="OpenApiResponseBodyAttribute"/>.
/// This represents the extension entity for <see cref="OpenApiResponseWithBodyAttribute"/>.
/// </summary>
public static class OpenApiResponseBodyAttributeExtensions
public static class OpenApiResponseWithBodyAttributeExtensions
{
/// <summary>
/// Converts <see cref="OpenApiResponseBodyAttribute"/> to <see cref="OpenApiResponse"/>.
/// Converts <see cref="OpenApiResponseWithBodyAttribute"/> to <see cref="OpenApiResponse"/>.
/// </summary>
/// <param name="attribute"><see cref="OpenApiResponseBodyAttribute"/> instance.</param>
/// <param name="attribute"><see cref="OpenApiResponseWithBodyAttribute"/> instance.</param>
/// <param name="namingStrategy"><see cref="NamingStrategy"/> instance to create the JSON schema from .NET Types.</param>
/// <returns><see cref="OpenApiResponse"/> instance.</returns>
public static OpenApiResponse ToOpenApiResponse(this OpenApiResponseBodyAttribute attribute, NamingStrategy namingStrategy = null)
public static OpenApiResponse ToOpenApiResponse(this OpenApiResponseWithBodyAttribute attribute, NamingStrategy namingStrategy = null)
{
attribute.ThrowIfNullOrDefault();

var description = string.IsNullOrWhiteSpace(attribute.Description)
? $"Payload of {attribute.BodyType.GetOpenApiDescription()}"
: attribute.Description;
var mediaType = attribute.ToOpenApiMediaType<OpenApiResponseBodyAttribute>(namingStrategy);
var mediaType = attribute.ToOpenApiMediaType<OpenApiResponseWithBodyAttribute>(namingStrategy);
var content = new Dictionary<string, OpenApiMediaType>()
{
{ attribute.ContentType, mediaType }
Expand All @@ -48,4 +48,4 @@ public static OpenApiResponse ToOpenApiResponse(this OpenApiResponseBodyAttribut
return response;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes;

using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;

using Newtonsoft.Json.Serialization;

namespace Aliencube.AzureFunctions.Extensions.OpenApi.Extensions
{
/// <summary>
/// This represents the extension entity for <see cref="OpenApiResponseWithoutBodyAttribute"/>.
/// </summary>
public static class OpenApiResponseWithoutBodyAttributeExtensions
{
/// <summary>
/// Converts <see cref="OpenApiResponseWithoutBodyAttribute"/> to <see cref="OpenApiResponse"/>.
/// </summary>
/// <param name="attribute"><see cref="OpenApiResponseWithoutBodyAttribute"/> instance.</param>
/// <param name="namingStrategy"><see cref="NamingStrategy"/> instance to create the JSON schema from .NET Types.</param>
/// <returns><see cref="OpenApiResponse"/> instance.</returns>
public static OpenApiResponse ToOpenApiResponse(this OpenApiResponseWithoutBodyAttribute attribute, NamingStrategy namingStrategy = null)
{
attribute.ThrowIfNullOrDefault();

var description = string.IsNullOrWhiteSpace(attribute.Description)
? "No description"
: attribute.Description;
var response = new OpenApiResponse()
{
Description = description,
};

if (!string.IsNullOrWhiteSpace(attribute.Summary))
{
var summary = new OpenApiString(attribute.Summary);

response.Extensions.Add("x-ms-summary", summary);
}

return response;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;

using Microsoft.OpenApi.Models;
Expand Down Expand Up @@ -28,4 +27,4 @@ public static OpenApiResponses ToOpenApiResponses(this Dictionary<string, OpenAp
return responses;
}
}
}
}
14 changes: 12 additions & 2 deletions src/Aliencube.AzureFunctions.Extensions.OpenApi/IDocumentHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Reflection;

using Microsoft.Azure.WebJobs;
Expand Down Expand Up @@ -88,8 +89,17 @@ public interface IDocumentHelper
/// <param name="element"><see cref="MethodInfo"/> instance.</param>
/// <param name="namingStrategy"><see cref="NamingStrategy"/> instance to create the JSON schema from .NET Types.</param>
/// <returns><see cref="OpenApiResponses"/> instance.</returns>
[Obsolete("This method is obsolete from 2.0.0. Use GetOpenApiResponses instead", error: true)]
OpenApiResponses GetOpenApiResponseBody(MethodInfo element, NamingStrategy namingStrategy = null);

/// <summary>
/// Gets the <see cref="OpenApiResponses"/> instance.
/// </summary>
/// <param name="element"><see cref="MethodInfo"/> instance.</param>
/// <param name="namingStrategy"><see cref="NamingStrategy"/> instance to create the JSON schema from .NET Types.</param>
/// <returns><see cref="OpenApiResponses"/> instance.</returns>
OpenApiResponses GetOpenApiResponses(MethodInfo element, NamingStrategy namingStrategy = null);

/// <summary>
/// Gets the collection of <see cref="OpenApiSchema"/> instances.
/// </summary>
Expand All @@ -104,4 +114,4 @@ public interface IDocumentHelper
/// <returns>Collection of <see cref="OpenApiSecurityScheme"/> instance.</returns>
Dictionary<string, OpenApiSecurityScheme> GetOpenApiSecuritySchemes();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;

using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes;
Expand All @@ -10,17 +10,17 @@
namespace Aliencube.AzureFunctions.Extensions.OpenApi.Tests.Attributes
{
[TestClass]
public class OpenApiResponseBodyAttributeTests
public class OpenApiResponseWithBodyAttributeTests
{
[TestMethod]
public void Given_Null_Constructor_Should_Throw_Exception()
{
var statusCode = HttpStatusCode.OK;

Action action = () => new OpenApiResponseBodyAttribute(statusCode, null, null);
Action action = () => new OpenApiResponseWithBodyAttribute(statusCode, null, null);
action.Should().Throw<ArgumentNullException>();

action = () => new OpenApiResponseBodyAttribute(statusCode, "hello world", null);
action = () => new OpenApiResponseWithBodyAttribute(statusCode, "hello world", null);
action.Should().Throw<ArgumentNullException>();
}

Expand All @@ -30,11 +30,12 @@ public void Given_Value_Property_Should_Return_Value()
var statusCode = HttpStatusCode.OK;
var contentType = "Hello World";
var bodyType = typeof(object);
var attribute = new OpenApiResponseBodyAttribute(statusCode, contentType, bodyType);
var attribute = new OpenApiResponseWithBodyAttribute(statusCode, contentType, bodyType);

attribute.StatusCode.Should().Be(statusCode);
attribute.ContentType.Should().BeEquivalentTo(contentType);
attribute.BodyType.Should().Be(bodyType);
attribute.Summary.Should().BeNullOrWhiteSpace();
attribute.Description.Should().BeNullOrWhiteSpace();
}
}
Expand Down
Loading

0 comments on commit c3decc3

Please sign in to comment.