Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#184 unmarshaller shuffle #292

Open
wants to merge 19 commits into
base: master
from
Open
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -12,7 +12,7 @@ crossScalaVersions := Seq("2.11.12", "2.12.3")
scalaVersion in ThisBuild := crossScalaVersions.value.last

val akkaVersion = "10.0.14"
val catsVersion = "1.4.0"
val catsVersion = "1.6.0"
val catsEffectVersion = "1.0.0"
val circeVersion = "0.10.1"
val http4sVersion = "0.19.0"
@@ -40,6 +40,7 @@ val exampleCases: List[(java.io.File, String, Boolean, List[String])] = List(
(sampleResource("issues/issue143.yaml"), "issues.issue143", false, List.empty),
(sampleResource("issues/issue148.yaml"), "issues.issue148", false, List.empty),
(sampleResource("issues/issue164.yaml"), "issues.issue164", false, List.empty),
(sampleResource("issues/issue184.yaml"), "issues.issue184", false, List.empty),
(sampleResource("issues/issue215.yaml"), "issues.issue215", false, List.empty),
(sampleResource("issues/issue218.yaml"), "issues.issue218", false, List.empty),
(sampleResource("issues/issue222.yaml"), "issues.issue222", false, List.empty),
@@ -150,6 +151,7 @@ val testDependencies = Seq(

val excludedWarts = Set(Wart.DefaultArguments, Wart.Product, Wart.Serializable, Wart.Any)
val codegenSettings = Seq(
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.0"),
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.10"),
wartremoverWarnings in Compile ++= Warts.unsafe.filterNot(w => excludedWarts.exists(_.clazz == w.clazz)),
wartremoverWarnings in Test := List.empty,
@@ -193,7 +195,10 @@ lazy val codegen = (project in file("modules/codegen"))
"org.typelevel" %% "cats-free" % catsVersion,
"org.scala-lang.modules" %% "scala-java8-compat" % "0.9.0",
),
scalacOptions += "-language:higherKinds",
scalacOptions ++= List(
"-language:higherKinds",
"-Ywarn-unused-import",
),
bintrayRepository := {
if (isSnapshot.value) "snapshots"
else "releases"
@@ -307,9 +307,9 @@ object ProtocolGenerator {
): Free[F, ProtocolElems[L]] = {
import R._
for {
deferredTpe <- SwaggerUtil.modelMetaType(arr)
tpe <- extractArrayType(deferredTpe, concreteTypes)
ret <- typeAlias[L, F](clsName, tpe)
(deferredTpe, _) <- SwaggerUtil.modelMetaType(arr)
tpe <- extractArrayType(deferredTpe, concreteTypes)
ret <- typeAlias[L, F](clsName, tpe)
} yield ret
}

@@ -13,6 +13,7 @@ import com.twilio.guardrail.terms.framework.FrameworkTerms
import com.twilio.guardrail.extract.{ CustomTypeName, Default, VendorExtension }
import com.twilio.guardrail.extract.VendorExtension.VendorExtensible._
import com.twilio.guardrail.generators.ScalaParameter
import com.twilio.guardrail.generators.syntax.RichSchema
import com.twilio.guardrail.languages.{ LA, ScalaLanguage }
import com.twilio.guardrail.protocol.terms.Responses
import java.util.{ Map => JMap }
@@ -139,15 +140,17 @@ object SwaggerUtil {
} yield CustomTypeName(v, prefixes)

sealed class ModelMetaTypePartiallyApplied[L <: LA, F[_]](val dummy: Boolean = true) {
def apply[T <: Schema[_]](model: T)(implicit Sc: ScalaTerms[L, F], Sw: SwaggerTerms[L, F], F: FrameworkTerms[L, F]): Free[F, ResolvedType[L]] =
def apply[T <: Schema[_]](
model: T
)(implicit Sc: ScalaTerms[L, F], Sw: SwaggerTerms[L, F], F: FrameworkTerms[L, F]): Free[F, (ResolvedType[L], (String, Option[String]))] =
Sw.log.function("modelMetaType") {
import Sc._
import Sw._
log.debug(s"model:\n${log.schemaToString(model)}") >> (model match {
case ref: Schema[_] if Option(ref.get$ref).isDefined =>
for {
ref <- getSimpleRef(ref)
} yield Deferred[L](ref)
} yield (Deferred[L](ref), ("object", None))
case arr: ArraySchema =>
for {
items <- getItems(arr)
@@ -161,13 +164,14 @@ object SwaggerUtil {
case x: DeferredArray[L] => embedArray(x)
case x: DeferredMap[L] => embedArray(x)
}
} yield res
} yield (res, ("array", None))
case impl: Schema[_] =>
for {
tpeName <- getType(impl)
customTpeName <- customTypeName(impl)
tpe <- typeName[L, F](tpeName, Option(impl.getFormat()), customTpeName)
} yield Resolved[L](tpe, None, None)
fmt = Option(impl.getFormat())
tpe <- typeName[L, F](tpeName, fmt, customTpeName)
} yield (Resolved[L](tpe, None, None), (tpeName, fmt))
})
}
}
@@ -190,10 +194,9 @@ object SwaggerUtil {
resolvedType = SwaggerUtil.Resolved[L](x, parentTerm, None): SwaggerUtil.ResolvedType[L]
} yield (clsName, resolvedType)
case (clsName, definition) =>
SwaggerUtil
.modelMetaType[L, F](definition)
.value
.map(x => (clsName, x))
for {
(resolved, _) <- SwaggerUtil.modelMetaType[L, F](definition)
} yield (clsName, resolved)
}
result <- SwaggerUtil.ResolvedType.resolveReferences[L, F](entries)
} yield
@@ -293,7 +296,10 @@ object SwaggerUtil {
}
} yield res
case o: ObjectSchema =>
objectType(None).map(Resolved[L](_, None, None)) // TODO: o.getProperties
for {
_ <- log.debug(s"Not attempting to process properties from ${o.showNotNull}")
res <- objectType(None).map(Resolved[L](_, None, None)) // TODO: o.getProperties
} yield res

