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

How to generate Response examples and Schema defaults? #283

Closed
mattfrear opened this issue Apr 20, 2015 · 30 comments
Closed

How to generate Response examples and Schema defaults? #283

mattfrear opened this issue Apr 20, 2015 · 30 comments
Milestone

Comments

@mattfrear
Copy link

Hi,

Swagger's Response object supports examples:
https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#response-object
https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#example-object

Does Swashbuckle support this?

Matt

@mattfrear mattfrear changed the title How to generate Response object examples? How to generate Response examples? Apr 20, 2015
@mattfrear
Copy link
Author

I've thought about how to implement this, and I came up with this:

<response code="201" examples="{'application/json' : {'id' : '123', 'firstname' : 'John', 'lastname' : 'Smith'}}"><paramref name="account"/> created</response>

i.e. the user can put raw json into an examples attribute. Hmm so we're mixing json inside xml - maybe not the cleanest solution.

Anyway, I've implemented this locally and could submit a PR if you wish.

@jnpwly
Copy link

jnpwly commented Apr 20, 2015

@mattfrear I'm interested in seeing more about this. Not entirely sure how "the user can put ... into an examples attribute." Is that an XMLDoc attribute, or some other mechanism?

@mattfrear
Copy link
Author

The is an XMLDoc element which Swashbuckle already supports, I suggested adding an examples attribute to it. I got this working locally by changing the Swashbuckle source.
I've had a think about this overnight and another way to implement it in Swashbuckle would be to change the new SwaggerResponse.

However I code reviewed my changes with one of my colleagues and he came up with another idea to get it working without changing the Swashbuckle source, by writing an IOperationFilter. I've blogged about it here.

http://mattfrear.com/2015/04/21/generating-swagger-example-responses-with-swashbuckle/

I'm happy to close this issue since I've gotten it working, although I think it would be better if we changed [SwaggerResponse] to support an examples parameter.

@jnpwly
Copy link

jnpwly commented Apr 21, 2015

Awesome, sounds good!

@PeterZong
Copy link

+1 for supporting response example.

@MerickOWA
Copy link

What about using the cref attribute to refer to the type of the response object instead?

Like

/// <response code="201" cref="CreatedResponse">Returns the id of the created object</response>

That way C# understands that you're trying to reference a class type, and the example could be generated automatically from the schema of the class referenced.

It might still be nice however, to have explicit example response for more dynamic responses.

@mattfrear
Copy link
Author

@MerickOWA your last sentence nails what we're trying to achieve here.

We can already do what you've suggested right now, using the [SwaggerResponse] attribute.

It's something like this (apologies, I'm not on a Windows machine at the moment):

[SwaggerResponse(HttpStatusCodes.Created, "Returns the id of the created object", typeof(CreatedResponse))]

I don't know why a new [SwaggerResponse] attribute was created for this; I think what you've suggested using the /// xml documentation is cleaner - in my code right now I have 3 usages of [SwaggerResponse] on one method and it's getting quite ugly.

How about something like this:

/// <response code="201" cref="CreatedResponse" examplesProvider="CreatedResponseBuilder" examplesMethod="GetExamples">Returns the id of the created object</response>

This would call CreatedResponseBuilder.GetExamples(201) in order get the examples to be added to the response.

I don't know if it's illegal to invent my own attributes (e.g. examplesProvider)

I'm happy to implement this if it is wanted, but I don't want to spend the time creating a rejected PR.

@domaindrivendev
Copy link
Owner

