All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wordnik.swagger.model.SwaggerModelSerializer.scala Maven / Gradle / Ivy

package com.wordnik.swagger.model

import com.wordnik.swagger.codegen.spec.ValidationMessage
import legacy.LegacySerializers

import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization.{read, write}

import scala.collection.mutable.{ListBuffer, LinkedHashMap}

object SwaggerSerializers {
  import ValidationMessage._

  val jsonSchemaTypeMap = Map(
    // simple types
    ("integer", "int32") -> "int",
    ("integer", "int64") -> "long",
    ("number", "float") -> "float",
    ("number", "double") -> "double",
    ("string", "byte") -> "byte",
    ("string", "date") -> "Date",
    ("string", "date-time") -> "Date",

    // containers
    ("array", "") -> "Array",
    ("set", "") -> "Set"
  )

  def toJsonSchema(name: String, `type`: String): JObject = {
    `type` match {
      case "int"       => (name -> "integer") ~ ("format" -> "int32")
      case "long"      => (name -> "integer") ~ ("format" -> "int64")
      case "float"     => (name -> "number")  ~ ("format" -> "float")
      case "double"    => (name -> "number")  ~ ("format" -> "double")
      case "string"    => (name -> "string")  ~ ("format" -> JNothing)
      case "byte"      => (name -> "string")  ~ ("format" -> "byte")
      case "boolean"   => (name -> "boolean") ~ ("format" -> JNothing)
      case "Date"      => (name -> "string")  ~ ("format" -> "date-time")
      case "date"      => (name -> "string")  ~ ("format" -> "date")
      case "date-time" => (name -> "string")  ~ ("format" -> "date-time")
      case "Array"     => (name -> "array")
      case _           => {
        val ComplexTypeMatcher = "([a-zA-Z]*)\\[([a-zA-Z\\.\\-]*)\\].*".r
        `type` match {
          case ComplexTypeMatcher(container, value) => 
            toJsonSchemaContainer(container) ~ {
              ("items" -> {if(isSimpleType(value))
                  toJsonSchema("type", value)
                else
                  toJsonSchema("$ref", value)})
            }
          case _ => (name -> `type`)    ~ ("format" -> JNothing)
        }
      }
    }
  }

  def toJsonSchemaContainer(name: String): JObject = {
    name match {
      case "List"      => ("type" -> "array")   ~ ("format" -> JNothing)
      case "Array"     => ("type" -> "array")   ~ ("format" -> JNothing)
      case "Set"       => ("type" -> "array")   ~ ("uniqueItems" -> true)
      case _           => ("type" -> JNothing)
    }
  }

  def isSimpleType(name: String) = {
    Set("int", "long", "float", "double", "string", "byte", "boolean", "Date", "date", "date-time", "array", "set").contains(name)
  }

  def formats(version: String) = {
    version match {
      case "1.1" => LegacySerializers.formats
      case "1.2" => {
        DefaultFormats + 
          new ModelSerializer + 
          new ModelPropertySerializer +
          new ModelRefSerializer + 
          new AllowableValuesSerializer + 
          new ParameterSerializer +
          new OperationSerializer +
          new ResponseMessageSerializer +
          new ApiDescriptionSerializer +
          new ApiListingReferenceSerializer +
          new ResourceListingSerializer +
          new ApiListingSerializer
      }
      case _ => throw new IllegalArgumentException("%s is not a valid Swagger version".format(version))
    }
  }

  def validationMessages = ValidationMessage.validationMessages

  def !!(element: AnyRef, elementType: String, elementId: String, message: String, level: String = ERROR) {
    val msg = new ValidationMessage(element, elementType, elementId, message, level)
    ValidationMessage.validationMessages += msg
  }

