aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVlad Uspensky <v.uspenskiy@icloud.com>2017-04-11 16:21:33 -0700
committerGitHub <noreply@github.com>2017-04-11 16:21:33 -0700
commitc890011c2dba22f52b007848d6d41e8acde6d38d (patch)
treeaccdaf6c1450745bdbac97a7ba69d46130ad00cc
parent686d4e2845bd6335d998dbc33f6a7d1ae49191a2 (diff)
parentf92036251d778a6c7a0445a46c1fdda930e19cfc (diff)
downloaddriver-core-c890011c2dba22f52b007848d6d41e8acde6d38d.tar.gz
driver-core-c890011c2dba22f52b007848d6d41e8acde6d38d.tar.bz2
driver-core-c890011c2dba22f52b007848d6d41e8acde6d38d.zip
Merge pull request #32 from drivergroup/SCALA-2v0.11.1
Custom Swagger Json Converter for SCALA-2
-rw-r--r--build.sbt4
-rw-r--r--src/main/scala/xyz/driver/core/database/Dal.scala8
-rw-r--r--src/main/scala/xyz/driver/core/swagger.scala149
3 files changed, 158 insertions, 3 deletions
diff --git a/build.sbt b/build.sbt
index 2cfcac3..c5fd1dd 100644
--- a/build.sbt
+++ b/build.sbt
@@ -13,12 +13,12 @@ lazy val core = (project in file("."))
"org.scalatest" % "scalatest_2.11" % "2.2.6" % "test",
"org.scalacheck" %% "scalacheck" % "1.12.5" % "test",
"org.mockito" % "mockito-core" % "1.9.5" % "test",
+ "com.github.swagger-akka-http" %% "swagger-akka-http" % "0.9.1",
"com.amazonaws" % "aws-java-sdk-s3" % "1.11.26",
"com.typesafe.slick" %% "slick" % "3.1.1",
"com.typesafe" % "config" % "1.2.1",
"com.typesafe.scala-logging" %% "scala-logging" % "3.1.0",
- "ch.qos.logback" % "logback-classic" % "1.1.3",
- "com.github.swagger-akka-http" %% "swagger-akka-http" % "0.7.1"
+ "ch.qos.logback" % "logback-classic" % "1.1.3"
))
.gitPluginConfiguration
.settings(lintingSettings ++ formatSettings)
diff --git a/src/main/scala/xyz/driver/core/database/Dal.scala b/src/main/scala/xyz/driver/core/database/Dal.scala
index d863e50..1bc3827 100644
--- a/src/main/scala/xyz/driver/core/database/Dal.scala
+++ b/src/main/scala/xyz/driver/core/database/Dal.scala
@@ -3,7 +3,7 @@ package xyz.driver.core.database
import slick.lifted.AbstractTable
import scala.concurrent.{ExecutionContext, Future}
-import scalaz.{ListT, Monad}
+import scalaz.{ListT, Monad, OptionT}
import scalaz.std.scalaFuture._
trait Dal {
@@ -13,6 +13,12 @@ trait Dal {
def execute[D](operations: T[D]): Future[D]
def noAction[V](v: V): T[V]
def customAction[R](action: => Future[R]): T[R]
+
+ def execute[D](action: => OptionT[T, D]): OptionT[Future, D] =
+ action.mapT(execute[Option[D]])
+
+ def customAction[R](action: => OptionT[Future, R]): OptionT[T, R] =
+ OptionT[T, R](customAction(action.run))
}
class FutureDal(executionContext: ExecutionContext) extends Dal {
diff --git a/src/main/scala/xyz/driver/core/swagger.scala b/src/main/scala/xyz/driver/core/swagger.scala
new file mode 100644
index 0000000..3a6faff
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/swagger.scala
@@ -0,0 +1,149 @@
+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 {
+
+ 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)
+ }
+ }
+ }
+
+ 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
+ }
+
+ override def resolve(`type`: Type, context: ModelConverterContext, chain: util.Iterator[ModelConverter]): Model = {
+
+ val javaType = Json.mapper().constructType(`type`)
+
+ (getEnumerationInstance(javaType.getRawClass) match {
+ case Some(enumInstance) => 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(value) => 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[_]])
+ }
+}