@mattfrear - there is an impedance mismatch between "code comments" (i.e. Xml comments) and description of a HTTP/REST based API. The former describes implementation details (i.e. controllers, c# types etc.) encapsulated by the API whereas the latter describes the actual API and how it should be consumed. The introduction of non-standard attributes to Xml comments only further highlights the existence of this mismatch.

This presents a problem because the "intent" becomes blurred. For example, a developer may provide implementation detail in Xml comments that's only intended for an internal audience. This is perfectly understandable, particularly if they're unfamiliar with Swashbuckle. After-all, this is what Xml comments are intended for.

However, they sure do provide a convenient way to assign the "textual" Swagger properties. For the more structured properties like response codes, operation id's and even examples, I think custom attributes are a better approach for the following reasons.

  1. They explicitly indicate the intent (i.e. API documentation) as opposed to piggy-backing onto a feature that's meant for a different purpose.
  2. They don't force the "Xml comments" practice onto development teams (I for one favor expressive code over comments).

So, with all that said, I'm comfortable (enough) to continue support for "descriptive" properties through the existing "standard" Xml attributes, purely for the convenience it affords (assigning paragraphs of text to an attribute property would be tricky). However, with the next version, I'm leaning toward removing support for the current "response" attribute in favor of the SwaggerResponse attribute.

@domaindrivendev
Copy link
Owner

For response examples, I have no immediate plans to provide any additional support out-of-the-box whether it's through Xml or custom attributes.

I already cited my reasons for avoiding the Xml comments approach above.

For custom attributes, I like the approach that @mattfrear blogged about. But right now, I don't plan to add it out-of-the-box because it requires the consuming app to implement an extension point anyway (IProvidesExamples). I would prefer to limit the introduction of new abstractions if possible.

Maybe a compromise would be to have SB support "per-operation" filters so you could provide your examples as follows:

[SwaggerOperationFilter(typeof(ProductExamples))]
public IEnumerable<Product> GetProducts()
{
...
}

Where "ProductExamples" is just an implementation of IOperationFilter that specifically sets examples on the operation responses.

OR for request body parameters ...

[SwaggerSchemaFilter(typeof(ProductExamples))]
public class CreateProductDto
{
...
}

Where in this case ProductExamples provides an implementation for ISchemaFilter that sets the "default" property on the corresponding Schema

@domaindrivendev domaindrivendev added this to the v5.2.0 milestone May 11, 2015
@domaindrivendev domaindrivendev changed the title How to generate Response examples? How to generate Response examples and Schema defaults? May 11, 2015
@mattfrear
Copy link
Author

Thanks @domaindrivendev for providing insight as to the design of SwaggerResponse re: the impedance mismatch of using xml comments for everything. That makes sense.

I think I understand your suggestion. Going back to my blogged example (http://mattfrear.com/2015/04/21/generating-swagger-example-responses-with-swashbuckle/), we would have something like:
CountriesController.cs:

[SwaggerResponse(HttpStatusCode.OK, Type=typeof(IEnumerable<Country>))]
[SwaggerOperationFilter(typeof(CountriesExamples))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(IEnumerable<ErrorResource>))]
public async Task<HttpResponseMessage> Get(string lang)
{

and then CountriesExamples.cs:

public class CountriesExamples : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        var responseAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerResponse>();

        foreach (var attr in responseAttributes)
        {
            var schema = schemaRegistry.GetOrRegister(attr.ResponseType);

            var response = operation.responses.FirstOrDefault(x => x.Value.schema.type == schema.type && x.Value.schema.@ref == schema.@ref).Value;

            if (response != null)
            {
                response.examples = FormatAsJson(provider); // todo
            }
        }
    }

Is that what you were suggesting or did I misunderstand? (apologies if it does not compile, I'm not on a Windows box)

If it is, then a downside would be that you could only give examples for one type of SwaggerResponse, right?

@jnpwly
Copy link

jnpwly commented May 12, 2015

Echoing @mattfrear -- thank you @domaindrivendev for providing insights into the design of SwaggerResponse.

In my naive view, it could be possible to support the definition of many example responses if there was a way of providing some discriminator to the SwaggerOperationFilter. Don't think I have a concrete suggestion of how to do this, though.

@domaindrivendev
Copy link
Owner

@mattfrear - my thinking is that you create a per-action filter with the sole responsibility of assigning response examples to a given operation. Something along the following lines ...

[SwaggerResponse(HttpStatusCode.OK, Type=typeof(IEnumerable<Country>))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(IEnumerable<ErrorResource>))]
[SwaggerOperationFilter(typeof(AssignGetCountriesExamples))]
public async Task<HttpResponseMessage> Get(string lang)
{
    ...
}

public class AssignGetCountriesExamples : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        var successResponse = operation.responses["200"];
        successResponse.examples = new Dictionary<string, object>()
        {
            { "application/json", new { ... } }
        };

        var errorResponse = operation.responses["400"];
        errorResponse.examples = new Dictionary<string, object>(
        {
            { "application/json", new { ... } }
        };
    }
}

@domaindrivendev
Copy link
Owner

I also discovered some behavior in the swagger-ui that could simplify things further ...

If you assign the @default property on an object Schema, then that will be used as the sample JSON anywhere on the UI where that Schema is referenced, for responses AND parameters.

So, you could achieve the behavior you originally wanted, plus additional support for showing the sample JSON for parameters, by just doing the following ...

