Skip to content

Commit

Permalink
Generate nested definition classes into companion objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Strnad committed Aug 10, 2019
1 parent 6b746c1 commit 0d27e3c
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package com.twilio.guardrail

import _root_.io.swagger.v3.oas.models._
import _root_.io.swagger.v3.oas.models.media._
import cats.data.NonEmptyList
import cats.free.Free
import cats.implicits._
import com.twilio.guardrail.SwaggerUtil.Resolved
import com.twilio.guardrail.extract.VendorExtension.VendorExtensible._
import com.twilio.guardrail.generators.RawParameterType
import com.twilio.guardrail.generators.syntax._
import com.twilio.guardrail.languages.LA
import com.twilio.guardrail.protocol.terms.protocol._
import com.twilio.guardrail.terms.framework.FrameworkTerms
Expand Down Expand Up @@ -264,33 +267,95 @@ object ProtocolGenerator {
} yield supper
}

private[this] def fromModel[L <: LA, F[_]](clsName: String, model: Schema[_], parents: List[SuperClass[L]], concreteTypes: List[PropMeta[L]])(
private[this] def fromModel[L <: LA, F[_]](clsName: NonEmptyList[String],
model: Schema[_],
parents: List[SuperClass[L]],
concreteTypes: List[PropMeta[L]],
definitions: List[(String, Schema[_])])(
implicit M: ModelProtocolTerms[L, F],
F: FrameworkTerms[L, F],
P: PolyProtocolTerms[L, F],
Sc: ScalaTerms[L, F],
Sw: SwaggerTerms[L, F]
): Free[F, Either[String, ProtocolElems[L]]] = {
): Free[F, Either[String, ClassDefinition[L]]] = {
import M._
import Sc._

for {
props <- extractProperties(model)
needCamelSnakeConversion = props.forall { case (k, _) => couldBeSnakeCase(k) }
(params, nestedClassDefinitions) <- prepareProperties(clsName, model, concreteTypes, definitions)
defn <- renderDTOClass(clsName.last, params, parents)
encoder <- encodeModel(clsName.last, needCamelSnakeConversion, params, parents)
decoder <- decodeModel(clsName.last, needCamelSnakeConversion, params, parents)
tpe <- parseTypeName(clsName.last)
staticDefns <- renderDTOStaticDefns(clsName.last, List.empty, encoder, decoder)
result <- if (parents.isEmpty && props.isEmpty) Free.pure[F, Either[String, ClassDefinition[L]]](Left("Entity isn't model"))
else {
val nestedClasses = nestedClassDefinitions.flatTraverse { classDefinition =>
for {
widenClass <- widenClassDefinition(classDefinition.cls)
companionTerm <- pureTermName(classDefinition.name)
companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions)
widenCompanion <- widenObjectDefinition(companionDefinition)
} yield List(widenClass, widenCompanion)
}
nestedClasses.map { v =>
val finalStaticDefns = staticDefns.copy(definitions = staticDefns.definitions ++ v)
tpe.toRight("Empty entity name").map(ClassDefinition[L](clsName.last, _, defn, finalStaticDefns, parents))
}
}
} yield result
}

