wordnik/swagger-core — pull request #630 by osbornk (2014-07-21 16:54:47+00:00)
Title: Features/dynamic fields and Features/version_support

It looks like my two branches got merged, but that is because I developed them together. But, here are the new features.

I added the ability to create an API filter. Specifically, this is for versioning, but could be used for other features.

Here is an example routes file:

GET /api-docs controllers.ApiHelpController.getResources(filter = "v1_all")
GET /v1/api-docs controllers.ApiHelpController.getResources(filter = "v1")
GET /v2/api-docs controllers.ApiHelpController.getResources(filter = "v2")

GET /api-docs/users controllers.ApiHelpController.getResource(path = "/v1_all/users")
GET /v1/api-docs/users controllers.ApiHelpController.getResource(path = "/v1/users")
GET /users @controllers.Users.findUsers

GET /v2/api-docs/users controllers.ApiHelpController.getResource(path = "/v2/users")
GET /v2/users @controllers.Users_v2.findUsers

filter is a new option with a default value of none to getResources. I also added a filter field to the @Api annotation. So, when getResources looks for ApiListings, it will match by filter if it has been provided. I also added pathAlias to @Api to handle paths where the path should not actually contain "/v1/users", but should really just be "/users". Here is an example:

@Api(value = "/v1/users", filter = "v1", pathAlias = "/users", description = "Operations on Users V1")

----------------

Dynamic fields is the ability to generate field information at runtime. This would be used if you wanted to expose a customer's custom fields at runtime through the Swagger UI. So, I created an @ApiDynamicModelProperty annotation. Here is an example.

@ApiModel("User")
case class User(@(ApiModelProperty @field)(position = 1)age: Int,
@(ApiModelProperty @field)(position = 2)firstName: String,
@(ApiModelProperty @field)(position = 3)lastName: String,
@(ApiModelProperty @field)(position = 4, value = "blah")active: Boolean)
@(ApiDynamicModelProperty @field)(position = 5, builderInstance = "services.ModelCustom1Builder")customField1: List[String],
@(ApiDynamicModelProperty @field)(position = 6, builderInstance = "services.ModelCustom2Builder")customField2: List[String])

The builderInstance points to an object that extends ApiDynamicBuilder.

object ModelCustom1Builder extends ApiDynamicBuilder {
def build(requestHeader: RequestHeader): List[ModelProperty] = {
val user = requestHeader.getQueryString("user")
user match {
case Some("bob") => List(ModelProperty("string", "java.lang.String", 0, false, Some("custom1_A desc"), dynamicName = Some("custom1_A_bob")), ModelProperty("int", "int", 1, false, Some("custom1_B desc"), dynamicName = Some("custom1_B_bob")))
case Some("fred") => List(ModelProperty("string", "java.lang.String", 0, false, Some("custom1_A desc"), dynamicName = Some("custom1_A_fred")), ModelProperty("int", "int", 1, false, Some("custom1_B desc"), dynamicName = Some("custom1_B_fred")))
case _ => Nil
}
}
}

Swagger will use reflection to call the build function with the RequestHeader. It will then return a list of ModelProperty objects, which will then be exposed to the Json Serializer in the normal manner.

In order to do this at runtime, but still have thread safety, in the case of an ApiListing with dynamic fields, I rebuild the ApiListing object at runtime. It is first fetched from the cache like normal. But then the model.properties field is reassembled as a merge of properties and the runtime properties from dynamicProperties.