diff --git a/src/main/scala/com/wordnik/swagger/model/SwaggerModelSerializer.scala b/src/main/scala/com/wordnik/swagger/model/SwaggerModelSerializer.scala index 7d8c3c81cbe..580e8824f15 100644 --- a/src/main/scala/com/wordnik/swagger/model/SwaggerModelSerializer.scala +++ b/src/main/scala/com/wordnik/swagger/model/SwaggerModelSerializer.scala @@ -5,60 +5,254 @@ import org.json4s.JsonDSL._ import org.json4s.jackson.JsonMethods._ import org.json4s.native.Serialization.{read, write} -class ModelPropertySerializer extends CustomSerializer[ModelProperty] (formats => ({ - case json => - implicit val fmts: Formats = formats - ModelProperty( - `type` = (json \ "type").extractOrElse(""), - required = ((json \ "required").extractOrElse("false")).toBoolean, - description = (json \ "description").extractOpt[String], - allowableValues = (json \ "allowableValues").extract[AllowableValuesFoo], - items = (json \ "items").extractOpt[ModelRef] - ) -}, { - case x: ModelProperty => - implicit val fmts = formats - ("type" -> x.`type`) ~ - ("required" -> x.required) ~ - ("description" -> x.description) ~ - ("allowableValues" -> { - x.allowableValues match { - case Any => JNothing // don't serialize when not a concrete type - case e:AllowableValuesFoo => Extraction.decompose(x.allowableValues) - case _ => JNothing - } - }) ~ - ("items" -> Extraction.decompose(x.items)) -})) +import scala.collection.mutable.HashMap +import scala.collection.JavaConverters._ -class ModelRefSerializer extends CustomSerializer[ModelRef](formats => ({ - case json => - implicit val fmts: Formats = formats - ModelRef( - (json \ "$ref").extract[String], - (json \ "type").extract[String] - ) -}, { - case x: ModelRef => - implicit val fmts = formats - ("$ref" -> x.ref) ~ - ("type" -> x.`type`) -})) +object SwaggerSerializers { + implicit val formats = DefaultFormats + + new ModelSerializer + + new ModelPropertySerializer + + new ModelRefSerializer + + new AllowableValuesSerializer + + new ParameterSerializer + + new OperationSerializer + + new ErrorResponseSerializer + + new ApiDescriptionSerializer + + new ApiListingReferenceSerializer + + new ResourceListingSerializer -class AllowableValuesSerializer extends CustomSerializer[AllowableValuesFoo](formats => ({ - case json => - implicit val fmts: Formats = formats - json \ "valueType" match { - case JString(x) if x.equalsIgnoreCase("list") => - AllowableListValues((json \ "values").extract[List[String]]) - case JString(x) if x.equalsIgnoreCase("range") => - AllowableRangeValues((json \ "min").extract[String], (json \ "max").extract[String]) - case _ => Any + class ResourceListingSerializer extends CustomSerializer[ResourceListing](formats => ({ + case json => + implicit val fmts: Formats = formats + ResourceListing( + (json \ "apiVersion").extract[String], + (json \ "swaggerVersion").extract[String], + (json \ "basePath").extract[String], + (json \ "apis").extract[List[ApiListingReference]] + ) + }, { + case x: ResourceListing => + implicit val fmts = formats + ("apiVersion" -> x.apiVersion) ~ + ("swaggerVersion" -> x.swaggerVersion) ~ + ("basePath" -> x.basePath) ~ + ("apis" -> { + x.apis match { + case e: List[ApiListingReference] if (e.size > 0) => Extraction.decompose(e) + case _ => JNothing + } + }) } -}, { - case AllowableListValues(values, "LIST") => - implicit val fmts = formats - ("valueType" -> "LIST") ~ ("values" -> Extraction.decompose(values)) - case AllowableRangeValues(min, max) => - ("valueType" -> "RANGE") ~ ("min" -> min) ~ ("max" -> max) -})) + )) + + class ApiListingReferenceSerializer extends CustomSerializer[ApiListingReference](formats => ({ + case json => + implicit val fmts: Formats = formats + ApiListingReference( + (json \ "path").extract[String], + (json \ "description").extractOrElse("") + ) + }, { + case x: ApiListingReference => + implicit val fmts = formats + ("path" -> x.path) ~ + ("description" -> x.description) + } + )) + + class ApiDescriptionSerializer extends CustomSerializer[ApiDescription](formats => ({ + case json => + implicit val fmts: Formats = formats + ApiDescription( + (json \ "path").extract[String], + (json \ "description").extractOrElse(""), + (json \ "operations").extract[List[Operation]] + ) + }, { + case x: ApiDescription => + implicit val fmts = formats + ("path" -> x.path) ~ + ("description" -> x.description) ~ + ("operations" -> { + x.operations match { + case e:List[Operation] if(e.size > 0) => Extraction.decompose(e) + case _ => JNothing + } + }) + } + )) + + class ErrorResponseSerializer extends CustomSerializer[ErrorResponse](formats => ({ + case json => + implicit val fmts: Formats = formats + ErrorResponse( + (json \ "code").extract[Int], + (json \ "reason").extract[String] + ) + }, { + case x: ErrorResponse => + implicit val fmts = formats + ("code" -> x.code) ~ + ("reason" -> x.reason) + } + )) + + class OperationSerializer extends CustomSerializer[Operation](formats => ({ + case json => + implicit val fmts: Formats = formats + Operation( + (json \ "httpMethod").extract[String], + (json \ "summary").extract[String], + (json \ "notes").extract[String], + (json \ "responseClass").extract[String], + (json \ "nickname").extract[String], + (json \ "parameters").extract[List[Parameter]], + (json \ "errorResponses").extract[List[ErrorResponse]], + (json \ "deprecated").extractOpt[String] + ) + }, { + case x: Operation => + implicit val fmts = formats + ("httpMethod" -> x.httpMethod) ~ + ("summary" -> x.summary) ~ + ("notes" -> x.notes) ~ + ("responseClass" -> x.responseClass) ~ + ("nickname" -> x.nickname) ~ + ("parameters" -> Extraction.decompose(x.parameters)) ~ + ("errorResponses" -> { + x.errorResponses match { + case e: List[ErrorResponse] if(e.size > 0) => Extraction.decompose(e) + case _ => JNothing + } + }) ~ + ("deprecated" -> x.`deprecated`) + } + )) + + class ParameterSerializer extends CustomSerializer[Parameter](formats => ({ + case json => + implicit val fmts: Formats = formats + Parameter( + (json \ "name").extract[String], + (json \ "description").extract[String], + (json \ "defaultValue").extract[String], + (json \ "required").extractOrElse(false), + (json \ "allowMultiple").extractOrElse(false), + (json \ "dataType").extract[String], + (json \ "allowableValues").extract[AllowableValuesFoo], + (json \ "paramType").extract[String] + ) + }, { + case x: Parameter => + implicit val fmts = formats + ("name" -> x.name) ~ + ("description" -> x.description) ~ + ("defaultValue" -> x.defaultValue) ~ + ("required" -> x.required) ~ + ("allowMultiple" -> x.allowMultiple) ~ + ("dataType" -> x.dataType) ~ + ("allowableValues" -> { + x.allowableValues match { + case Any => JNothing // don't serialize when not a concrete type + case e:AllowableValuesFoo => Extraction.decompose(x.allowableValues) + case _ => JNothing + } + }) ~ + ("paramType" -> x.paramType) + } + )) + + class ModelSerializer extends CustomSerializer[Model](formats => ({ + case json => + implicit val fmts: Formats = formats + val output = new HashMap[String, ModelProperty] + val properties = (json \ "properties") match { + case JObject(entries) => { + entries.map({ + case (key, value) => output += key -> value.extract[ModelProperty] + }) + } + case _ => + } + + Model( + (json \ "id").extract[String], + (json \ "name").extract[String], + output.asJava, + (json \ "description").extractOpt[String] + ) + }, { + case x: Model => + implicit val fmts = formats + ("id" -> x.id) ~ + ("name" -> x.name) ~ + ("properties" -> { + x.properties match { + case e:java.util.Map[String, ModelProperty] => Extraction.decompose(e.asScala.toMap) + case _ => JNothing + } + }) + } + )) + + class ModelPropertySerializer extends CustomSerializer[ModelProperty] (formats => ({ + case json => + implicit val fmts: Formats = formats + ModelProperty( + `type` = (json \ "type").extractOrElse(""), + required = ((json \ "required").extractOrElse(false)), + description = (json \ "description").extractOpt[String], + allowableValues = (json \ "allowableValues").extract[AllowableValuesFoo], + items = (json \ "items").extractOpt[ModelRef] + ) + }, { + case x: ModelProperty => + implicit val fmts = formats + ("type" -> x.`type`) ~ + ("required" -> x.required) ~ + ("description" -> x.description) ~ + ("allowableValues" -> { + x.allowableValues match { + case Any => JNothing // don't serialize when not a concrete type + case e:AllowableValuesFoo => Extraction.decompose(x.allowableValues) + case _ => JNothing + } + }) ~ + ("items" -> Extraction.decompose(x.items)) + } + )) + + class ModelRefSerializer extends CustomSerializer[ModelRef](formats => ({ + case json => + implicit val fmts: Formats = formats + ModelRef( + (json \ "$ref").extract[String], + (json \ "type").extract[String] + ) + }, { + case x: ModelRef => + implicit val fmts = formats + ("$ref" -> x.ref) ~ + ("type" -> x.`type`) + } + )) + + class AllowableValuesSerializer extends CustomSerializer[AllowableValuesFoo](formats => ({ + case json => + implicit val fmts: Formats = formats + json \ "valueType" match { + case JString(x) if x.equalsIgnoreCase("list") => + AllowableListValues((json \ "values").extract[List[String]]) + case JString(x) if x.equalsIgnoreCase("range") => + AllowableRangeValues((json \ "min").extract[String], (json \ "max").extract[String]) + case _ => Any + } + }, { + case AllowableListValues(values, "LIST") => + implicit val fmts = formats + ("valueType" -> "LIST") ~ ("values" -> Extraction.decompose(values)) + case AllowableRangeValues(min, max) => + ("valueType" -> "RANGE") ~ ("min" -> min) ~ ("max" -> max) + } + )) +} \ No newline at end of file diff --git a/src/main/scala/com/wordnik/swagger/model/SwaggerModels.scala b/src/main/scala/com/wordnik/swagger/model/SwaggerModels.scala index bd3de13cf8a..1525d4140b0 100644 --- a/src/main/scala/com/wordnik/swagger/model/SwaggerModels.scala +++ b/src/main/scala/com/wordnik/swagger/model/SwaggerModels.scala @@ -26,10 +26,10 @@ case class ResourceListing( case class ApiListingReference(path:String, description: String) -abstract class AllowableValuesFoo -case object Any extends AllowableValuesFoo -case class AllowableListValues (values: List[String] = List(), valueType: String = "LIST") extends AllowableValuesFoo -case class AllowableRangeValues(min: String, max: String) extends AllowableValuesFoo +trait AllowableValuesFoo +case object Any extends AllowableValues with AllowableValuesFoo +case class AllowableListValues (values: List[String] = List(), valueType: String = "LIST") extends AllowableValues with AllowableValuesFoo +case class AllowableRangeValues(min: String, max: String) extends AllowableValues with AllowableValuesFoo // using java.util.Map because Jackon 2 isn't deserializing ListMap correctly, and ordered // insertion is required diff --git a/src/test/scala/ModelSerializersTest.scala b/src/test/scala/ModelSerializersTest.scala index b9e9c7663ea..99d92a4e06f 100644 --- a/src/test/scala/ModelSerializersTest.scala +++ b/src/test/scala/ModelSerializersTest.scala @@ -10,9 +10,367 @@ import org.scalatest.junit.JUnitRunner import org.scalatest.FlatSpec import org.scalatest.matchers.ShouldMatchers +import scala.collection.JavaConverters._ + +@RunWith(classOf[JUnitRunner]) +class ResourceListingSerializersTest extends FlatSpec with ShouldMatchers { + implicit val formats = SwaggerSerializers.formats + + it should "deserialize an ResourceListing with no apis" in { + val jsonString = """ + { + "apiVersion":"1.2.3", + "swaggerVersion":"1.1", + "basePath":"http://foo/bar" + } + """ + val json = parse(jsonString) + json.extract[ResourceListing] match { + case p: ResourceListing => { + p.apiVersion should be ("1.2.3") + p.swaggerVersion should be ("1.1") + p.basePath should be ("http://foo/bar") + p.apis.size should be (0) + } + case _ => fail("wrong type returned, should be ResourceListing") + } + } + + it should "serialize an ApiListingReference with no apis" in { + val l = ApiListingReference("/foo/bar", "the description") + write(l) should be ("""{"path":"/foo/bar","description":"the description"}""") + } + + it should "deserialize an ResourceListing" in { + val jsonString = """ + { + "apiVersion":"1.2.3", + "swaggerVersion":"1.1", + "basePath":"http://foo/bar", + "apis":[ + { + "path":"/a/b", + "description":"path ab apis" + },{ + "path":"/c", + "description":"path c apis" + } + ] + } + """ + val json = parse(jsonString) + json.extract[ResourceListing] match { + case p: ResourceListing => { + p.apiVersion should be ("1.2.3") + p.swaggerVersion should be ("1.1") + p.basePath should be ("http://foo/bar") + p.apis.size should be (2) + } + case _ => fail("wrong type returned, should be ResourceListing") + } + } + + it should "serialize an ApiListingReference" in { + val l = ApiListingReference("/foo/bar", "the description") + write(l) should be ("""{"path":"/foo/bar","description":"the description"}""") + } +} + +@RunWith(classOf[JUnitRunner]) +class ApiListingReferenceSerializersTest extends FlatSpec with ShouldMatchers { + implicit val formats = SwaggerSerializers.formats + + it should "deserialize an ApiListingReference" in { + val jsonString = """ + { + "path":"/foo/bar", + "description":"the description" + } + """ + val json = parse(jsonString) + json.extract[ApiListingReference] match { + case p: ApiListingReference => { + p.path should be ("/foo/bar") + p.description should be ("the description") + } + case _ => fail("wrong type returned, should be ApiListingReference") + } + } + + it should "serialize an ApiListingReference" in { + val l = ApiListingReference("/foo/bar", "the description") + write(l) should be ("""{"path":"/foo/bar","description":"the description"}""") + } +} + +@RunWith(classOf[JUnitRunner]) +class ApiDescriptionSerializersTest extends FlatSpec with ShouldMatchers { + implicit val formats = SwaggerSerializers.formats + + it should "deserialize an ApiDescription with no ops" in { + val jsonString = """ + { + "path":"/foo/bar", + "description":"the description" + } + """ + val json = parse(jsonString) + json.extract[ApiDescription] match { + case p: ApiDescription => { + p.path should be ("/foo/bar") + p.description should be ("the description") + p.operations.size should be (0) + } + case _ => fail("wrong type returned, should be ApiDescription") + } + } + + it should "serialize an ApiDescription with no operations" in { + val l = ApiDescription("/foo/bar", "the description") + write(l) should be ("""{"path":"/foo/bar","description":"the description"}""") + } + + it should "deserialize an ApiDescription" in { + val jsonString = """ + { + "path":"/foo/bar", + "description":"the description", + "operations":[ + { + "httpMethod":"GET", + "summary":"the summary", + "notes":"the notes", + "responseClass":"string", + "nickname":"getMeSomeStrings", + "parameters":[ + { + "name":"id", + "description":"the id", + "defaultValue":"-1", + "required":false, + "allowMultiple":true, + "dataType":"string", + "allowableValues":{ + "valueType":"LIST", + "values":["a","b","c"] + }, + "paramType":"query" + } + ] + } + ] + } + """ + val json = parse(jsonString) + json.extract[ApiDescription] match { + case p: ApiDescription => { + p.path should be ("/foo/bar") + p.description should be ("the description") + p.operations.size should be (1) + } + case _ => fail("wrong type returned, should be ApiDescription") + } + } + + it should "serialize an ApiDescription" in { + val l = ApiDescription( + "/foo/bar", + "the description", + List(Operation( + "get", + "the summary", + "the notes", + "string", + "getMeSomeStrings", + List(Parameter("id", "the id", "-1", false, true, "string", AllowableListValues(List("a","b","c")), "query")) + )) + ) + write(l) should be ("""{"path":"/foo/bar","description":"the description","operations":[{"httpMethod":"get","summary":"the summary","notes":"the notes","responseClass":"string","nickname":"getMeSomeStrings","parameters":[{"name":"id","description":"the id","defaultValue":"-1","required":false,"allowMultiple":true,"dataType":"string","allowableValues":{"valueType":"LIST","values":["a","b","c"]},"paramType":"query"}]}]}""") + } +} + +@RunWith(classOf[JUnitRunner]) +class OperationSerializersTest extends FlatSpec with ShouldMatchers { + implicit val formats = SwaggerSerializers.formats + + it should "deserialize an Operation" in { + val jsonString = """ + { + "httpMethod":"GET", + "summary":"the summary", + "notes":"the notes", + "responseClass":"string", + "nickname":"getMeSomeStrings", + "parameters":[ + { + "name":"id", + "description":"the id", + "defaultValue":"-1", + "required":false, + "allowMultiple":true, + "dataType":"string", + "allowableValues":{ + "valueType":"LIST", + "values":["a","b","c"] + }, + "paramType":"query" + } + ] + } + """ + val json = parse(jsonString) + json.extract[Operation] match { + case op: Operation => { + op.httpMethod should be ("GET") + op.summary should be ("the summary") + op.notes should be ("the notes") + op.responseClass should be ("string") + op.nickname should be ("getMeSomeStrings") + op.parameters.size should be (1) + } + case _ => fail("wrong type returned, should be Operation") + } + } + + it should "serialize an operation" in { + val op = Operation( + "get", + "the summary", + "the notes", + "string", + "getMeSomeStrings", + List(Parameter("id", "the id", "-1", false, true, "string", AllowableListValues(List("a","b","c")), "query")) + ) + write(op) should be ("""{"httpMethod":"get","summary":"the summary","notes":"the notes","responseClass":"string","nickname":"getMeSomeStrings","parameters":[{"name":"id","description":"the id","defaultValue":"-1","required":false,"allowMultiple":true,"dataType":"string","allowableValues":{"valueType":"LIST","values":["a","b","c"]},"paramType":"query"}]}""") + } +} + +@RunWith(classOf[JUnitRunner]) +class ErrorResponseSerializersTest extends FlatSpec with ShouldMatchers { + implicit val formats = SwaggerSerializers.formats + + it should "deserialize an ErrorResponse" in { + val jsonString = """ + { + "code":101, + "reason":"the reason" + } + """ + val json = parse(jsonString) + json.extract[ErrorResponse] match { + case p: ErrorResponse => { + p.code should be (101) + p.reason should be ("the reason") + } + case _ => fail("wrong type returned, should be ErrorResponse") + } + } + + it should "serialize an operation" in { + val l = ErrorResponse(101, "the reason") + write(l) should be ("""{"code":101,"reason":"the reason"}""") + } +} + +@RunWith(classOf[JUnitRunner]) +class ParameterSerializersTest extends FlatSpec with ShouldMatchers { + implicit val formats = SwaggerSerializers.formats + + it should "deserialize a parameter" in { + val jsonString = """ + { + "name":"name", + "description":"description", + "defaultValue":"tony", + "required":false, + "allowMultiple":true, + "dataType":"string", + "paramType":"query" + } + """ + val json = parse(jsonString) + json.extract[Parameter] match { + case p: Parameter => { + p.name should be ("name") + p.description should be ("description") + p.defaultValue should be ("tony") + p.required should be (false) + p.allowMultiple should be (true) + p.dataType should be ("string") + p.paramType should be ("query") + } + case _ => fail("wrong type returned, should be Parameter") + } + } + + it should "serialize a parameter" in { + val l = Parameter("name", "description", "tony", false, true, "string", Any, "query") + write(l) should be ("""{"name":"name","description":"description","defaultValue":"tony","required":false,"allowMultiple":true,"dataType":"string","paramType":"query"}""") + } +} + +@RunWith(classOf[JUnitRunner]) +class ModelSerializationTest extends FlatSpec with ShouldMatchers { + implicit val formats = SwaggerSerializers.formats + + it should "deserialize a model" in { + val jsonString = """ + { + "id":"Foo", + "name":"Bar", + "properties": { + "id": { + "type":"string", + "required":true, + "description":"id" + }, + "name": { + "type":"string", + "required":false, + "description":"name" + } + }, + "description":"nice model" + } + """ + val json = parse(jsonString) + json.extract[Model] match { + case model: Model => { + model.id should be ("Foo") + model.name should be ("Bar") + model.properties should not be (null) + model.properties.size should be (2) + model.description should be (Some("nice model")) + model.properties.asScala("id") match { + case e: ModelProperty => { + e.`type` should be ("string") + e.required should be (true) + e.description should be (Some("id")) + } + case _ => fail("missing property id") + } + model.properties.asScala("name") match { + case e: ModelProperty => { + e.`type` should be ("string") + e.required should be (false) + e.description should be (Some("name")) + } + case _ => fail("missing property name") + } + } + case _ => fail("expected type Model") + } + } + + it should "serialize a model" in { + val ref = Model("Foo", "Bar", (Map("s" -> ModelProperty("string", true, Some("a string")))).asJava) + write(ref) should be ("""{"id":"Foo","name":"Bar","properties":{"s":{"type":"string","required":true,"description":"a string"}}}""") + } +} + @RunWith(classOf[JUnitRunner]) class ModelRefSerializationTest extends FlatSpec with ShouldMatchers { - implicit val formats = DefaultFormats + new ModelRefSerializer + implicit val formats = SwaggerSerializers.formats it should "deserialize a model ref" in { val jsonString = """ @@ -39,7 +397,7 @@ class ModelRefSerializationTest extends FlatSpec with ShouldMatchers { @RunWith(classOf[JUnitRunner]) class ModelPropertySerializationTest extends FlatSpec with ShouldMatchers { - implicit val formats = DefaultFormats + new AllowableValuesSerializer + new ModelPropertySerializer + new ModelRefSerializer + implicit val formats = SwaggerSerializers.formats it should "deserialize a model property with allowable values and ref" in { val jsonString = """ @@ -120,19 +478,15 @@ class ModelPropertySerializationTest extends FlatSpec with ShouldMatchers { val jsonString = """ { "type":"string", - "required":false, - "description":"nice", - "allowableValues": { - "valueType":"LIST", - "values":["1","2","3"] - } + "required":true, + "description":"nice" } """ val json = parse(jsonString) json.extract[ModelProperty] match { case p: ModelProperty => { p.`type` should be ("string") - p.required should be (false) + p.required should be (true) p.description should be (Some("nice")) } case _ => fail("expected type ModelProperty") @@ -147,7 +501,7 @@ class ModelPropertySerializationTest extends FlatSpec with ShouldMatchers { @RunWith(classOf[JUnitRunner]) class AllowableValuesSerializersTest extends FlatSpec with ShouldMatchers { - implicit val formats = DefaultFormats + new AllowableValuesSerializer + implicit val formats = SwaggerSerializers.formats it should "deserialize allowable value list" in { val allowableValuesListString = """