case ref: Schema[_] if Option(ref.get$ref).isDefined =>
getSimpleRef(ref).map(Deferred[L])
@@ -613,8 +619,7 @@ object SwaggerUtil {
def generateUrlAkkaPathExtractors(path: String, pathArgs: List[ScalaParameter[ScalaLanguage]]): Target[NonEmptyList[(Term, List[Term.Name])]] = {
import akkaExtractor._
for {
partsQS <- runParse(path, pathArgs)
(parts, (trailingSlash, queryParams)) = partsQS
(parts, (trailingSlash, queryParams)) <- runParse(path, pathArgs)
allPairs = parts
.foldLeft[NonEmptyList[(Term, List[Term.Name])]](NonEmptyList.one((q"pathEnd", List.empty)))({
case (NonEmptyList((q"pathEnd ", bindings), xs), (termName, b)) =>
@@ -643,8 +648,7 @@ object SwaggerUtil {
def generateUrlHttp4sPathExtractors(path: String, pathArgs: List[ScalaParameter[ScalaLanguage]]): Target[(Pat, Option[Pat])] = {
import http4sExtractor._
for {
partsQS <- runParse(path, pathArgs)
(parts, (trailingSlash, queryParams)) = partsQS
(parts, (trailingSlash, queryParams)) <- runParse(path, pathArgs)
(directive, bindings) = parts
.foldLeft[(Pat, List[Term.Name])]((p"${Term.Name("Root")}", List.empty))({
case ((acc, bindings), (termName, c)) =>
@@ -659,12 +663,11 @@ object SwaggerUtil {
def generateUrlEndpointsPathExtractors(path: String, pathArgs: List[ScalaParameter[ScalaLanguage]]): Target[(Term, Option[Term])] = {
import endpointsExtractor._
for {
partsQS <- pattern(pathArgs)
(parts, (trailingSlash, queryParams)) <- pattern(pathArgs)
.parse(path)
.done
.either
.fold(Target.raiseError(_), Target.pure(_))
(parts, (trailingSlash, queryParams)) = partsQS
(directive, bindings) = parts
.foldLeft[(Term, List[Term.Name])]((q"pathRoot", List.empty))({
case ((acc, bindings), (termName, c)) =>
@@ -68,19 +68,22 @@ object AkkaHttpGenerator {
val empty: IgnoredEntity = new IgnoredEntity {}
}
// Translate Json => HttpEntity
implicit final def jsonMarshaller(
implicit printer: Printer = Printer.noSpaces
): ToEntityMarshaller[${jsonType}] =
Marshaller.withFixedContentType(MediaTypes.`application/json`) { json =>
HttpEntity(MediaTypes.`application/json`, printer.pretty(json))
}
// Translate [A: Encoder] => HttpEntity
implicit final def jsonEntityMarshaller[A](
implicit J: ${jsonEncoderTypeclass}[A],
printer: Printer = Printer.noSpaces
): ToEntityMarshaller[A] =
jsonMarshaller(printer).compose(J.apply)
// Translate HttpEntity => Json (for `text/plain`)
final val stringyJsonEntityUnmarshaller: FromEntityUnmarshaller[${jsonType}] =
Unmarshaller.byteStringUnmarshaller
.forContentTypes(MediaTypes.`text/plain`)
@@ -91,6 +94,28 @@ object AkkaHttpGenerator {
Json.fromString(data.decodeString("utf-8"))
})
// Translate HttpEntity => Json (for `text/plain`, relying on the Decoder to reject incorrect types.
// This permits not having to manually construct ToStringMarshaller/FromStringUnmarshallers.
// This is definitely lazy, but lets us save a lot of scalar parsers as circe decoders are fairly common.)
final val sneakyJsonEntityUnmarshaller: FromEntityUnmarshaller[${jsonType}] =

This comment has been minimized.

Copy link
@kelnos

kelnos May 20, 2019

Member

sneaky! :D

Unmarshaller.byteStringUnmarshaller
.forContentTypes(MediaTypes.`text/plain`)
.flatMapWithInput { (httpEntity, byteString) =>
if (byteString.isEmpty) {
FastFuture.failed(Unmarshaller.NoContentException)
} else {
val parseResult = Unmarshaller.bestUnmarshallingCharsetFor(httpEntity) match {
case HttpCharsets.`UTF-8` => jawn.parse(byteString.utf8String)
case otherCharset => jawn.parse(byteString.decodeString(otherCharset.nioCharset.name))
}
parseResult.fold(FastFuture.failed, FastFuture.successful)
}
}
final val stringyJsonUnmarshaller: FromStringUnmarshaller[${jsonType}] =
Unmarshaller.strict(value => Json.fromString(value))
// Translate HttpEntity => Json (for `application/json`)
implicit final val structuredJsonEntityUnmarshaller: FromEntityUnmarshaller[${jsonType}] =
Unmarshaller.byteStringUnmarshaller
.forContentTypes(MediaTypes.`application/json`)
@@ -106,19 +131,28 @@ object AkkaHttpGenerator {
}
}
// Translate HttpEntity => [A: Decoder] (for `application/json` or `text/plain`)
implicit def jsonEntityUnmarshaller[A](implicit J: ${jsonDecoderTypeclass}[A]): FromEntityUnmarshaller[A] = {
Unmarshaller.firstOf(structuredJsonEntityUnmarshaller, stringyJsonEntityUnmarshaller)
.flatMap(_ => _ => json => J.decodeJson(json).fold(FastFuture.failed, FastFuture.successful))
}
def unmarshallJson[A](implicit J: ${jsonDecoderTypeclass}[A]): Unmarshaller[${jsonType}, A] =
Unmarshaller { _ => value =>
J.decodeJson(value)
.fold(FastFuture.failed, FastFuture.successful)
}
// Translate String => Json (by either successfully parsing or string literalizing (Dangerous!))
final val jsonStringUnmarshaller: FromStringUnmarshaller[${jsonType}] = Unmarshaller.strict {
case "" =>
throw Unmarshaller.NoContentException
case data =>
jawn.parse(data).getOrElse(Json.fromString(data))
}
implicit def jsonDecoderUnmarshaller[A](implicit J: ${jsonDecoderTypeclass}[A]): FromStringUnmarshaller[A] = {
// Translate String => [A: Decoder]
def jsonDecoderUnmarshaller[A](implicit J: ${jsonDecoderTypeclass}[A]): FromStringUnmarshaller[A] = {
jsonStringUnmarshaller
.flatMap(_ => _ => json => J.decodeJson(json).fold(FastFuture.failed, FastFuture.successful))
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.