mattfrear / Swashbuckle.AspNetCore.Filters

A bunch of useful filters for Swashbuckle.AspNetCore
MIT License
428 stars 80 forks source link

JsonPatchDocument Example #44

Closed chrisjainsley closed 6 years ago

chrisjainsley commented 6 years ago

I have a simple patch endpoint and I'm trying to create an example:

[SwaggerRequestExample(typeof(JsonPatchDocument<UpdateCustomerRequest>), typeof(UpdateCustomerRequestExample))]
public IActionResult Patch(Guid id, [FromBody]JsonPatchDocument<UpdateCustomerRequest> updateCustomerRequestPatch)
public class UpdateCustomerRequestExample : IExamplesProvider
    {
        public object GetExamples()
        {
            return new JsonPatchDocument<UpdateCustomerRequest>()
            {
                Operations = { new Operation<UpdateCustomerRequest>("replace", "email", "Aubree.Scott@gmail.com", "jane@hotmail.com") }
            };
        }
    }

Unfortunately the example displayed is:

[
  {
    "value": {},
    "path": "string",
    "op": "string",
    "from": "string"
  }
]
mattfrear commented 6 years ago

I'm holiday without access to a computer, but can you look at the swagger.json and see if that looks correct?

Matt

On 4 May 2018, at 13:24, cja100 notifications@github.com wrote:

I have a simple patch endpoint and I'm trying to create an example:

[SwaggerRequestExample(typeof(JsonPatchDocument), typeof(UpdateCustomerRequestExample))] public IActionResult Patch(Guid id, [FromBody]JsonPatchDocument updateCustomerRequestPatch) public class UpdateCustomerRequestExample : IExamplesProvider { public object GetExamples() { return new JsonPatchDocument() { Operations = { new Operation("replace", "email", "Aubree.Scott@gmail.com", "jane@hotmail.com") } }; } } Unfortunately the example displayed is:

[ { "value": {}, "path": "string", "op": "string", "from": "string" } ] — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

chrisjainsley commented 6 years ago

The swagger json generated is also incorrect, is the same as above.

freeranger commented 6 years ago

I have exactly the same problem. Even subclassed JsonPatchDocument but it didnt help :/

mattfrear commented 6 years ago

I must confess I've never heard of JsonPatchDocument before. I'll investigate this when I get a chance but it won't be for at least another week or two.

freeranger commented 6 years ago

@mattfrear - some more info that may help.

I don't know that there is anything you can do from this end - maybe it's one for the Swashbuckle guys. JsonPatchDocument is basically just a List\<Operation> and this seems to be what Swashbuckle registers as the parameter type.

The examples filter here: https://github.com/mattfrear/Swashbuckle.AspNetCore.Examples/blob/9eb6b006d4ce5e65f829c5944a15ada8d6885b82/src/Swashbuckle.AspNetCore.Examples/Examples/ExamplesOperationFilter.cs#L43 is looking for JsonPatchDocument but there isn't one because it defined as an Operation, so you get back null and therefore no example.

A solution may be to have a schema filter which would generate the correct types for your models. https://github.com/domaindrivendev/Swashbuckle.AspNetCore#extend-generator-with-operation-schema--document-filters

Or perhaps Swashbuckle should be registering JsonPatchDocument.

Incidentally, I created a custom class inheriting from JsonPatchDocument to see if that would help and fool Swashbuckle into registering my type, but sadly not :(

mattfrear commented 6 years ago

Hello

Sorry for the delay in looking at this, I haven't had a lot of free time until the last week or so.

This works, using ASP.NET Core 1.1:

[HttpPatch]
[Route("api/values/patchperson")]
[SwaggerRequestExample(typeof(JsonPatchDocument<PersonRequest>), typeof(JsonPatchPersonRequestExample))]
public PersonResponse JsonPatchPerson([FromBody]JsonPatchDocument<PersonRequest> personRequest)

or this, for ASP.NET Core 2.0:

[HttpPatch]
[Route("api/values/patchperson")]
[SwaggerRequestExample(typeof(Operation), typeof(JsonPatchPersonRequestExample))]
public PersonResponse JsonPatchPerson([FromBody]JsonPatchDocument<PersonRequest> personRequest)
using Microsoft.AspNetCore.JsonPatch.Operations;
using Swashbuckle.AspNetCore.Filters;

namespace WebApi.Models.Examples
{
    public class JsonPatchPersonRequestExample : IExamplesProvider
    {
        public object GetExamples()
        {
            return new[]
            {
                new Operation
                {
                    op = "replace",
                    path = "/firstname",
                    value = "Steve"
                },
                new Operation
                {
                    op = "remove",
                    path = "/income"
                }
            };
        }
    }
}

image

Hope that helps.

mattfrear commented 6 years ago

See my updated answer above for a solution for .NET Core 1.1.

freeranger commented 6 years ago

Thanks @mattfrear - It is not intuitive to use Operation rather than JsonPatchDocument, but at least this works which is the main thing!

tocsi-hun commented 5 years ago

Hey, I might miss something here. My problem on ASP.NET Core 2 is not about if it is intuitive or not (well, it might be). Because the example is registered to a parameter type and not for an operation/action (if I understood well) using the Operation type means the example still will be the same for each and every JsonPatchDocument<> parameter - just not the default one. But in practice I'd like to provide a real meaningful example for each of my patch actions. Because, for example, JsonPatchDocument<MyClass> is not the same as JsonPatchDocument<List<OtherClass>>. Currently I cannot do it, not even with a schema filter, because only the Operation type is taken into account. If I register an example for Operation<Myclass> then it does nothing, so I cannot differentiate between different JsonPatchDocuments. Is there a way to do that?

tocsi-hun commented 5 years ago

And the fact that it uses Operation instead of JsonPatchDocument also causes trouble to give an example with multiple operations... It will have an extra level of array.

mattfrear commented 5 years ago

Yes, it's a limitation of Swagger 2.0 that you can only have example per Request type. OpenApi 3 seems to allow different request examples for each operation, and I have released a beta version of this package which supports OpenApi 3, so that may work for you?

But to be honest, I haven't had the need to use JsonPatch on any of my own projects and until I do I'm not going to really attempt to tackle this.

tocsi-hun commented 5 years ago

OK, thanks, That might work indeed. And you're right, I have to admit my requirements are a bit extreme. But in case someone has the same weird thing in mind: I was able to solve it with an operation filter. I pick up the BodyParameter from the operation. That one has a Schema which is not from the SchemaRegistry but rather just has a reference to it. So if I set the example on this Schema, then it will be for that certain operation only.

freeranger commented 5 years ago

We ended up inherting from JsonPatchDocument - e.g. FooOverlaysJsonPatchDocument and then doing a map type on that:

public class FooJsonPatchDocument : JsonPatchDocument
{
    public FooJsonPatchDocument() : base()
    {
    }

    public FooJsonPatchDocument(List<Operation> operations, IContractResolver contractResolver) : base(operations, contractResolver)
    {
    }
}

public static void AddPatchExample<TPatchDocument, TExamplesProvider>(this SwaggerGenOptions options) where TPatchDocument : JsonPatchDocument where TExamplesProvider: IExamplesProvider, new()
{
    options.MapType<TPatchDocument>(() => new Schema
    {
        Type = "object",
        Example = new TExamplesProvider().GetExamples()
    });
}

options.AddPatchExample<FooJsonPatchDocument, PatchFooExample>();