-
Notifications
You must be signed in to change notification settings - Fork 59
Nancy.Swagger for Nancy v2
Note: This documentation is a work in progress. If you see any mistakes feel free to report or fix them.
NuGet Package to use: Nancy.Swagger
Once you have included the necessary packages in your NancyFx project and configured your bootstrapper and modules, you can start creating the metadata for your routes.
The original way to describe your routes is to use the builder methods. To get started for describing routes for <Name>Module, you need to create a <Name>MetadataModule that extends MetadataModule<PathItem>.
For example, if you wanted to create a metadata module for your 'HomeModule', you would do the following:
public class HomeMetadataModule : MetadataModule<PathItem>
{
public HomeMetadataModule()
{
}
}
Note:
The Module and MetadataModule must use the corresponding namespace patterns (where [namespace] can be whatever you want):
-
[namespace]and[namespace] -
[namespace]and[namespace].Metadata -
[namespace].Modulesand[namespace].Metadata - See NancyFx for more info.
Make sure that your routes have names in the actual NancyModule. For example, if you wanted to have a GetUsers route in HomeModule, it should look something like this, with the last parameter being the name:
Get("/users", _ => new[] { new User { Name = "Vincent Vega", Age = 45 } }, null, "GetUsers");
You will then want to return to your MetadataModule and start the describing function. The GetUsers description would look like the following:
public class HomeMetadataModule : MetadataModule<PathItem>
{
public HomeMetadataModule()
{
Describe["GetUsers"] = description => description.AsSwagger(
with => with.Operation(
op => op.OperationId("GetUsers")
.Tag("Users")
.Summary("The list of users")
.Description("This returns a list of users from our awesome app")
.Response(r => r.Schema<User>().Description("The list of users"))));
}
}
Note: It is important that the string index in Describe["string"] matches the name of the route, so it will be mapped correctly.
Click here to view the full example code.
This is the list of options you can use to describe your routes:
-
op.OperationId("String")- This should match the name of the route -
op.Tag("String")- The tags are used to separate your functions on the swagger-ui page. You would just include the name here, but you'd need to create and add the tag elsewhere. See the information about tags at the bottom of this page. -
op.Summary("String")- This is a quick summary/name of the route -
op.Description("String")- This actually maps to the 'Implementation Notes' section of the swagger doc -
op.Response(r => r)andop.Response(200, r => r)- Where r is the ResponseBuilder object and 200 can be replaced with whatever status code you want to define. This defines one of the possible response values for your route-
r.Description("String")- This should describe the reason for this response being returned. It can be the status text that is returned with your status code -
r.Schema<Type>()- Where Type defines the return type that is being returned with this particular status code -
r.Header("String", header)- This defines what headers will be returned with this response -
See more here
-
-
op.Parameter(p)- Where p is the Parameter object; (This should be the ParameterBuilder object, but for some reason the builder's functions are private - this should be fixed). This defines one of the possible parameter values for your route-
p = new Parameter() { Name = "string", In = ParameterIn.[Form|Header|Path|Query], ... }
-
See more here
-
-
op.BodyParameter(bp => bp)- Where r is the BodyParameterBuilder object. This defines the body parameter info for your route-
bp.Description("String")- This should describe what the body represents -
bp.Name("String")- The name of this parameter -
bp.Schema<Type>()- Where Type defines the type that the body represents -
See more here
-
-
See the full list for OperationBuilder here
The SwaggerMetadataModule class is designed as a wrapper around the swagger metadata builders to allow for the easier creation of descriptions using concrete functions; it also provides some extra utilities to make your describing process more convenient, such as adding tags to an entire module and adding extra models. To get started, you still need to create a separate <Name>MetadataModule, but this time you will extend SwaggerMetadataModule. For example, if we wanted to create a ServiceDetailsMetadataModule, we would do the following.
public class ServiceDetailsMetadataModule : SwaggerMetadataModule
{
public ServiceDetailsMetadataModule(ISwaggerModelCatalog modelCatalog, ISwaggerTagCatalog tagCatalog) :base (modelCatalog, tagCatalog)
{
}
}
Note: ISwaggerModelCatalog and ISwaggerTagCatalog are already registered with Nancy by Nancy.Swagger to be used for dependency injection, so you don't need to worry about instantiating them.
You will still need to make sure to have a name for your route in the NancyModule. For example, if you wanted to create a route for getting a service's details, you would do the following in ServiceDetailsModule:
Get("/details", _ => new ServiceDetails(), null, "GetDetails");
And then you would return to the MetadataModule to describe the route. The GetDetails description would like:
public ServiceDetailsMetadataModule(ISwaggerModelCatalog modelCatalog, ISwaggerTagCatalog tagCatalog) :base (modelCatalog, tagCatalog)
{
RouteDescriber.DescribeRoute<ServiceDetails>("GetDetails", "", "Get Details", new[]
{
new HttpResponseMetadata {Code = 200, Message = "OK"}
});
}
}
Note: It is important that first string parameter in DescribeRoute matches the name of the route, so it will be mapped correctly.
Click here to view the full example code.
This is the list of functions you can use to help describe all of the routes in the MetadataModule.
-
RouteDescriber.AddBaseTag(Tag)- This sets a tag to be applied to all of the routes. This should be set before describing routes. -
RouteDescriber.AddAdditionalModels(types)- This adds models to the model catalog that aren't directly mentioned by your descriptions. See the notes about Models/Schemas at the bottom of the page.
This is the list of arguments you can use with DescribeRoute and DescribeRouteWithParams
-
DescribeRoute<Type>- Where Type is the default response type for your route -
string description- The name of the route (should match the route name in your NancyModule) -
string notes- The implementation notes of the route -
string summary- The short visible description/title of your route -
IEnumerable<HttpResponseMetadata> responseMetadatas- A list of response objects for your route-
They should look like this:
new HttpResponseMetadata {Code = 200, Message = "OK"} -
Or
new HttpResponseMetadata<Type> {Code = 200, Message = "OK"}if you want to specify a return type for that response
-
-
IEnumerable<Parameter> parameters- A list of parameter objects for your route-
They should look like this:
new Parameter{Name = "service", In = ParameterIn.Path, Required = true, Description = "The service's name", Default = "Nancy Swagger Service", Type = "string" } -
Or
new BodyParameter<ServiceCustomer>(ModelCatalog) {Name = "user", Required = true, Description = "The user"}if you want to create a body parameter
-
-
IEnumerable<Tag> tags- A list of tag objects for your route-
They should look like this:
new Tag(){Name = "Service/Customers", Description = "Operations of 'Service' relating to Customers"} -
To have the same tag for multiple routes, you should save the tag as a local variable and just pass that variable
-
-
See the full list for SwaggerRouteDescriber here
If you would prefer to not create a separate MetadataModule class for each NancyModule, you can use the annotations approach to mark up your routes directly in the NancyModule class.
First, you will need to add one more package/reference to your project: Nancy.Swagger.Annotations
Note: In Swagger.Nancy.Annotations current state, it does not do everything that Nancy.Swagger does for routes. Future work should be done to make the Annotations approach more of a wrapper around Nancy.Swagger's default metadata provider.
Once you've done that, adding annotations for the route descriptions is pretty simple. You will want to make sure that each of your routes has its own separate function, so that you can apply the annotations directly to it. For example, here's what a Module might look like without the annotations:
public class HomeModule : NancyModule
{
public HomeModule()
{
Get("/users", _ => GetUsers(), null, "GetUsers");
Post("/users", _ =>
{
var user = this.Bind<User>();
return PostUser(this.Bind<User>());
});
}
private IEnumerable<User> GetUsers()
{
return new[] {new User {Name = "Vincent Vega", Age = 45}};
}
private User PostUser(User user)
{
return user;
}
}
And then you can add the annotations to the functions to start describing them. Most importantly, you will need to provide the data to map the description back to the actual route.
This can be done using [Route("[Name]")] or [Route(HttpMethod.[Method], "[Path]")], where [Name], [Method], and [Path] would need to match exactly what you defined for the route. (The Named attribute will take priority over the Method/Path attribute, so that is the preferred usage; you can also use both, with the Method/Path attribute there just to provide extra contextual information.)
After that, you can start adding the necessary information to describe the route.
public class HomeModule : NancyModule
{
public HomeModule()
{
Get("/", _ => Response.AsRedirect("/swagger-ui/dist/index.html"), null, "Home");
Get("/users", _ => GetUsers(), null, "GetUsers");
Post("/users", _ =>
{
var user = this.Bind<User>();
return PostUser(user);
});
}
[Route("GetUsers")]
[Route(HttpMethod.Get, "/users")]
[SwaggerResponse(HttpStatusCode.OK, Message = "OK", Model = typeof(IEnumerable<User>))]
private IEnumerable<User> GetUsers()
{
return new[] {new User {Name = "Vincent Vega", Age = 45}};
}
[Route(HttpMethod.Post, "/users")]
[SwaggerResponse(HttpStatusCode.OK, Message = "OK", Model = typeof(User))]
[Route(Produces = new[] { "application/json" })]
[Route(Consumes = new[] { "application/json", "application/xml" })]
private User PostUser([RouteParam(ParameterIn.Body)] User user)
{
return user;
}
}
By default, all routes will be described in the swagger.json document, (e.g., the /swagger.json and /api-docs links will show up on the UI, as well as any others NancyModule routes created by you or any other libraries). This is great for debugging, because it will clearly show you which route descriptions may be malformed, but it may also clutter your swagger-ui page with routes you don't want to be visible. This can be disabled by adding the following line to your bootstrapper:
SwaggerAnnotationsConfig.ShowOnlyAnnotatedRoutes = true;
These are notes that don't apply to just one of the above approaches.
Swagger-ui expects a json object specifying the meta information for your API, such as the name, version, and contact information.
This information can be set using the following function:
SwaggerMetadataProvider.SetInfo(
"Nancy Swagger Example", //Name
"v0", //Version
"Our awesome service", //Description
new Contact() { EmailAddress = "exampleEmail@example.com"}, //Contact Info
"Tier1" //Service Level
);
You can also set the security information for your API using the following:
var securitySchemeBuilder = new Oauth2SecuritySchemeBuilder();
securitySchemeBuilder.Flow(Oauth2Flows.Implicit);
securitySchemeBuilder.Description("Authentication with Auth0");
securitySchemeBuilder.AuthorizationUrl(@"https://something.auth0.com");
securitySchemeBuilder.Scope("openid", "Grants access to user_id");
SwaggerMetadataProvider.SetSecuritySchemeBuilder(securitySchemeBuilder, "auth0");
Available security information types are Basic, Oauth2, or ApiKey.
When describing routes, tags are only attached by name. The swagger spec requires more information about tags, that are saved in a separate part of the JSON document. This means you will need to specify the tags independently. You can do this by creating a class that implements ISwaggerTagProvider or by adding the tag directly to the registered ISwaggerTagCatalog object.
public class UserTagProvider : ISwaggerTagProvider
{
public Tag GetTag()
{
return new Tag()
{
Description = "Operations related to users",
Name = "Users"
};
}
}
or
_tagCatalog.Add(new Tag()
{
Description = "Operations related to users",
Name = "Users"
});
If you are using SwaggerMetadataModule, this process will be taken care of for you when you add the specific tag object to the DescribeRoute functions.
As you should have noticed with all of the approaches above, they all have different ways of setting the type for a response object or for a body parameter.
If you use the default methods, the type will automatically be modeled and returned with your swagger JSON document. But if you would like to add extra information to your models, you can create a class that extends ISwaggerModelDataProvider. This will automatically be added to the model catalog with all your values set.
It is also important to note that, despite having the ability to create the models for classes automatically, the process does not create models for inner classes automatically. For example, if you have the following objects:
class A {
public String Name {get; set;}
public B SubClass {get; set;}
}
class B {
public String Name {get; set;}
}
If Class A is set as the type for a response, Class A will be modeled, but Class B will not. This means Class A will not know how to show Class B. (This was intentional, since there were some troublesome classes that broke when their inner classes were automatically modeled, like Date)
To get around this, you will need to explicitly tell Nancy.Swagger to add these models. This can be done by defining the ISwaggerModelDataProvider for the class or by adding the Type to ISwaggerModelCatalog via _modelCatalog.AddModels(types) or _modelCatalog.AddModel<Type>(). If you are using SwaggerMetadataModule, you can also use RouteDescriber.AddAdditionalModels(types).
Sometimes you may want to represent a type in your swagger output as another type. For example, if one of your classes used by your modules has a DateTime field called 'Timestamp' but that DateTime object is automatically converted from/to a string by Nancy, then you would want the swagger to say that 'Timestamp' is a string. You could also have a similar situation if you're using Nancy's custom JSON serializers.
For these situations, the SwaggerTypeMapping class comes in handy. This class allows you to easily say that a certain type should be resolved to another type during the generation of the swagger document.
For example, you can do the following to represent a DateTime as a string.
SwaggerTypeMapping.AddTypeMapping(typeof(DateTime), typeof(string));