From 572494a893d47e698b1a78c95fb99151b5e5d1a9 Mon Sep 17 00:00:00 2001 From: vlad Date: Fri, 7 Apr 2017 08:45:51 -0700 Subject: swagger-akka-http update to 0.9.1 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 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) -- cgit v1.2.3 From 5649383cd7e830baf66049f2db4060bc581dfa99 Mon Sep 17 00:00:00 2001 From: vlad Date: Mon, 10 Apr 2017 12:23:46 -0700 Subject: Customizable Scala Json converter for SCALA-2 --- src/main/scala/xyz/driver/core/swagger.scala | 149 +++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/main/scala/xyz/driver/core/swagger.scala 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..fd519f9 --- /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.map(sp.example) + pattern.map(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(e => dp.setExample(e)) + } + } + + 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 => + rt.getContentType + val nextType = rt.getContentType + val nextResolved = 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[_]]) + } +} -- cgit v1.2.3 From 30d771fe7d2e19c1d26a04bbe0eb771fd8513d67 Mon Sep 17 00:00:00 2001 From: vlad Date: Mon, 10 Apr 2017 14:05:21 -0700 Subject: Update to handle custom values in `Option`s --- src/main/scala/xyz/driver/core/swagger.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/xyz/driver/core/swagger.scala b/src/main/scala/xyz/driver/core/swagger.scala index fd519f9..09dae1e 100644 --- a/src/main/scala/xyz/driver/core/swagger.scala +++ b/src/main/scala/xyz/driver/core/swagger.scala @@ -63,9 +63,9 @@ object swagger { } orElse { `type` match { case rt: ReferenceType if isOption(javaType.getRawClass) && chain.hasNext => - rt.getContentType - val nextType = rt.getContentType - val nextResolved = chain.next().resolveProperty(nextType, context, annotations, chain) + 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 => -- cgit v1.2.3 From f92036251d778a6c7a0445a46c1fdda930e19cfc Mon Sep 17 00:00:00 2001 From: vlad Date: Tue, 11 Apr 2017 16:21:09 -0700 Subject: Review comments --- src/main/scala/xyz/driver/core/database/Dal.scala | 8 +++++++- src/main/scala/xyz/driver/core/swagger.scala | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) 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 index 09dae1e..3a6faff 100644 --- a/src/main/scala/xyz/driver/core/swagger.scala +++ b/src/main/scala/xyz/driver/core/swagger.scala @@ -20,8 +20,8 @@ object swagger { def stringProperty(pattern: Option[String] = None, example: Option[String] = None): Property = { make(new StringProperty()) { sp => sp.required(true) - example.map(sp.example) - pattern.map(sp.pattern) + example.foreach(sp.example) + pattern.foreach(sp.pattern) } } @@ -35,7 +35,7 @@ object swagger { def numericProperty(example: Option[AnyRef] = None): Property = { make(PrimitiveType.DECIMAL.createProperty()) { dp => dp.setRequired(true) - example.foreach(e => dp.setExample(e)) + example.foreach(dp.setExample) } } -- cgit v1.2.3