  class ApiListingSerializer extends CustomSerializer[ApiListing](implicit formats => ({
    case json =>
      val authorizations = (json \ "authorizations").extractOpt[Map[String, AuthorizationType]] match {
        case Some(m) => m.values.toList
        case _ => List.empty
      }
      ApiListing(
        (json \ "apiVersion").extractOrElse({
          !!(json, RESOURCE, "apiVersion", "missing required field", ERROR)
          ""
        }),
        (json \ "swaggerVersion").extractOrElse({
          !!(json, RESOURCE, "swaggerVersion", "missing required field", ERROR)
          ""
        }),
        (json \ "basePath").extractOrElse({
          !!(json, RESOURCE, "basePath", "missing required field", ERROR)
          ""
        }),
        (json \ "resourcePath").extractOrElse({
          !!(json, RESOURCE, "resourcePath", "missing recommended field", WARNING)
          ""
        }),
        (json \ "produces").extract[List[String]],
        (json \ "consumes").extract[List[String]],
        (json \ "protocols").extract[List[String]],
        authorizations,
        (json \ "apis").extract[List[ApiDescription]],
        (json \ "models").extractOpt[Map[String, Model]],
        (json \ "description").extractOpt[String],
        (json \ "position").extractOrElse(0)
      )
    }, {
      case x: ApiListing =>
      ("apiVersion" -> x.apiVersion) ~
      ("resourcePath" -> x.resourcePath) ~
      ("swaggerVersion" -> x.swaggerVersion) ~
      ("basePath" -> x.basePath) ~
      ("apis" -> {
        x.apis match {
          case e: List[ApiDescription] if (e.size > 0) => Extraction.decompose(e)
          case _ => JNothing
        }
      }) ~
      ("models" -> {
        x.models match {
          case Some(e) if (e.size > 0) => Extraction.decompose(e)
          case _ => JNothing
        }
      })
    }
  ))

  class ResourceListingSerializer extends CustomSerializer[ResourceListing](implicit formats => ({
    case json =>

      val apis = (json \ "apis").extract[List[ApiListingReference]]

      ResourceListing(
        (json \ "apiVersion").extractOrElse({
          !!(json, RESOURCE_LISTING, "apiVersion", "missing required field", ERROR)
          ""
        }),
        (json \ "swaggerVersion").extractOrElse({
          !!(json, RESOURCE_LISTING, "swaggerVersion", "missing required field", ERROR)
          ""
        }),
        "",
        apis.filter(a => a.path != "" && a.path != null)
      )
    }, {
      case x: ResourceListing =>
      ("apiVersion" -> x.apiVersion) ~
      ("swaggerVersion" -> x.swaggerVersion) ~
      ("apis" -> {
        x.apis match {
          case e: List[ApiListingReference] if (e.size > 0) => Extraction.decompose(e)
          case _ => JNothing
        }
      })
    }
  ))

  class ApiListingReferenceSerializer extends CustomSerializer[ApiListingReference](implicit formats => ({
    case json =>
      ApiListingReference(
        (json \ "path").extractOrElse({
          !!(json, RESOURCE, "path", "missing required field", ERROR)
          ""
        }),
        (json \ "description").extractOpt[String]
      )
    }, {
      case x: ApiListingReference =>
      ("path" -> x.path) ~
      ("description" -> x.description)
    }
  ))

  class ApiDescriptionSerializer extends CustomSerializer[ApiDescription](implicit formats => ({
    case json =>
      ApiDescription(
        (json \ "path").extractOrElse({
          !!(json, RESOURCE_LISTING, "path", "missing required field", ERROR)
          ""
        }),
        (json \ "description").extractOpt[String],
        (json \ "operations").extract[List[Operation]]
      )
    }, {
      case x: ApiDescription =>
      ("path" -> x.path) ~
      ("description" -> x.description) ~
      ("operations" -> {
        x.operations match {
          case e:List[Operation] if(e.size > 0) => Extraction.decompose(e)
          case _ => JNothing
        }
      })
    }
  ))

  class ResponseMessageSerializer extends CustomSerializer[ResponseMessage](implicit formats => ({
    case json =>
      ResponseMessage(
        (json \ "code").extractOrElse({
          !!(json, ERROR, "code", "missing required field", ERROR)
          0
        }),
        (json \ "message").extractOrElse({
          !!(json, ERROR, "reason", "missing required field", ERROR)
          ""
        })
      )
    }, {
      case x: ResponseMessage =>
      ("code" -> x.code) ~
      ("message" -> x.message)
    }
  ))

