Skip to content

Commit

Permalink
Decouple Resolved from LazyResolved type
Browse files Browse the repository at this point in the history
  • Loading branch information
blast-hardcheese committed Nov 15, 2023
1 parent 239c3c6 commit 920b5f0
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 165 deletions.
112 changes: 57 additions & 55 deletions modules/core/src/main/scala/dev/guardrail/core/ResolvedType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,22 @@ case class LiteralRawType(rawType: Option[String], rawFormat: Option[String]) ex
case class VectorRawType(items: ReifiedRawType) extends ReifiedRawType
case class MapRawType(items: ReifiedRawType) extends ReifiedRawType

sealed trait ResolvedType[L <: LA]
case class Resolved[L <: LA](tpe: L#Type, classDep: Option[L#TermName], defaultValue: Option[L#Term], rawType: ReifiedRawType) extends ResolvedType[L]
sealed trait LazyResolvedType[L <: LA] extends ResolvedType[L] { def value: String }
case class Resolved[L <: LA](tpe: L#Type, classDep: Option[L#TermName], defaultValue: Option[L#Term], rawType: ReifiedRawType)
sealed trait LazyResolvedType[L <: LA] extends { def value: String }
case class Deferred[L <: LA](value: String) extends LazyResolvedType[L]
case class DeferredArray[L <: LA](value: String, containerTpe: Option[L#Type]) extends LazyResolvedType[L]
case class DeferredMap[L <: LA](value: String, containerTpe: Option[L#Type]) extends LazyResolvedType[L]

object ResolvedType {
def resolveReferences[L <: LA, F[_]: Monad](
values: List[(String, ResolvedType[L])]
values: List[(String, Either[LazyResolvedType[L], Resolved[L]])]
)(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: OpenAPITerms[L, F]): F[List[(String, Resolved[L])]] =
Sw.log.function("resolveReferences") {
import Cl._
import Sw._
val (lazyTypes, resolvedTypes) = Foldable[List].partitionEither(values) {
case (clsName, x: Resolved[L]) => Right((clsName, x))
case (clsName, x: LazyResolvedType[L]) => Left((clsName, x))
case (clsName, Right(x: Resolved[L])) => Right((clsName, x))
case (clsName, Left(x: LazyResolvedType[L])) => Left((clsName, x))
}

def lookupTypeName(tpeName: String, resolvedTypes: List[(String, Resolved[L])])(
Expand Down Expand Up @@ -77,59 +76,62 @@ object ResolvedType {
}

def resolve[L <: LA, F[_]: Monad](
value: ResolvedType[L],
value: Either[LazyResolvedType[L], Resolved[L]],
protocolElems: List[StrictProtocolElems[L]]
)(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: OpenAPITerms[L, F]): F[Resolved[L]] = {
import Sc._
import Cl._
import Sw._
log.debug(s"value: ${value} in ${protocolElems.length} protocol elements") >> (value match {
case x @ Resolved(_, _, _, _) => x.pure[F]
case Deferred(name) =>
for {
formattedName <- formatTypeName(name)
resolved <- resolveType(formattedName, protocolElems)
.flatMap {
case RandomType(name, tpe) =>
Resolved[L](tpe, None, None, ReifiedRawType.unsafeEmpty).pure[F]
case ClassDefinition(name, _, fullType, cls, _, _) =>
Resolved[L](fullType, None, None, ReifiedRawType.unsafeEmpty).pure[F]
case EnumDefinition(name, _, fullType, _, cls, _) =>
Resolved[L](fullType, None, None, ReifiedRawType.of(Some("string"), None)).pure[F]
case ADT(_, _, fullType, _, _) =>
Resolved[L](fullType, None, None, ReifiedRawType.unsafeEmpty).pure[F]
}
} yield resolved
case DeferredArray(name, containerTpe) =>
for {
formattedName <- formatTypeName(name)
resolved <- resolveType(formattedName, protocolElems)
.flatMap {
case RandomType(name, tpe) =>
liftVectorType(tpe, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
case ClassDefinition(name, _, fullType, cls, _, _) =>
liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
case EnumDefinition(name, _, fullType, _, cls, _) =>
liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
case ADT(_, _, fullType, _, _) =>
liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
}
} yield resolved
case DeferredMap(name, containerTpe) =>
for {
formattedName <- formatTypeName(name)
resolved <- resolveType(formattedName, protocolElems)
.flatMap {
case RandomType(name, tpe) =>
liftMapType(tpe, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
case ClassDefinition(_, _, fullType, _, _, _) =>
liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
case EnumDefinition(_, _, fullType, _, _, _) =>
liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
case ADT(_, _, fullType, _, _) =>
liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
}
} yield resolved
})
for {
_ <- log.debug(s"value: ${value} in ${protocolElems.length} protocol elements")
res <- value match {
case Right(x @ Resolved(_, _, _, _)) => x.pure[F]
case Left(Deferred(name)) =>
for {
formattedName <- formatTypeName(name)
resolved <- resolveType(formattedName, protocolElems)
.flatMap {
case RandomType(name, tpe) =>
Resolved[L](tpe, None, None, ReifiedRawType.unsafeEmpty).pure[F]
case ClassDefinition(name, _, fullType, cls, _, _) =>
Resolved[L](fullType, None, None, ReifiedRawType.unsafeEmpty).pure[F]
case EnumDefinition(name, _, fullType, _, cls, _) =>
Resolved[L](fullType, None, None, ReifiedRawType.of(Some("string"), None)).pure[F]
case ADT(_, _, fullType, _, _) =>
Resolved[L](fullType, None, None, ReifiedRawType.unsafeEmpty).pure[F]
}
} yield resolved
case Left(DeferredArray(name, containerTpe)) =>
for {
formattedName <- formatTypeName(name)
resolved <- resolveType(formattedName, protocolElems)
.flatMap {
case RandomType(name, tpe) =>
liftVectorType(tpe, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
case ClassDefinition(name, _, fullType, cls, _, _) =>
liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
case EnumDefinition(name, _, fullType, _, cls, _) =>
liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
case ADT(_, _, fullType, _, _) =>
liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty)))
}
} yield resolved
case Left(DeferredMap(name, containerTpe)) =>
for {
formattedName <- formatTypeName(name)
resolved <- resolveType(formattedName, protocolElems)
.flatMap {
case RandomType(name, tpe) =>
liftMapType(tpe, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
case ClassDefinition(_, _, fullType, _, _, _) =>
liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
case EnumDefinition(_, _, fullType, _, _, _) =>
liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
case ADT(_, _, fullType, _, _) =>
liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty)))
}
} yield resolved
}
} yield res
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,43 @@ object ModelResolver {
Cl: CollectionsLibTerms[L, F],
Sw: OpenAPITerms[L, F],
Fw: FrameworkTerms[L, F]
): F[core.ResolvedType[L]] = propMetaImpl[L, F](property, components)(Left(_))
): F[Either[core.LazyResolvedType[L], core.Resolved[L]]] = propMetaImpl[L, F](property, components)(Left(_))

def propMetaWithName[L <: LA, F[_]: Monad](tpe: L#Type, property: Tracker[Schema[_]], components: Tracker[Option[Components]])(implicit
Sc: LanguageTerms[L, F],
Cl: CollectionsLibTerms[L, F],
Sw: OpenAPITerms[L, F],
Fw: FrameworkTerms[L, F]
): F[core.ResolvedType[L]] =
): F[Either[core.LazyResolvedType[L], core.Resolved[L]]] =
propMetaImpl(property, components)(
_.refine[core.ResolvedType[L]] { case ObjectExtractor(schema) if Option(schema.getProperties).exists(p => !p.isEmpty) => schema }(_ =>
_.refine[core.Resolved[L]] { case ObjectExtractor(schema) if Option(schema.getProperties).exists(p => !p.isEmpty) => schema }(_ =>
core.Resolved[L](tpe, None, None, ReifiedRawType.unsafeEmpty)
).orRefine { case c: ComposedSchema => c }(_ => core.Resolved[L](tpe, None, None, ReifiedRawType.unsafeEmpty))
.orRefine { case schema: StringSchema if Option(schema.getEnum).map(_.asScala).exists(_.nonEmpty) => schema }(_ =>
core.Resolved[L](tpe, None, None, ReifiedRawType.unsafeEmpty)
)
.map(_.pure[F])
.map(r => r.pure[Either[core.LazyResolvedType[L], *]].pure[F])
)

def modelMetaType[L <: LA, F[_]: Monad](
model: Tracker[Schema[_]],
components: Tracker[Option[Components]]
)(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: OpenAPITerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] =
)(implicit
Sc: LanguageTerms[L, F],
Cl: CollectionsLibTerms[L, F],
Sw: OpenAPITerms[L, F],
Fw: FrameworkTerms[L, F]
): F[Either[core.LazyResolvedType[L], core.Resolved[L]]] =
propMetaImpl[L, F](model, components)(Left(_))

private def propMetaImpl[L <: LA, F[_]: Monad](property: Tracker[Schema[_]], components: Tracker[Option[Components]])(
strategy: Tracker[Schema[_]] => Either[Tracker[Schema[_]], F[core.ResolvedType[L]]]
)(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: OpenAPITerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] =
strategy: Tracker[Schema[_]] => Either[Tracker[Schema[_]], F[Either[core.LazyResolvedType[L], core.Resolved[L]]]]
)(implicit
Sc: LanguageTerms[L, F],
Cl: CollectionsLibTerms[L, F],
Sw: OpenAPITerms[L, F],
Fw: FrameworkTerms[L, F]
): F[Either[core.LazyResolvedType[L], core.Resolved[L]]] =
Sw.log.function("propMeta") {
import Fw._
import Sc._
Expand All @@ -74,14 +84,16 @@ object ModelResolver {
for {
_ <- log.debug(s"property:\n${log.schemaToString(property.unwrapTracker)} (${property.unwrapTracker.getExtensions()}, ${property.showHistory})")

res <- strategy(property)
.orRefine { case ref: Schema[_] if Option(ref.get$ref).isDefined => ref }(ref => getSimpleRef(ref.map(Option.apply _)).map(core.Deferred[L]))
scalarResolved <- strategy(property)
.orRefine { case ref: Schema[_] if Option(ref.get$ref).isDefined => ref }(ref =>
getSimpleRef(ref.map(Option.apply _)).map(t => Left(core.Deferred[L](t)))
)
.orRefine { case ObjectExtractor(o) => o }(o =>
for {
prefixes <- vendorPrefixes()
customTpe <- CustomTypeName(o, prefixes).flatTraverse(x => liftCustomType[L, F](Tracker.cloneHistory(o, x)))
fallback <- objectType(None)
} yield core.Resolved[L](customTpe.getOrElse(fallback), None, None, ReifiedRawType.unsafeEmpty)
} yield Right(core.Resolved[L](customTpe.getOrElse(fallback), None, None, ReifiedRawType.unsafeEmpty))
)
.orRefine { case arr: ArraySchema => arr }(arr =>
for {
Expand All @@ -96,12 +108,14 @@ object ModelResolver {
prefixes <- vendorPrefixes()
arrayType <- CustomArrayTypeName(arr, prefixes).flatTraverse(x => parseType(Tracker.cloneHistory(arr, x)))
res <- meta match {
case core.Resolved(inner, dep, default, _) =>
case Right(core.Resolved(inner, dep, default, _)) =>
(liftVectorType(inner, arrayType), default.traverse(liftVectorTerm))
.mapN(core.Resolved[L](_, dep, _, ReifiedRawType.ofVector(ReifiedRawType.of(itemsRawType.unwrapTracker, itemsRawFormat.unwrapTracker))))
case x: core.Deferred[L] => embedArray(x, arrayType)
case x: core.DeferredArray[L] => embedArray(x, arrayType)
case x: core.DeferredMap[L] => embedArray(x, arrayType)
.mapN((t, d) =>
Right(core.Resolved[L](t, dep, d, ReifiedRawType.ofVector(ReifiedRawType.of(itemsRawType.unwrapTracker, itemsRawFormat.unwrapTracker))))
)
case Left(x: core.Deferred[L]) => embedArray(x, arrayType).map(Left(_))
case Left(x: core.DeferredArray[L]) => embedArray(x, arrayType).map(Left(_))
case Left(x: core.DeferredMap[L]) => embedArray(x, arrayType).map(Left(_))
}
} yield res
)
Expand All @@ -112,27 +126,27 @@ object ModelResolver {
rec <- map
.downField("additionalProperties", _.getAdditionalProperties())
.map(_.getOrElse(false))
.refine[F[core.ResolvedType[L]]] { case b: java.lang.Boolean => b }(_ =>
objectType(None).map(core.Resolved[L](_, None, None, ReifiedRawType.ofMap(ReifiedRawType.unsafeEmpty)))
.refine[F[Either[core.LazyResolvedType[L], core.Resolved[L]]]] { case b: java.lang.Boolean => b }(_ =>
objectType(None).map(t => Right(core.Resolved[L](t, None, None, ReifiedRawType.ofMap(ReifiedRawType.unsafeEmpty))))
)
.orRefine { case s: Schema[_] => s }(s => propMetaImpl[L, F](s, components)(strategy))
.orRefineFallback { s =>
log.debug(s"Unknown structure cannot be reflected: ${s.unwrapTracker} (${s.showHistory})") >> objectType(None)
.map(core.Resolved[L](_, None, None, ReifiedRawType.ofMap(ReifiedRawType.of(rawType.unwrapTracker, rawFormat.unwrapTracker))))
.map(t => Right(core.Resolved[L](t, None, None, ReifiedRawType.ofMap(ReifiedRawType.of(rawType.unwrapTracker, rawFormat.unwrapTracker)))))
}
prefixes <- vendorPrefixes()
mapType <- CustomMapTypeName(map, prefixes).flatTraverse(x => parseType(Tracker.cloneHistory(map, x)))
res <- rec match {
case core.Resolved(inner, dep, _, rawType) => liftMapType(inner, mapType).map(core.Resolved[L](_, dep, None, ReifiedRawType.ofMap(rawType)))
case x: core.DeferredMap[L] => embedMap(x, mapType)
case x: core.DeferredArray[L] => embedMap(x, mapType)
case x: core.Deferred[L] => embedMap(x, mapType)
case Right(core.Resolved(inner, dep, _, rawType)) =>
liftMapType(inner, mapType).map(t => Right(core.Resolved[L](t, dep, None, ReifiedRawType.ofMap(rawType))))
case Left(x: core.DeferredMap[L]) => embedMap(x, mapType).map(Left(_))
case Left(x: core.DeferredArray[L]) => embedMap(x, mapType).map(Left(_))
case Left(x: core.Deferred[L]) => embedMap(x, mapType).map(Left(_))
}
} yield res
}
.pure[F]
scalarResolved <- resolveScalarTypes[L, F](res, components)
withDefaults <- enrichWithDefault[L, F](property).apply(scalarResolved)
.orRefineFallback(r => resolveScalarTypes[L, F](r, components).map(Right(_): Either[core.LazyResolvedType[L], core.Resolved[L]]))
withDefaults <- enrichWithDefault[L, F](property, scalarResolved)
} yield withDefaults
}

Expand Down Expand Up @@ -252,23 +266,25 @@ object ModelResolver {
} yield (result, reifiedRawType)
}

private def enrichWithDefault[L <: LA, F[_]: Monad](schema: Tracker[Schema[_]])(implicit
private def enrichWithDefault[L <: LA, F[_]: Monad](schema: Tracker[Schema[_]], resolved: Either[core.LazyResolvedType[L], core.Resolved[L]])(implicit
Sc: LanguageTerms[L, F],
Cl: CollectionsLibTerms[L, F],
Sw: OpenAPITerms[L, F],
Fw: FrameworkTerms[L, F]
): core.ResolvedType[L] => F[core.ResolvedType[L]] = { resolved =>
): F[Either[core.LazyResolvedType[L], core.Resolved[L]]] = {
import Sc._
def buildResolve[B: Extractable, A <: Schema[_]: Default.GetDefault](transformLit: B => F[L#Term]): Tracker[A] => F[core.ResolvedType[L]] = { a =>
def buildResolve[B: Extractable, A <: Schema[_]: Default.GetDefault](
transformLit: B => F[L#Term]
): Tracker[A] => F[Either[core.LazyResolvedType[L], core.Resolved[L]]] = { a =>
for {
default <- Default(a).extract[B].traverse(transformLit(_))
} yield resolved match {
case x: core.Resolved[L] => x.copy(defaultValue = default)
case other => other
case Right(x: core.Resolved[L]) => Right(x.copy(defaultValue = default))
case other => other
}
}
schema
.refine[F[core.ResolvedType[L]]] { case b: BooleanSchema => b }(buildResolve(litBoolean))
.refine[F[Either[core.LazyResolvedType[L], core.Resolved[L]]]] { case b: BooleanSchema => b }(buildResolve(litBoolean))
.orRefine { case s: StringSchema => s }(buildResolve(litString))
.orRefine { case s: IntegerSchema => s }(buildResolve(litInt))
.orRefineFallback(_ => resolved.pure[F])
Expand All @@ -283,20 +299,20 @@ object ModelResolver {
}

private def resolveScalarTypes[L <: LA, F[_]: Monad](
partial: Either[Tracker[Schema[_]], F[core.ResolvedType[L]]],
schema: Tracker[Schema[_]],
components: Tracker[Option[Components]]
)(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: OpenAPITerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] = {
)(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: OpenAPITerms[L, F], Fw: FrameworkTerms[L, F]): F[core.Resolved[L]] = {
import Cl._
import Sw._
def buildResolveNoDefault[A <: Schema[_]]: Tracker[A] => F[core.ResolvedType[L]] = { a =>
def buildResolveNoDefault[A <: Schema[_]]: Tracker[A] => F[core.Resolved[L]] = { a =>
for {
prefixes <- vendorPrefixes()
(tpe, rawType) <- determineTypeName[L, F](a, Tracker.cloneHistory(a, CustomTypeName(a, prefixes)), components)
} yield core.Resolved[L](tpe, None, None, rawType)
}

partial
.orRefine { case b: BooleanSchema => b }(buildResolveNoDefault)
schema
.refine[F[core.Resolved[L]]] { case b: BooleanSchema => b }(buildResolveNoDefault)
.orRefine { case s: StringSchema => s }(buildResolveNoDefault)
.orRefine { case s: EmailSchema => s }(buildResolveNoDefault)
.orRefine { case d: DateSchema => d }(buildResolveNoDefault)
Expand All @@ -308,7 +324,7 @@ object ModelResolver {
.orRefine { case b: BinarySchema => b }(buildResolveNoDefault)
.orRefine { case u: UUIDSchema => u }(buildResolveNoDefault)
.orRefineFallback(x =>
fallbackPropertyTypeHandler(x).map(core.Resolved[L](_, None, None, ReifiedRawType.unsafeEmpty))
fallbackPropertyTypeHandler(x).map(t => core.Resolved[L](t, None, None, ReifiedRawType.unsafeEmpty))
) // This may need to be rawType=string?
}
}
Loading

0 comments on commit 920b5f0

Please sign in to comment.