private def prepareProperties[L <: LA, F[_]](
clsName: NonEmptyList[String],
model: Schema[_],
concreteTypes: List[PropMeta[L]],
definitions: List[(String, Schema[_])]
)(implicit M: ModelProtocolTerms[L, F],
F: FrameworkTerms[L, F],
P: PolyProtocolTerms[L, F],
Sc: ScalaTerms[L, F],
Sw: SwaggerTerms[L, F]): Free[F, (List[ProtocolParameter[L]], List[ClassDefinition[L]])] = {
import F._
import M._
import Sc._
def processProperty(name: String, schema: Schema[_]): Free[F, Option[Either[String, ClassDefinition[L]]]] = {
val nestedClassName = clsName.append(name.toCamelCase.capitalize)
schema match {
case _: ObjectSchema =>
fromModel(nestedClassName, schema, List.empty, concreteTypes, definitions).map(Some(_))
case o: ComposedSchema =>
for {
parents <- extractParents(o, definitions, concreteTypes)
maybeClassDefinition <- fromModel(nestedClassName, schema, parents, concreteTypes, definitions)
} yield Some(maybeClassDefinition)
case a: ArraySchema =>
processProperty(name, a.getItems)
case _ =>
Free.pure(None)
}
}
for {
props <- extractProperties(model)
requiredFields = getRequiredFieldsRec(model)
needCamelSnakeConversion = props.forall { case (k, _) => couldBeSnakeCase(k) }
params <- props.traverse({
case (name, prop) =>
val isRequired = requiredFields.contains(name)
SwaggerUtil.propMeta[L, F](prop).flatMap(transformProperty(clsName, needCamelSnakeConversion, concreteTypes)(name, prop, _, isRequired))
})
defn <- renderDTOClass(clsName, params, parents)
deps = params.flatMap(_.dep)
encoder <- encodeModel(clsName, needCamelSnakeConversion, params, parents)
decoder <- decodeModel(clsName, needCamelSnakeConversion, params, parents)
staticDefns <- renderDTOStaticDefns(clsName, List.empty, encoder, decoder)
tpe <- parseTypeName(clsName)
} yield
if (parents.isEmpty && props.isEmpty) Left("Entity isn't model")
else tpe.toRight("Empty entity name").map(ClassDefinition[L](clsName, _, defn, staticDefns, parents))
paramsAndNestedClasses <- props.traverse[Free[F, ?], (ProtocolParameter[L], Option[ClassDefinition[L]])] {
case (name, schema) =>
val typeName = clsName.append(name.toCamelCase.capitalize)
for {
tpe <- selectType(typeName)
maybeClassDefinition <- processProperty(name, schema)
resolvedType <- maybeClassDefinition.fold(SwaggerUtil.propMetaWithName(tpe, schema)) {
case Left(_) => objectType(None).map(Resolved(_, None, None, None, None))
case Right(_) => SwaggerUtil.propMetaWithName(tpe, schema)
}
parameter <- transformProperty(clsName.last, needCamelSnakeConversion, concreteTypes)(name, schema, resolvedType, requiredFields.contains(name))
} yield (parameter, maybeClassDefinition.flatMap(_.toOption))
}
(params, nestedClassDefinitions) = paramsAndNestedClasses.unzip
} yield params -> nestedClassDefinitions.flatten
}