  class OperationSerializer extends CustomSerializer[Operation](implicit formats => ({
    case json =>

      val authorizations = (json \ "authorizations").extractOpt[Map[String, AuthorizationType]] match {
        case Some(m) => m.values.toList
        case _ => List.empty
      }
      val t =  SwaggerSerializers.jsonSchemaTypeMap.getOrElse(
            ((json \ "type").extractOrElse(""), (json \ "format").extractOrElse(""))
          , (json \ "type").extractOrElse(""))

      val inner = {
        val items = new scala.collection.mutable.HashSet[String]
        val map = new scala.collection.mutable.HashMap[String, String]
        (json \ "items") match {
          case JObject(e) => {
            for(a <- e) {
              a._2 match {
                case e: JString => map += a._1 -> e.s
                case _ =>
              }
            }
            val `type` = map.getOrElse("type", "")
            val format = map.getOrElse("format", "")
            if(map.contains("$ref")) {
              Some(map("$ref"))
            }
            else
              Option(jsonSchemaTypeMap.getOrElse((`type`,format), `type`))
          }
          case _ => None
        }
      }
      val responseClass = inner match {
        case Some(a) => "%s[%s]".format(t, a)
        case _ => t
      }

      if(responseClass == "" || responseClass == null){
        !!(json, OPERATION, "responseClass", "missing required field", ERROR)
      }

      Operation(
        (json \ "httpMethod").extractOrElse(
          (json \ "method").extractOrElse({
            !!(json, OPERATION, "method", "missing required field", ERROR)
            ""
          })
        ),
        (json \ "summary").extract[String],
        (json \ "notes").extractOrElse(""),
        responseClass,
        (json \ "nickname").extractOrElse({
          !!(json, OPERATION, "nickname", "missing required field", ERROR)
          ""
        }),
        (json \ "position").extractOrElse(0),
        (json \ "produces").extract[List[String]],
        (json \ "consumes").extract[List[String]],
        (json \ "protocols").extract[List[String]],
        authorizations,
        (json \ "parameters").extract[List[Parameter]],
        (json \ "responseMessages").extract[List[ResponseMessage]],
        (json \ "deprecated").extractOpt[String]
      )
    }, {
      case x: Operation =>

      val ComplexTypeMatcher = "([a-zA-Z]*)\\[([a-zA-Z\\.\\-]*)\\].*".r
      val output = x.responseClass match {
        case ComplexTypeMatcher(container, value) => 
          toJsonSchemaContainer(container) ~ {
            ("items" -> {if(isSimpleType(value))
                toJsonSchema("type", value)
              else
                toJsonSchema("$ref", value)})
          }
        case _ => toJsonSchema("type", x.responseClass)    ~ ("format" -> JNothing)
      }

      ("method" -> x.method) ~
      ("summary" -> x.summary) ~
      ("notes" -> x.notes) ~
      output ~
      ("nickname" -> x.nickname) ~
      ("parameters" -> Extraction.decompose(x.parameters)) ~
      ("responseMessages" -> {
        x.responseMessages match {
          case e: List[ResponseMessage] 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

      val output = new ListBuffer[String]
      (json \ "enum") match {
        case JArray(entries) => entries.map {
          case e: JInt => output += e.num.toString
          case e: JBool => output += e.value.toString
          case e: JString => output += e.s
          case e: JDouble => output += e.num.toString
          case _ =>
        }
        case _ =>
      }
      val allowableValues = {
        if(output.size > 0) AllowableListValues(output.toList)
        else {
          val min = (json \ "min") match {
            case e: JObject => e.extract[String]
            case e: JString => e.s
            case e: JInt => e.num.toString
            case e: JDouble => e.num.toString          
            case _ => ""
          }
          val max = (json \ "max") match {
            case e: JObject => e.extract[String]
            case e: JString => e.s
            case e: JInt => e.num.toString
            case e: JDouble => e.num.toString          
            case _ => ""
          }
          if(min != "" && max != "")
            AllowableRangeValues(min, max)
          else
            AnyAllowableValues
        }
      }

      val t =  SwaggerSerializers.jsonSchemaTypeMap.getOrElse(
            ((json \ "type").extractOrElse(""), (json \ "format").extractOrElse(""))
          , (json \ "type").extractOrElse(""))

      val inner = {
        val items = new scala.collection.mutable.HashSet[String]
        val map = new scala.collection.mutable.HashMap[String, String]
        (json \ "items") match {
          case JObject(e) => {
            for(a <- e) {
              a._2 match {
                case e: JString => map += a._1 -> e.s
                case _ =>
              }
            }
            val `type` = map.getOrElse("type", "")
            val format = map.getOrElse("format", "")
            if(map.contains("$ref")) {
              Some(map("$ref"))
            }
            else
              Option(jsonSchemaTypeMap.getOrElse((`type`,format), `type`))
          }
          case _ => None
        }
      }
      val `type` = inner match {
        case Some(a) => "%s[%s]".format(t, a)
        case _ => t
      }
      Parameter(
        name = (json \ "name").extractOrElse({
          !!(json, OPERATION_PARAM, "reason", "missing parameter name", WARNING)
          ""
        }),
        description = (json \ "description").extractOpt[String],
        defaultValue = (json \ "defaultValue") match {
          case e:JInt => Some(e.num.toString)
          case e:JBool => Some(e.value.toString)
          case e:JString => Some(e.s)
          case e:JDouble => Some(e.num.toString)
          case _ => None
        },
        required = (json \ "required") match {
          case e:JString => e.s.toBoolean
          case e:JBool => e.value
          case _ => false
        },
        allowMultiple = (json \ "allowMultiple").extractOrElse(false),
        dataType = `type`,
        allowableValues = allowableValues,
        paramType = (json \ "paramType").extractOrElse({
          !!(json, OPERATION_PARAM, "paramType", "missing required field", ERROR)
          ""
        })
      )
    }, {
      case x: Parameter =>
      implicit val fmts = formats
      val output = ("name" -> x.name) ~
      ("description" -> x.description) ~
      ("defaultValue" -> x.defaultValue) ~
      ("required" -> x.required) ~
      ("allowMultiple" -> x.allowMultiple) ~
      toJsonSchema("type", x.dataType) ~
      ("paramType" -> x.paramType)

      x.allowableValues match {
        case AllowableListValues(values, "LIST") => 
          output ~ ("enum" -> Extraction.decompose(values))
        case AllowableRangeValues(min, max)  => 
          output ~ ("minimum" -> min) ~ ("maximum" -> max)
        case _ => output
      }
    }
  ))

  class ModelSerializer extends CustomSerializer[Model](implicit formats => ({
    case json =>
      val output = new LinkedHashMap[String, ModelProperty]
      val required = (json \ "required").extract[Set[String]]
      json \ "properties" match {
        case JObject(entries) => {
          entries.map({
            case (key, value) => {
              val prop = value.extract[ModelProperty]
              if(required.contains(key))
                output += key -> prop.copy(required = true)
              else
                output += key -> prop
            }
          })
        }
        case _ =>
      }

      Model(
        (json \ "id").extractOrElse({
          !!(json, MODEL, "id", "missing required field", ERROR)
          ""
        }),
        (json \ "name").extractOrElse(""),
        (json \ "qualifiedType").extractOrElse((json \ "id").extractOrElse("")),
        output,
        (json \ "description").extractOpt[String]
      )
    }, {
    case x: Model =>
      val required: List[String] = (for((name, prop) <- x.properties) yield {
        if(prop.required) Some(name)
        else None
      }).flatten.toList

      ("id" -> x.id) ~
      ("name" -> x.name) ~
      ("required" -> (required.size match {
        case 0 => JNothing
        case _ => Extraction.decompose(required)
      })) ~
      ("properties" -> {
        (x.properties: @unchecked) match {
          case e: LinkedHashMap[String, ModelProperty] => Extraction.decompose(e.toMap)
          case _ => JNothing
        }
      })
    }
  ))

  class ModelPropertySerializer extends CustomSerializer[ModelProperty] (implicit formats => ({
    case json =>
      val `type` = (json \ "$ref") match {
        case e: JString => e.s
        case _ => {
          // convert the jsonschema types into swagger types.  Note, this logic will move elsewhere soon
          val t = SwaggerSerializers.jsonSchemaTypeMap.getOrElse(
            ((json \ "type").extractOrElse(""), (json \ "format").extractOrElse(""))
          , (json \ "type").extractOrElse(""))
          val isUnique = (json \ "uniqueItems") match {
            case e: JBool => e.value
            case e: JString => e.s.toBoolean
            case _ => false
          }
          if(t == "Array" && isUnique) "Set"
          else t
        }
      }

      val output = new ListBuffer[String]
      json \ "enum" match {
        case JArray(entries) => entries.map {
          case e: JInt => output += e.num.toString
          case e: JBool => output += e.value.toString
          case e: JString => output += e.s
          case e: JDouble => output += e.num.toString
          case _ =>
        }
        case _ =>
      }
      val allowableValues = {
        if(output.size > 0) AllowableListValues(output.toList)
        else {
          val min = (json \ "min") match {
            case e: JObject => e.extract[String]
            case _ => ""
          }
          val max = (json \ "max") match {
            case e: JObject => e.extract[String]
            case _ => ""
          }
          if(min != "" && max != "")
            AllowableRangeValues(min, max)
          else
            AnyAllowableValues
        }
      }
      ModelProperty(
        `type` = `type`,
        `qualifiedType` = (json \ "qualifiedType").extractOpt[String].getOrElse(`type`),
        required = (json \ "required") match {
          case e:JString => e.s.toBoolean
          case e:JBool => e.value
          case _ => false
        },
        description = (json \ "description").extractOpt[String],
        allowableValues = allowableValues,
        items = {
          (json \ "items").extractOpt[ModelRef] match {
            case Some(e: ModelRef) if(e.`type` != null || e.ref != None) => Some(e)
            case _ => None
          }
        }
      )
    }, {
    case x: ModelProperty =>
      val output = toJsonSchema("type", x.`type`) ~
      ("description" -> x.description) ~
      ("items" -> Extraction.decompose(x.items))

      x.allowableValues match {
        case AllowableListValues(values, "LIST") => 
          output ~ ("enum" -> Extraction.decompose(values))
        case AllowableRangeValues(min, max)  => 
          output ~ ("minimum" -> min) ~ ("maximum" -> max)
        case _ => output
      }
    }
  ))

  class ModelRefSerializer extends CustomSerializer[ModelRef](implicit formats => ({
    case json =>

      val `type` = (json \ "type") match {
        case e: JString => e.s
        case _ => ""
      }
      val format = (json \ "format") match {
        case e: JString => e.s
        case _ => ""
      }
      val jsonSchemaType = jsonSchemaTypeMap.getOrElse((`type`, format), `type`)

      ModelRef(
        jsonSchemaType match {
          case e: String if(e != "") => e
          case _ => null
        },
        (json \ "$ref").extractOpt[String]
      )
    }, {
      case x: ModelRef =>
      ("type" -> {
        x.`type` match {
          case e:String => Some(e)
          case _ => None
        }
      }) ~
      ("$ref" -> x.ref)
    }
  ))

  class AllowableValuesSerializer extends CustomSerializer[AllowableValues](implicit formats => ({
    case json =>
      json \ "valueType" match {
        case JString(x) if x.equalsIgnoreCase("list") => {
          val output = new ListBuffer[String]
          val properties = (json \ "values") match {
            case JArray(entries) => entries.map {
              case e:JInt => output += e.num.toString
              case e:JBool => output += e.value.toString
              case e:JString => output += e.s
              case e:JDouble => output += e.num.toString
              case _ =>
            }
            case _ =>
          }
          AllowableListValues(output.toList)
        }
        case JString(x) if x.equalsIgnoreCase("range") =>
          AllowableRangeValues((json \ "min").extract[String], (json \ "max").extract[String])
        case _ => AnyAllowableValues
      }
    }, {
      case AllowableListValues(values, "LIST") => 
        ("valueType" -> "LIST") ~ ("values" -> Extraction.decompose(values))
      case AllowableRangeValues(min, max)  => 
        ("valueType" -> "RANGE") ~ ("min" -> min) ~ ("max" -> max)
    }
  ))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy