Skip to content

Commit

Permalink
Implement Azure Functions isolated worker parameter builder
Browse files Browse the repository at this point in the history
  • Loading branch information
idg10 committed Jan 20, 2023
1 parent 3812d39 commit b3da060
Show file tree
Hide file tree
Showing 13 changed files with 569 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ protected override IActionResult FromPoco(
}

/// <inheritdoc/>
protected override bool CanConstructFrom(OpenApiResult openApiResult, OpenApiOperation operation, ILogger logger)
protected override bool CanConstructFrom(
object result,
OpenApiOperation operation,
IEnumerable<IOpenApiConverter> converters,
ILogger logger)
{
return OpenApiActionResult.CanConstructFrom(openApiResult, operation, logger);
return OpenApiActionResult.CanConstructFrom(result, operation, logger);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ protected override IHttpResponseResult FromPoco(

/// <inheritdoc/>
protected override bool CanConstructFrom(
OpenApiResult openApiResult,
object result,
OpenApiOperation operation,
IEnumerable<IOpenApiConverter> converters,
ILogger logger)
{
return OpenApiHttpResponseResult.CanConstructFrom(openApiResult, operation, logger);
return OpenApiHttpResponseResult.CanConstructFrom(result, operation, logger);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static IServiceCollection AddOpenApiActionResultHosting<TContext>(
}

/// <summary>
/// Adds <see cref="HttpRequest"/> / middleware-based hosting.
/// Adds <see cref="HttpRequest"/> middleware-based hosting.
/// </summary>
/// <typeparam name="TContext">The type of the OpenApi context.</typeparam>
/// <param name="services">The service collection to configure.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace Menes.Hosting.AzureFunctionsWorker;

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Web;

using Menes.Converters;

Expand All @@ -33,30 +35,42 @@ public HttpRequestDataParameterBuilder(
/// <inheritdoc/>
protected override (Stream? Body, string? ContentType) GetBodyAndContentType(HttpRequestData request)
{
throw new NotImplementedException();
string? contentType = request.Headers.TryGetValues("Content-Type", out IEnumerable<string>? values)
? values.FirstOrDefault() : null;
return (request.Body, contentType);
}

/// <inheritdoc/>
protected override (string Path, string Method) GetPathAndMethod(HttpRequestData request)
{
throw new NotImplementedException();
return (request.Url.AbsolutePath, request.Method);
}

/// <inheritdoc/>
protected override bool TryGetCookieValue(HttpRequestData request, string key, [NotNullWhen(true)] out string? value)
{
throw new NotImplementedException();
value = request.Cookies.FirstOrDefault(c => c.Name == key)?.Value;
return value is not null;
}

/// <inheritdoc/>
protected override bool TryGetHeaderValue(HttpRequestData request, string key, [NotNullWhen(true)] out string? value)
{
throw new NotImplementedException();
if (request.Headers.TryGetValues(key, out IEnumerable<string>? values))
{
value = values.First();
return true;
}

value = null;
return false;
}

/// <inheritdoc/>
protected override bool TryGetQueryValue(HttpRequestData request, string key, [NotNullWhen(true)] out string? value)
{
throw new NotImplementedException();
NameValueCollection queryStringCollection = HttpUtility.ParseQueryString(request.Url.Query);
value = queryStringCollection[key];
return value is not null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ protected override IHttpResponseDataResult FromPoco(

/// <inheritdoc/>
protected override bool CanConstructFrom(
OpenApiResult openApiResult,
object result,
OpenApiOperation operation,
IEnumerable<IOpenApiConverter> converters,
ILogger logger)
{
return OpenApiHttpResponseDataResult.CanConstructFrom(openApiResult, operation, logger);
return OpenApiHttpResponseDataResult.CanConstructFrom(result, operation, logger);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// <copyright file="OpenApiAzureFunctionsWorkerHostingServiceCollectionExtensions.cs" company="Endjin Limited">
// Copyright (c) Endjin Limited. All rights reserved.
// </copyright>

namespace Microsoft.Extensions.DependencyInjection;

using System;

using Menes;
using Menes.Hosting.AzureFunctionsWorker;
using Menes.Internal;

using Microsoft.Azure.Functions.Worker.Http;

/// <summary>
/// Extensions to register open api request hosting for <see cref="HttpRequestData"/> and <see cref="HttpResponseData"/>.
/// </summary>
public static class OpenApiAzureFunctionsWorkerHostingServiceCollectionExtensions
{
/// <summary>
/// Adds <see cref="HttpRequestData"/> / <see cref="HttpResponseData"/> middleware-based hosting.
/// </summary>
/// <typeparam name="TContext">The type of the OpenApi context.</typeparam>
/// <param name="services">The service collection to configure.</param>
/// <param name="configureHost">A function to configure the host.</param>
/// <param name="configureEnvironment">A function to configure the environment.</param>
/// <returns>The configured service collection.</returns>
public static IServiceCollection AddOpenApiAzureFunctionsWorkerHosting<TContext>(
this IServiceCollection services,
Action<IOpenApiHostConfiguration>? configureHost,
Action<IOpenApiConfiguration>? configureEnvironment = null)
where TContext : class, IOpenApiContext, new()
{
services.AddSingleton<IResponseOutputBuilder<IHttpResponseDataResult>, PocoHttpResponseDataOutputBuilder>();
services.AddSingleton<IResponseOutputBuilder<IHttpResponseDataResult>, OpenApiResultHttpResponseDataOutputBuilder>();
services.AddSingleton<IOpenApiResultBuilder<IHttpResponseDataResult>, OpenApiHttpResponseDataResultBuilder>();

services.AddSingleton<IOpenApiContextBuilder<HttpRequestData>, OpenApiContextBuilder<HttpRequestData, TContext>>();
services.AddSingleton<IOpenApiParameterBuilder<HttpRequestData>, HttpRequestDataParameterBuilder>();

services.AddOpenApiHosting<HttpRequestData, HttpResponseData>(
configureHost,
configureEnvironment);

return services;
}
}
12 changes: 7 additions & 5 deletions Solutions/Menes.Hosting/Menes/Internal/PocoOutputBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal abstract class PocoOutputBuilder<TResponse> : IResponseOutputBuilder<TR
private readonly ILogger logger;

/// <summary>
/// Initializes a new instance of the <see cref="PocoActionResultOutputBuilder"/> class.
/// Initializes a new instance of the <see cref="PocoOutputBuilder{TResponse}"/> class.
/// </summary>
/// <param name="converters">The open API converters to use with the builder.</param>
/// <param name="logger">The logger for the output builder.</param>
Expand Down Expand Up @@ -67,7 +67,7 @@ public TResponse BuildOutput(object result, OpenApiOperation operation)
/// <inheritdoc/>
public bool CanBuildOutput(object result, OpenApiOperation operation)
{
return result is not OpenApiResult && OpenApiHttpResponseResult.CanConstructFrom(result, operation, this.logger);
return result is not OpenApiResult && this.CanConstructFrom(result, operation, this.converters, this.logger);
}

/// <summary>
Expand All @@ -85,15 +85,17 @@ protected abstract TResponse FromPoco(
ILogger logger);

/// <summary>
/// Determines if the action result can be constructed from the provided result and operation definition.
/// Determines if the resposne can be constructed from the provided result and operation definition.
/// </summary>
/// <param name="openApiResult">The <see cref="OpenApiResult"/>.</param>
/// <param name="result">The POCO result.</param>
/// <param name="operation">The OpenAPI operation definition.</param>
/// <param name="converters">The OpenAPI converters to use.</param>
/// <param name="logger">A logger for the operation.</param>
/// <returns>True if an action result can be constructed from this operation result.</returns>
protected abstract bool CanConstructFrom(
OpenApiResult openApiResult,
object result,
OpenApiOperation operation,
IEnumerable<IOpenApiConverter> converters,
ILogger logger);
}
}
20 changes: 14 additions & 6 deletions Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,17 @@ public static void InitializeContainer(ScenarioContext scenarioContext)
serviceCollection.AddLogging(configure => configure.SetMinimumLevel(LogLevel.Debug).AddProvider(new DummyLogger()));
serviceCollection.AddOpenApiAspNetPipelineHosting<SimpleOpenApiContext>(
null,
config =>
{
config.DiscriminatedTypes.Add("registeredDiscriminatedType1", typeof(RegisteredDiscriminatedType1));
config.DiscriminatedTypes.Add("registeredDiscriminatedType2", typeof(RegisteredDiscriminatedType2));
});
ConfigureOpenApi);
serviceCollection.AddOpenApiAzureFunctionsWorkerHosting<SimpleOpenApiContext>(
null,
ConfigureOpenApi);

static void ConfigureOpenApi(IOpenApiConfiguration config)
{
config.DiscriminatedTypes.Add("registeredDiscriminatedType1", typeof(RegisteredDiscriminatedType1));
config.DiscriminatedTypes.Add("registeredDiscriminatedType2", typeof(RegisteredDiscriminatedType2));
}

serviceCollection.AddContent(cf =>
{
cf.RegisterTransientContent<RegisteredContentType1>();
Expand Down Expand Up @@ -73,7 +79,9 @@ public void Dispose()

private class Logger : ILogger, IDisposable
{
public IDisposable BeginScope<TState>(TState state) => this;
public IDisposable? BeginScope<TState>(TState state)
where TState : notnull
=> this;

public void Dispose()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@perScenarioContainer

Feature: Azure Functions HttpRequestData Parameter Builder
In order to implement a web API that can run in Azure Functions with the isolated worker model
As a developer
I want Menes to be able to extract inputs from requests wrapped as HttpRequestData

Scenario: Body
Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer'
When I try to parse the value '[1,2,3,4,5]' as the HttpRequestData body
Then the parameter body should be [1,2,3,4,5] of type System.String

Scenario Outline: Cookie
Given I have constructed the OpenAPI specification with a cookie parameter with name 'openApiBoolean', type 'boolean', and format ''
When I try to parse the value '<Value>' as the cookie 'openApiBoolean' in an HttpRequestData
Then the parameter openApiBoolean should be <ExpectedValue> of type System.Boolean

Examples:
| Value | ExpectedValue |
| true | true |
| false | false |

Scenario Outline: Header
Given I have constructed the OpenAPI specification with a header parameter with name 'openApiBoolean', type 'boolean', and format ''
When I try to parse the value '<Value>' as the header 'openApiBoolean' in an HttpRequestData
Then the parameter openApiBoolean should be <ExpectedValue> of type System.Boolean

Examples:
| Value | ExpectedValue |
| true | true |
| false | false |

Scenario Outline: Query
Given I have constructed the OpenAPI specification with a query parameter with name 'openApiBoolean', type 'boolean', and format ''
When I try to parse the value '<Value>' as the query parameter 'openApiBoolean' in an HttpRequestData
Then the parameter openApiBoolean should be <ExpectedValue> of type System.Boolean

Examples:
| Value | ExpectedValue |
| true | true |
| false | false |
15 changes: 3 additions & 12 deletions Solutions/Menes.Specs/Menes.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(EndjinProjectPropsPath)" Condition="$(EndjinProjectPropsPath) != ''" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>Menes.Specs</RootNamespace>
Expand Down Expand Up @@ -62,32 +62,23 @@
<ItemGroup>
<ProjectReference Include="..\Menes.Abstractions\Menes.Abstractions.csproj" />
<ProjectReference Include="..\Menes.Hosting.AspNetCore\Menes.Hosting.AspNetCore.csproj" />
<ProjectReference Include="..\Menes.Hosting.FunctionsWorker\Menes.Hosting.AzureFunctionsWorker.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Steps\TestClasses\OpenApiWebLinkResolverTest.yaml" />
</ItemGroup>
<ItemGroup>
<SpecFlowObsoleteCodeBehindFiles Remove="Features\JsonTypeConversion\ByteArrayOutputParsing - Copy.feature.cs" />
<SpecFlowObsoleteCodeBehindFiles Remove="Features\ParameterBuilders\ArrayInputParsing.feature.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Steps\TestClasses\OpenApiWebLinkResolverTest.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Features\JsonTypeConversion\StringOutputParsing.feature.cs">
<DependentUpon>StringOutputParsing.feature</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="specflow.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<SpecFlowFeatureFiles Update="Features\JsonTypeConversion\StringOutputParsing.feature">
<Visible>$(UsingMicrosoftNETSdk)</Visible>
<CodeBehindFile>%(RelativeDir)%(Filename).feature$(DefaultLanguageSourceExtension)</CodeBehindFile>
</SpecFlowFeatureFiles>
</ItemGroup>
</Project>
Loading

0 comments on commit b3da060

Please sign in to comment.