def modelTypeAlias[L <: LA, F[_]](clsName: String, abstractModel: Schema[_])(
Expand Down Expand Up @@ -436,14 +501,14 @@ object ProtocolGenerator {
case m: StringSchema =>
for {
enum <- fromEnum(clsName, m)
model <- fromModel(clsName, m, List.empty, concreteTypes)
model <- fromModel(NonEmptyList.of(clsName), m, List.empty, concreteTypes, definitions)
alias <- modelTypeAlias(clsName, m)
} yield enum.orElse(model).getOrElse(alias)

case comp: ComposedSchema =>
for {
parents <- extractParents(comp, definitions, concreteTypes)
model <- fromModel(clsName, comp, parents, concreteTypes)
model <- fromModel(NonEmptyList.of(clsName), comp, parents, concreteTypes, definitions)
alias <- modelTypeAlias(clsName, comp)
} yield model.getOrElse(alias)

Expand All @@ -453,7 +518,7 @@ object ProtocolGenerator {
case m: ObjectSchema =>
for {
enum <- fromEnum(clsName, m)
model <- fromModel(clsName, m, List.empty, concreteTypes)
model <- fromModel(NonEmptyList.of(clsName), m, List.empty, concreteTypes, definitions)
alias <- modelTypeAlias(clsName, m)
} yield enum.orElse(model).getOrElse(alias)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,20 +277,56 @@ object SwaggerUtil {
} yield result
}

def propMeta[L <: LA, F[_]](property: Schema[_])(implicit Sc: ScalaTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): Free[F, ResolvedType[L]] =
def propMeta[L <: LA, F[_]](property: Schema[_])(
implicit Sc: ScalaTerms[L, F],
Sw: SwaggerTerms[L, F],
Fw: FrameworkTerms[L, F]
): Free[F, ResolvedType[L]] = {
import Fw._
import Sw._
propMetaImpl(property) {
case o: ObjectSchema =>
for {
_ <- log.debug(
s"Not attempting to process properties from ${o.showNotNull}"
)
tpe <- objectType(None) // TODO: o.getProperties
} yield Resolved[L](tpe, None, None, None, None)
}
}

def propMetaWithName[L <: LA, F[_]](tpe: L#Type, property: Schema[_])(
implicit Sc: ScalaTerms[L, F],
Sw: SwaggerTerms[L, F],
Fw: FrameworkTerms[L, F]
): Free[F, ResolvedType[L]] = {
import Sc._
val action: Free[F, ResolvedType[L]] = Free.pure(Resolved[L](tpe, None, None, None, None))
propMetaImpl(property) {
case _: ObjectSchema =>
action
case _: ComposedSchema =>
action
}
}

private def propMetaImpl[L <: LA, F[_]](property: Schema[_])(
strategy: PartialFunction[Schema[_], Free[F, ResolvedType[L]]]
)(implicit Sc: ScalaTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): Free[F, ResolvedType[L]] =
Sw.log.function("propMeta") {
import Fw._
import Sc._
import Sw._

log.debug(s"property:\n${log.schemaToString(property)}") >> (property match {
val baseStrategy: PartialFunction[Schema[_], Free[F, ResolvedType[L]]] = {
case p: ArraySchema =>
for {
items <- getItems(p)
rec <- propMeta[L, F](items)
rec <- propMetaImpl[L, F](items)(strategy)
res <- rec match {
case Resolved(inner, dep, default, _, _) =>
(liftVectorType(inner), default.traverse(liftVectorTerm)).mapN(Resolved[L](_, dep, _, None, None): ResolvedType[L])
(liftVectorType(inner), default.traverse(liftVectorTerm))
.mapN(Resolved[L](_, dep, _, None, None): ResolvedType[L])
case x: DeferredMap[L] => embedArray(x)
case x: DeferredArray[L] => embedArray(x)
case x: Deferred[L] => embedArray(x)
Expand All @@ -299,22 +335,21 @@ object SwaggerUtil {
case m: MapSchema =>
for {
rec <- Option(m.getAdditionalProperties)
.fold[Free[F, ResolvedType[L]]](objectType(None).map(Resolved[L](_, None, None, None, None)))({
case b: java.lang.Boolean => objectType(None).map(Resolved[L](_, None, None, None, None))
case s: Schema[_] => propMeta[L, F](s)
.fold[Free[F, ResolvedType[L]]](
objectType(None).map(Resolved[L](_, None, None, None, None))
)({
case b: java.lang.Boolean =>
objectType(None).map(Resolved[L](_, None, None, None, None))
case s: Schema[_] => propMetaImpl[L, F](s)(strategy)
})
res <- rec match {
case Resolved(inner, dep, _, _, _) => liftMapType(inner).map(Resolved[L](_, dep, None, None, None))
case x: DeferredMap[L] => embedMap(x)
case x: DeferredArray[L] => embedMap(x)
case x: Deferred[L] => embedMap(x)
case Resolved(inner, dep, _, _, _) =>
liftMapType(inner).map(Resolved[L](_, dep, None, None, None))
case x: DeferredMap[L] => embedMap(x)
case x: DeferredArray[L] => embedMap(x)
case x: Deferred[L] => embedMap(x)
}
} yield res
case o: ObjectSchema =>
for {
_ <- log.debug(s"Not attempting to process properties from ${o.showNotNull}")
tpe <- objectType(None) // TODO: o.getProperties
} yield Resolved[L](tpe, None, None, None, None)

case ref: Schema[_] if Option(ref.get$ref).isDefined =>
getSimpleRef(ref).map(Deferred[L])
Expand All @@ -324,17 +359,21 @@ object SwaggerUtil {
val rawFormat = Option.empty[String]
for {
customTpeName <- customTypeName(b)
res <- (typeName[L, F](rawType, None, customTpeName), Default(b).extract[Boolean].traverse(litBoolean(_)))
.mapN(Resolved[L](_, None, _, Some(rawType), rawFormat))
res <- (
typeName[L, F](rawType, None, customTpeName),
Default(b).extract[Boolean].traverse(litBoolean(_))
).mapN(Resolved[L](_, None, _, Some(rawType), rawFormat))
} yield res

case s: StringSchema =>
val rawType = "string"
val rawFormat = Option(s.getFormat())
for {
customTpeName <- customTypeName(s)
res <- (typeName[L, F](rawType, rawFormat, customTpeName), Default(s).extract[String].traverse(litString(_)))
.mapN(Resolved[L](_, None, _, Option(rawType), rawFormat))
res <- (
typeName[L, F](rawType, rawFormat, customTpeName),
Default(s).extract[String].traverse(litString(_))
).mapN(Resolved[L](_, None, _, Option(rawType), rawFormat))
} yield res

case d: DateSchema =>
Expand Down Expand Up @@ -392,12 +431,17 @@ object SwaggerUtil {
customTpeName <- customTypeName(u)
tpe <- typeName[L, F]("string", rawFormat, customTpeName)
} yield Resolved[L](tpe, None, None, Option(rawType), rawFormat)
}

val strategies = baseStrategy.orElse(strategy).orElse[Schema[_], Free[F, ResolvedType[L]]] {
case x =>
for {
tpe <- fallbackPropertyTypeHandler(x)
} yield Resolved[L](tpe, None, None, None, None) // This may need to be rawType=string?
})
}
log.debug(s"property:\n${log.schemaToString(property)}") >> strategies(
property
)
}

def extractSecuritySchemes[L <: LA, F[_]](swagger: OpenAPI, prefixes: List[String])(implicit Sw: SwaggerTerms[L, F],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ object JavaGenerator {
case ExtractTermName(term) =>
Target.pure(term.asString)

case SelectType(typeNames) =>
safeParseType(typeNames.toList.mkString("."))

case AlterMethodParameterName(param, name) =>
safeParseSimpleName(name.asString.escapeIdentifier).map(
new Parameter(
Expand Down Expand Up @@ -209,8 +212,10 @@ object JavaGenerator {
case ArrayType(format) => safeParseClassOrInterfaceType("java.util.List").map(_.setTypeArguments(new NodeList[Type](STRING_TYPE)))
case FallbackType(tpe, format) => safeParseType(tpe)

case WidenTypeName(tpe) => safeParseType(tpe.asString)
case WidenTermSelect(value) => Target.pure(value)
case WidenTypeName(tpe) => safeParseType(tpe.asString)
case WidenTermSelect(value) => Target.pure(value)
case WidenClassDefinition(value) => Target.pure(value)
case WidenObjectDefinition(value) => Target.pure(value)

case RenderImplicits(pkgPath, pkgName, frameworkImports, jsonImports, customImports) =>
Target.pure(None)
Expand Down Expand Up @@ -322,6 +327,9 @@ object JavaGenerator {
handlerTree <- writeServerTree(pkgPath, pkg, pkgDecl, allImports, handlerDefinition)
serverTrees <- serverDefinitions.traverse(writeServerTree(pkgPath, pkg, pkgDecl, allImports, _))
} yield handlerTree +: serverTrees

case WrapToObject(_, _, _) =>
Target.raiseError("Currently not supported for Java")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import com.twilio.guardrail.generators.syntax.Scala._
import com.twilio.guardrail.languages.ScalaLanguage
import com.twilio.guardrail.terms._
import java.nio.charset.StandardCharsets

import cats.data.NonEmptyList

import scala.meta._

object ScalaGenerator {
Expand Down Expand Up @@ -104,6 +107,19 @@ object ScalaGenerator {
val Term.Name(name) = term
Target.pure(name)

case SelectType(typeNames) =>
val tpe = Type.Name(typeNames.last)
val names = typeNames.init.map(Term.Name.apply _)

val result = names match {
case Nil => tpe
case x :: xs =>
val term = xs.foldLeft[Term.Ref](x)(Term.Select.apply _)
Type.Select(term, tpe)
}

Target.pure(result)

case AlterMethodParameterName(param, name) =>
Target.pure(param.copy(name = name))

Expand All @@ -121,8 +137,10 @@ object ScalaGenerator {
case ArrayType(format) => Target.pure(t"Iterable[String]")
case FallbackType(tpe, format) => Target.pure(Type.Name(tpe))

case WidenTypeName(tpe) => Target.pure(tpe)
case WidenTermSelect(value) => Target.pure(value)
case WidenTypeName(tpe) => Target.pure(tpe)
case WidenTermSelect(value) => Target.pure(value)
case WidenClassDefinition(value) => Target.pure(value)
case WidenObjectDefinition(value) => Target.pure(value)

case RenderImplicits(pkgPath, pkgName, frameworkImports, jsonImports, customImports) =>
val pkg: Term.Ref =
Expand Down Expand Up @@ -381,6 +399,13 @@ object ScalaGenerator {
)
)
)
case WrapToObject(name, imports, definitions) =>
Target.pure(q"""
object $name {
..$imports
..$definitions
}
""")
}
}
}
Loading

0 comments on commit 0d27e3c

Please sign in to comment.