[SwaggerSchemaFilter(typeof(AssignCountryDefault)]
public class Country
{
    ...
}

public class AssignCountryDefault : ISchemaFilter
{
    public void Apply(Schema schema, SchemaRegistry schemaRegistry, System.Type type)
    {
        schema.@default = new { ... };
    }
} 

@mattfrear
Copy link
Author

OK cool, this looks great. Once this is released I'll give it a go.

@DannyRyman
Copy link

Personally, the fact that the SwaggerSchemaFilter attribute would need to leak into our data model would not work well for us. As much as I dislike the web api help page implementation, the SetSampleObjects feature works very well. Is there no possibility to implement something similar?

@domaindrivendev
Copy link
Owner

If you don't want to pollute your data model with cross-cutting concerns, you can simply wire up a global operation filter that branches on the "type" to assign specific values like "examples" and "default".

This way you would be providing all the samples in one place instead of scattering throughout the code

@domaindrivendev
Copy link
Owner

Sorry meant to say "Schema filter" above as opposed to "operation filter"

@DannyRyman
Copy link

Ok, thanks. I will have to play around with this to see how it works. Can I supply an instance of a populated model or do I need to get more involved?

@RodH257
Copy link

RodH257 commented Jun 1, 2015

With regards to this - the default generated model schema follows my JSON Serialization Convention to use lower_snake_case

 {
  "timestamp_utc": "2015-04-19T12:30:00",
  "status": "Created"
 }

but once I follow @domaindrivendev recommendation of setting a global schema.@default it reverts to the C# style capitalisation:

{
  "TimestampUtc": "2015-04-19T12:30:00",
  "Status": "Created"
}

Is there anyway to get the defaults to use the serializer settings? Right now I'm working around it by using an anonymous type or my schema example but I'd prefer it to be strongly typed so when I rename stuff it's automatically updated.

@domaindrivendev
Copy link
Owner

Should be resolved in latest release - 5.2.0

@RodH257
Copy link

RodH257 commented Jun 4, 2015

@domaindrivendev Thanks for that, however I just updated to the new version but it still doesn't appear to be working for me. Model has the correct casing, but model schema doesn't. Any ideas what I might be doing wrong?

@domaindrivendev
Copy link
Owner

@RodH257 - for the time being, you're going to be stuck with the "anonymous object workaround".

The problem is that the serialization of the SwaggerDocument (generated by Swashbuckle) requires it's own specific serialization settings to conform to the swagger-spec. It can't use the application's serialization settings because these could result in invalid Swagger JSON. However, the "example" and "default" objects are part of that SwaggerDocument. One solution would be to create a custom implementation of IContractResolver that uses the Swagger-specific settings for all properties except for the values of "example" and "default". For these, it would delegate to the ContractResolver configured by the app.

This would be a non-trivial piece of work and given that a reasonable workaround exists, it's not high on the list of priorities at the moment.

I closed the issue because I believe the original question is resolved with the introduction of per-action/per-schema filters. I think your problem is definitely valid and would like to address it at some point - perhaps you could create a separate issue just to keep it on the backlog?

Thanks

@hitmanmcc
Copy link

EDIT: scratch that guys, I got it, it's easy. :)

Hello guys, I'm sorry to bother you but I'm a noob trying to navigate the world of Swagger and Swashbuckle. I was trying to use SB to document an API and I'm running into some issues and learning some workarounds. At the moment I have an issue when I list certain operations with very complex objects whose automatically inferred model schemas take 30-60s to load.
As such, I was trying the approach mentioned by @domaindrivendev where you could do
schema.@default = new { ... };
To provide a schema for that object whenever it is needed.
Sadly, I haven't found any examples of this so I don't really know what I should replace the new { ... } with, to generate a schema model for my object.
Could someone please provide a very quick example or something that would help me sort this out? I've been struggling to find something useful to shed light on this.

BTW, I really like SB and Swagger, I wish I'd known about this sooner, I find documentation to be very important. Keep up the good work.

@hvaughan3
Copy link

hvaughan3 commented Jul 20, 2016

This is an old issue but is one of the first to show up in Google. So for anyone arriving here like me, @domaindrivendev's answer above works for the Value text box but not for the Model Schema to the right of it.

For complex objects, I ended up setting both schema.@default and schema.example using this guide. This correctly filled in the Model Schema text, which is what I originally wanted.

@urao123
Copy link

urao123 commented Sep 19, 2016

Hi Matt @mattfrear,

Could you please help me out in implementing default request data to the controller action of the model schema on the Swagger UI with Swagger 6.0.0-rc1-final with C#, same like your above implementation. As i'm using Swagger 6.0.0-rc1-final the functions which are used at above link are not found in this swagger which i'm using.

@hvaughan3
Copy link

@urao123 Have you seen this? That will generate default value items using annotations, but maybe you are looking to do something else...

@urao123
Copy link

urao123 commented Sep 19, 2016

Thank you @hvaughan3 for your reply, as i'm using Swagger 6.0.0-rc1-final, the functions which are used at above answer are not available in the version which i'm using, i'm using IOperationFilter for default values?

@thinkTank15
Copy link

Hi @mattfrear
is there a way that we can reference all the generic http responses(400,401,404,500) and their description to avoid redundant block of code after each method?

@atrophic
Copy link

@Simar15, I added a generic 401 response to all methods like this:

In config:

    c.OperationFilter<AddDefaultResponse>();

AddDefaultResponse class:

public class AddDefaultResponse : IOperationFilter
    {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            operation.responses.Add("401", new Response { description = "The request did not have the correct authorization header credientials." });
        }
    }

@thinkTank15
Copy link

@atrophic , Thanks for the help!, although I think I posted the question in a wrong thread, I was looking for generic responses in yaml spec file, I used this for generic responses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests