aboutsummaryrefslogtreecommitdiff
path: root/core-rest/src/main/scala/xyz/driver/core/swagger.scala
diff options
context:
space:
mode:
Diffstat (limited to 'core-rest/src/main/scala/xyz/driver/core/swagger.scala')
-rw-r--r--core-rest/src/main/scala/xyz/driver/core/swagger.scala164
1 files changed, 164 insertions, 0 deletions
diff --git a/core-rest/src/main/scala/xyz/driver/core/swagger.scala b/core-rest/src/main/scala/xyz/driver/core/swagger.scala
new file mode 100644
index 0000000..0c1e15d
--- /dev/null
+++ b/core-rest/src/main/scala/xyz/driver/core/swagger.scala
@@ -0,0 +1,164 @@
+package xyz.driver.core
+
+import java.lang.annotation.Annotation
+import java.lang.reflect.Type
+import java.util
+
+import com.fasterxml.jackson.databind.{BeanDescription, ObjectMapper}
+import com.fasterxml.jackson.databind.`type`.ReferenceType
+import io.swagger.converter._
+import io.swagger.jackson.AbstractModelConverter
+import io.swagger.models.{Model, ModelImpl}
+import io.swagger.models.properties._
+import io.swagger.util.{Json, PrimitiveType}
+import spray.json._
+
+object swagger {
+
+ def configureCustomSwaggerModels(
+ customPropertiesExamples: Map[Class[_], Property],
+ customObjectsExamples: Map[Class[_], JsValue]) = {
+ ModelConverters
+ .getInstance()
+ .addConverter(new CustomSwaggerJsonConverter(Json.mapper(), customPropertiesExamples, customObjectsExamples))
+ }
+
+ object CustomSwaggerJsonConverter {
+
+ def stringProperty(pattern: Option[String] = None, example: Option[String] = None): Property = {
+ make(new StringProperty()) { sp =>
+ sp.required(true)
+ example.foreach(sp.example)
+ pattern.foreach(sp.pattern)
+ }
+ }
+
+ def enumProperty[V](values: V*): Property = {
+ make(new StringProperty()) { sp =>
+ for (v <- values) sp._enum(v.toString)
+ sp.setRequired(true)
+ }
+ }
+
+ def numericProperty(example: Option[AnyRef] = None): Property = {
+ make(PrimitiveType.DECIMAL.createProperty()) { dp =>
+ dp.setRequired(true)
+ example.foreach(dp.setExample)
+ }
+ }
+
+ def booleanProperty(): Property = {
+ make(new BooleanProperty()) { bp =>
+ bp.setRequired(true)
+ }
+ }
+ }
+
+ @SuppressWarnings(Array("org.wartremover.warts.Null"))
+ class CustomSwaggerJsonConverter(
+ mapper: ObjectMapper,
+ customProperties: Map[Class[_], Property],
+ customObjects: Map[Class[_], JsValue])
+ extends AbstractModelConverter(mapper) {
+ import CustomSwaggerJsonConverter._
+
+ override def resolveProperty(
+ `type`: Type,
+ context: ModelConverterContext,
+ annotations: Array[Annotation],
+ chain: util.Iterator[ModelConverter]): Property = {
+ val javaType = Json.mapper().constructType(`type`)
+
+ Option(javaType.getRawClass)
+ .flatMap { cls =>
+ customProperties.get(cls)
+ }
+ .orElse {
+ `type` match {
+ case rt: ReferenceType if isOption(javaType.getRawClass) && chain.hasNext =>
+ val nextType = rt.getContentType
+ val nextResolved = Option(resolveProperty(nextType, context, annotations, chain)).getOrElse(
+ chain.next().resolveProperty(nextType, context, annotations, chain))
+ nextResolved.setRequired(false)
+ Option(nextResolved)
+ case t if chain.hasNext =>
+ val nextResolved = chain.next().resolveProperty(t, context, annotations, chain)
+ nextResolved.setRequired(true)
+ Option(nextResolved)
+ case _ =>
+ Option.empty[Property]
+ }
+ }
+ .orNull
+ }
+
+ @SuppressWarnings(Array("org.wartremover.warts.Null"))
+ override def resolve(`type`: Type, context: ModelConverterContext, chain: util.Iterator[ModelConverter]): Model = {
+
+ val javaType = Json.mapper().constructType(`type`)
+
+ (getEnumerationInstance(javaType.getRawClass) match {
+ case Some(_) => Option.empty[Model] // ignore scala enums
+ case None =>
+ val customObjectModel = customObjects.get(javaType.getRawClass).map { objectExampleJson =>
+ val properties = objectExampleJson.asJsObject.fields.mapValues(parseJsonValueToSwaggerProperty).flatMap {
+ case (key, value) => value.map(v => key -> v)
+ }
+
+ val beanDesc = _mapper.getSerializationConfig.introspect[BeanDescription](javaType)
+ val name = _typeName(javaType, beanDesc)
+
+ make(new ModelImpl()) { model =>
+ model.name(name)
+ properties.foreach { case (field, property) => model.addProperty(field, property) }
+ }
+ }
+
+ customObjectModel.orElse {
+ if (chain.hasNext) {
+ val next = chain.next()
+ Option(next.resolve(`type`, context, chain))
+ } else {
+ Option.empty[Model]
+ }
+ }
+ }).orNull
+ }
+
+ private def parseJsonValueToSwaggerProperty(jsValue: JsValue): Option[Property] = {
+ import scala.collection.JavaConverters._
+
+ jsValue match {
+ case JsArray(elements) =>
+ elements.headOption.flatMap(parseJsonValueToSwaggerProperty).map { itemProperty =>
+ new ArrayProperty(itemProperty)
+ }
+ case JsObject(subFields) =>
+ val subProperties = subFields.mapValues(parseJsonValueToSwaggerProperty).flatMap {
+ case (key, value) => value.map(v => key -> v)
+ }
+ Option(new ObjectProperty(subProperties.asJava))
+ case JsBoolean(_) => Option(booleanProperty())
+ case JsNumber(value) => Option(numericProperty(example = Option(value)))
+ case JsString(value) => Option(stringProperty(example = Option(value)))
+ case _ => Option.empty[Property]
+ }
+ }
+
+ private def getEnumerationInstance(cls: Class[_]): Option[Enumeration] = {
+ if (cls.getFields.map(_.getName).contains("MODULE$")) {
+ val javaUniverse = scala.reflect.runtime.universe
+ val m = javaUniverse.runtimeMirror(Thread.currentThread().getContextClassLoader)
+ val moduleMirror = m.reflectModule(m.staticModule(cls.getName))
+ moduleMirror.instance match {
+ case enumInstance: Enumeration => Some(enumInstance)
+ case _ => None
+ }
+ } else {
+ None
+ }
+ }
+
+ private def isOption(cls: Class[_]): Boolean = cls.equals(classOf[scala.Option[_]])
+ }
+}