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

Doesn't support multiple operations (for example, GET, POST) for one endpoint #68

Closed
serhiisolodovnyk opened this issue Mar 12, 2021 · 10 comments
Labels
question Further information is requested resolved Request has been resolved

Comments

@serhiisolodovnyk
Copy link

When I scanned the generated file swagger.json I found desription for one endpoint:
swagger: '2.0'
info:
title: Azure Functions Open API Extension
version: 1.0.0
host: localhost:7071
basePath: /api
schemes:

  • http
    paths:
    /HttpTriggerCSharp1:
    get:
    tags:
    - name
    operationId: Run
    produces:
    - text/plain
    parameters:
    - in: query
    name: name
    description: The Name parameter
    required: true
    type: string
    responses:
    '200':
    description: The OK response
    schema:
    type: string
    security:
    - function_key: [ ]
    securityDefinitions:
    function_key:
    type: apiKey
    name: code
    in: query

But in C# code we have support two types HTTP request GET and POST:

namespace Company.Function
{
public static class HttpTriggerCSharp1
{
[FunctionName("HttpTriggerCSharp1")]
[OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description =
"The Name parameter")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string),
Description = "The OK response")]
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");

        string name = req.Query["name"];

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        name = name ?? data?.name;

        string responseMessage = string.IsNullOrEmpty(name)
            ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
            : $"Hello, {name}. This HTTP triggered function executed successfully.";

        return new OkObjectResult(responseMessage);
    }
}

}

@justinyoo justinyoo added the question Further information is requested label Mar 13, 2021
@justinyoo
Copy link
Contributor

@sergeysolod17 Thanks for the question!

That's correct. Although technically one Function method can take more than one HTTP verb/method, semantically it should be two different operations.

Therefore, I would recommend separating multiple verbs/methods from each other, and giving them respective operation IDs.

@justinyoo justinyoo added the resolved Request has been resolved label Mar 13, 2021
@serhiisolodovnyk
Copy link
Author

Thank you!

@IainStevenson
Copy link

According to your answer, the Visual Studio Azure Function template function then is wrong to code the way it does. It provides misleading support for the function via the Swagger UI and json output. As it stands the function template (Http trigger with OpenAPI) code, and the behaviour of OpenAPI are out of sync and need addressing in some way. Etiher allow it to work the way the OP expressd (Which I very much prefer), or change, or pass to the responsible persons to change, the VS template to correctly code to your assertion and recomendation.

@IainStevenson
Copy link

I went over to look at the templates github repo, and they removed OpenAPI support !

@agravity-philipp
Copy link

agravity-philipp commented Jun 11, 2021

I went over to look at the templates github repo, and they removed OpenAPI support !

Yes they did.. but it seems that they added a separate OpenAPI template

@rhyspaterson
Copy link

rhyspaterson commented Feb 6, 2022

I'm a bit confused here. My understanding is that in a well designed API, we can leverage the same method with different HTTP verbs/operations. This is clearly articulated in the Swagger/Open API documentation, and based on my experience, a fairly common and intuitive architecture.

A single path can support multiple operations, for example GET /users to get a list of users and POST /users to add a new user.

This example is identical to how the Microsoft Graph API is documented and functions. For example, on the the user resource type, we can both list all users and create a new user on the same path:

GET /users (list)
POST /users (create)

This pattern is repeated endlessly throughout even the Microsoft API specifications. Are you suggesting we need to instead do something like?

GET /users
POST /users_create

Apart from duplicating code it seems like an anti-pattern. If it helps anyone, using routes seems to at least mitigate the unique function name requirement part of this problem and allow for Swagger to generate docco for multiple verbs on the same API path.

@IainStevenson
Copy link

For future reference: The following is a sample in VS2022 of a GET and POST working for the same REST resource (Hello) with Swagger.

As mentioned above, the key to this working as you want it to is using Route = "Hello" in the trigger declaration, in this case HttpTrigger.

This works but means, as of now, you cant really have a function handling more than one HTTP method for a resource AND leverage swagger.

I leave the arguments about THAT to the community.

using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;

namespace FunctionApp4
{
    public class Function1
    {
        private readonly ILogger<Function1> _logger;

        public Function1(ILogger<Function1> log)
        {
            _logger = log;
        }
        [FunctionName("GetHello")]
        [OpenApiOperation(operationId: "GetHello", tags: new[] { "name" })]
        [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
        [OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
        [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
        public async Task<IActionResult> GetHello(
            [HttpTrigger(AuthorizationLevel.Function, "get",  Route = "Hello")] HttpRequest req)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }

        [FunctionName("PostHello")]
        [OpenApiOperation(operationId: "PostHello", tags: new[] { "name" })]
        [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
        [OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
        [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
        public async Task<IActionResult> PostHello(
                    [HttpTrigger(AuthorizationLevel.Function,  "post", Route = "Hello")] HttpRequest req)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }
    }
}

@ConcatenatedNonsense
Copy link

Came across this issue today after instantiating a default Azure Functions project in Visual studio and realising that the Swagger docs only respect the first method in the function's methods params array.

For years we have implemented different behaviours for the same endpoint depending on the HTTP method used to communicate with them as HTTP methods themselves are semantically implicit - we would really appreciate seeing this implemented properly as at the moment it's neither clear why this behaves the way it does, nor is it compatible with this rather widely-used approach to API development.

Functions developed this way are already in production and being used by 3rd parties, refactoring to accommodate this would not be semantically beneficial and would introduce a needless breaking change to our dependants.

@chriscostanza
Copy link

chriscostanza commented Nov 17, 2022

Just encountered this same issue today while attempting to implement OpenAPI with a function that accepts multiple methods.

This seems crazy that it hasn’t been addressed. I’m not sure how to structure things now.

@keith-walker
Copy link

keith-walker commented Oct 13, 2023

Would love for this to be addressed as I have a function that has both Get and Post routes. Unfortunately, I had to separate the functionality out into it's own worker function to be called within two other function definitions with different function names. Luckily, it seems like I can have two function definitions with the same route so the path will remain the same and functionality from the outside will not change. Would've been nice to just have two OpenApiOperation tags above one function definition with the ability to specify Get/Post.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested resolved Request has been resolved
Projects
None yet
Development

No branches or pull requests

8 participants