From 2200bc310c37758fd67e08efc728301ac593da20 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 13:47:37 -0800 Subject: [PATCH 01/21] Using lift instead of just switching which toXYZ is being called --- .../generators/scala/http4s/Http4sServerGenerator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala b/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala index df4b8c82d7..f4bfef2588 100644 --- a/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala +++ b/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala @@ -538,7 +538,7 @@ class Http4sServerGenerator private (version: Http4sVersion) extends ServerTerms }, arg => { case t"String" => - _ => Target.pure(Param(None, Some((q"urlForm.values.get(${arg.argName.toLit})", p"Some(${Pat.Var(arg.paramName)})")), q"${arg.paramName}.toList")) + lift => Target.pure(Param(None, Some((q"urlForm.values.get(${arg.argName.toLit})", p"Some(${Pat.Var(arg.paramName)})")), lift(q"${arg.paramName}"))) case tpe => _ => Target.pure( From b3fabdcebf446a79f584c5d9ad72d986f7550a95 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Wed, 16 Feb 2022 14:22:37 -0800 Subject: [PATCH 02/21] Renaming swagger to schema --- .../dev/guardrail/generators/ProtocolGenerator.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala index 51a7a723db..d826c8dfb5 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala @@ -69,7 +69,7 @@ object ProtocolGenerator { private[this] def fromEnum[L <: LA, F[_], A]( clsName: String, - swagger: Tracker[Schema[A]], + schema: Tracker[Schema[A]], dtoPackage: List[String] )(implicit P: ProtocolTerms[L, F], @@ -134,12 +134,13 @@ object ProtocolGenerator { // Default to `string` for untyped enums. // Currently, only plain strings are correctly supported anyway, so no big loss. - val tpeName = swagger.downField("type", _.getType()).map(_.filterNot(_ == "object").orElse(Option("string"))) + val tpeName = schema.downField("type", _.getType()).map(_.filterNot(_ == "object").orElse(Option("string"))) + val rawFormat = schema.downField("format", _.getFormat()) for { - enum <- extractEnum(swagger.map(wrapEnumSchema)) - customTpeName <- SwaggerUtil.customTypeName(swagger) - tpe <- SwaggerUtil.typeName(tpeName, swagger.downField("format", _.getFormat()), Tracker.cloneHistory(swagger, customTpeName)) + enum <- extractEnum(schema.map(wrapEnumSchema)) + customTpeName <- SwaggerUtil.customTypeName(schema) + tpe <- SwaggerUtil.typeName(tpeName, rawFormat, Tracker.cloneHistory(schema, customTpeName)) fullType <- selectType(NonEmptyList.fromList(dtoPackage :+ clsName).getOrElse(NonEmptyList.of(clsName))) res <- enum.traverse(validProg(_, tpe, fullType)) } yield res From 50dd432bff157448e6d5284d7fbe5fdd0e065884 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Wed, 16 Feb 2022 14:24:49 -0800 Subject: [PATCH 03/21] Eliminating unreachable code --- .../main/scala/dev/guardrail/generators/ProtocolGenerator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala index d826c8dfb5..26e6cdbf94 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala @@ -141,7 +141,7 @@ object ProtocolGenerator { enum <- extractEnum(schema.map(wrapEnumSchema)) customTpeName <- SwaggerUtil.customTypeName(schema) tpe <- SwaggerUtil.typeName(tpeName, rawFormat, Tracker.cloneHistory(schema, customTpeName)) - fullType <- selectType(NonEmptyList.fromList(dtoPackage :+ clsName).getOrElse(NonEmptyList.of(clsName))) + fullType <- selectType(NonEmptyList.ofInitLast(dtoPackage, clsName)) res <- enum.traverse(validProg(_, tpe, fullType)) } yield res } From 16301bf10c1a7b5c7a6e02482f2b79865cf4137e Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 19:33:16 -0800 Subject: [PATCH 04/21] DefaultCharset is deprecated --- .../sample-http4s/src/test/scala/core/issues/Issue148.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sample-http4s/src/test/scala/core/issues/Issue148.scala b/modules/sample-http4s/src/test/scala/core/issues/Issue148.scala index 7ae9af5954..226cac0877 100644 --- a/modules/sample-http4s/src/test/scala/core/issues/Issue148.scala +++ b/modules/sample-http4s/src/test/scala/core/issues/Issue148.scala @@ -54,7 +54,7 @@ class Issue148Suite extends AnyFunSuite with Matchers with EitherValues with Sca Request[IO](method = Method.POST, uri = Uri.unsafeFromString("/test")) .withEntity(body)( EntityEncoder[IO, String] - .withContentType(`Content-Type`(MediaType.application.json).withCharset(DefaultCharset)) + .withContentType(`Content-Type`(MediaType.application.json).withCharset(Charset.`UTF-8`)) ) def makeFormRequest(body: Multipart[IO]): Request[IO] = @@ -211,7 +211,7 @@ class Issue148Suite extends AnyFunSuite with Matchers with EitherValues with Sca Response[IO](Status.Ok) .withEntity(str)( EntityEncoder[IO, String] - .withContentType(`Content-Type`(MediaType.application.json).withCharset(DefaultCharset)) + .withContentType(`Content-Type`(MediaType.application.json).withCharset(Charset.`UTF-8`)) ) ) ) From 27aec871f5ded4737d5feaa0aa9636bc47e18b7e Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Tue, 15 Feb 2022 19:39:39 -0800 Subject: [PATCH 05/21] Threading through components --- .../core/src/main/scala/dev/guardrail/Common.scala | 7 ++++--- .../dev/guardrail/generators/ClientGenerator.scala | 7 +++++-- .../dev/guardrail/generators/LanguageParameter.scala | 12 +++++++----- .../dev/guardrail/generators/ServerGenerator.scala | 7 +++++-- .../main/scala/dev/guardrail/terms/RouteMeta.scala | 5 +++-- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/Common.scala b/modules/core/src/main/scala/dev/guardrail/Common.scala index 4abe653d2d..5a41f877a5 100644 --- a/modules/core/src/main/scala/dev/guardrail/Common.scala +++ b/modules/core/src/main/scala/dev/guardrail/Common.scala @@ -73,7 +73,8 @@ object Common { globalSecurityRequirements = NonEmptyList .fromList(swagger.downField("security", _.getSecurity).indexedDistribute) .flatMap(SecurityRequirements(_, SecurityRequirements.Global)) - requestBodies <- extractCommonRequestBodies(swagger.downField("components", _.getComponents)) + components = swagger.downField("components", _.getComponents) + requestBodies <- extractCommonRequestBodies(components) routes <- extractOperations(paths, requestBodies, globalSecurityRequirements) prefixes <- Cl.vendorPrefixes() securitySchemes <- SwaggerUtil.extractSecuritySchemes(swagger.unwrapTracker, prefixes) @@ -87,7 +88,7 @@ object Common { case CodegenTarget.Client => for { clientMeta <- ClientGenerator - .fromSwagger[L, F](context, frameworkImports)(serverUrls, basePath, groupedRoutes)(protocolElems, securitySchemes) + .fromSwagger[L, F](context, frameworkImports)(serverUrls, basePath, groupedRoutes)(protocolElems, securitySchemes, components) Clients(clients, supportDefinitions) = clientMeta frameworkImplicits <- getFrameworkImplicits() } yield CodegenDefinitions[L](clients, List.empty, supportDefinitions, frameworkImplicits) @@ -95,7 +96,7 @@ object Common { case CodegenTarget.Server => for { serverMeta <- ServerGenerator - .fromSwagger[L, F](context, supportPackage, basePath, frameworkImports)(groupedRoutes)(protocolElems, securitySchemes) + .fromSwagger[L, F](context, supportPackage, basePath, frameworkImports)(groupedRoutes)(protocolElems, securitySchemes, components) Servers(servers, supportDefinitions) = serverMeta frameworkImplicits <- getFrameworkImplicits() } yield CodegenDefinitions[L](List.empty, servers, supportDefinitions, frameworkImplicits) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala index 571bd48fb7..80c1bb9ca1 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala @@ -12,6 +12,8 @@ import dev.guardrail.terms.client.ClientTerms import dev.guardrail.terms.framework.FrameworkTerms import dev.guardrail.terms.protocol.{ StaticDefns, StrictProtocolElems } import dev.guardrail.{ Context, _ } +import dev.guardrail.core.Tracker +import io.swagger.v3.oas.models.Components case class Clients[L <: LA](clients: List[Client[L]], supportDefinitions: List[SupportDefinition[L]]) case class Client[L <: LA]( @@ -34,7 +36,8 @@ object ClientGenerator { groupedRoutes: List[(List[String], List[RouteMeta])] )( protocolElems: List[StrictProtocolElems[L]], - securitySchemes: Map[String, SecurityScheme[L]] + securitySchemes: Map[String, SecurityScheme[L]], + components: Tracker[Option[Components]] )(implicit C: ClientTerms[L, F], Fw: FrameworkTerms[L, F], Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F]): F[Clients[L]] = { import C._ import Sc._ @@ -53,7 +56,7 @@ object ClientGenerator { responses <- Responses.getResponses[L, F](operationId, operation, protocolElems) responseClsName <- formatTypeName(operationId, Some("Response")) responseDefinitions <- generateResponseDefinitions(responseClsName, responses, protocolElems) - parameters <- route.getParameters[L, F](protocolElems) + parameters <- route.getParameters[L, F](components, protocolElems) methodName <- formatMethodName(operationId) clientOp <- generateClientOperation(className, responseClsName, context.tracing, securitySchemes, parameters)(route, methodName, responses) } yield (responseDefinitions, clientOp) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index fb609f602f..f110ef5639 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -3,17 +3,17 @@ package dev.guardrail.generators import cats.syntax.all._ import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.parameters._ +import io.swagger.v3.oas.models.Components import dev.guardrail._ import dev.guardrail.core.extract.{ Default, FileHashAlgorithm } -import dev.guardrail.core.{ ResolvedType, Tracker } +import dev.guardrail.core.{ ReifiedRawType, ResolvedType, Tracker } import dev.guardrail.generators.syntax._ import dev.guardrail.languages.LA import dev.guardrail.shims._ import dev.guardrail.terms.framework.FrameworkTerms import dev.guardrail.terms.protocol._ import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, SwaggerTerms } -import dev.guardrail.core.ReifiedRawType case class RawParameterName private[generators] (value: String) class LanguageParameters[L <: LA](val parameters: List[LanguageParameter[L]]) { @@ -49,7 +49,8 @@ object LanguageParameter { Some((param.in, param.param, param.paramName, param.argName, param.argType)) def fromParameter[L <: LA, F[_]]( - protocolElems: List[StrictProtocolElems[L]] + protocolElems: List[StrictProtocolElems[L]], + components: Tracker[Option[Components]] )(implicit Fw: FrameworkTerms[L, F], Sc: LanguageTerms[L, F], @@ -179,7 +180,8 @@ object LanguageParameter { } def fromParameters[L <: LA, F[_]]( - protocolElems: List[StrictProtocolElems[L]] + protocolElems: List[StrictProtocolElems[L]], + components: Tracker[Option[Components]] )(implicit Fw: FrameworkTerms[L, F], Sc: LanguageTerms[L, F], @@ -188,7 +190,7 @@ object LanguageParameter { ): List[Tracker[Parameter]] => F[List[LanguageParameter[L]]] = { params => import Sc._ for { - parameters <- params.traverse(fromParameter(protocolElems)) + parameters <- params.traverse(fromParameter(protocolElems, components)) counts <- parameters.traverse(param => extractTermName(param.paramName)).map(_.groupBy(identity).view.mapValues(_.length).toMap) result <- parameters.traverse { param => extractTermName(param.paramName).flatMap { name => diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala index 9e1b7f8f8c..50105f3951 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala @@ -11,6 +11,8 @@ import dev.guardrail.terms.framework.FrameworkTerms import dev.guardrail.terms.protocol.StrictProtocolElems import dev.guardrail.terms.server.{ GenerateRouteMeta, SecurityExposure, ServerTerms } import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, RouteMeta, SecurityScheme, SwaggerTerms } +import dev.guardrail.core.Tracker +import io.swagger.v3.oas.models.Components case class Servers[L <: LA](servers: List[Server[L]], supportDefinitions: List[SupportDefinition[L]]) case class Server[L <: LA](pkg: List[String], extraImports: List[L#Import], handlerDefinition: L#Definition, serverDefinitions: List[L#Definition]) @@ -30,7 +32,8 @@ object ServerGenerator { groupedRoutes: List[(List[String], List[RouteMeta])] )( protocolElems: List[StrictProtocolElems[L]], - securitySchemes: Map[String, SecurityScheme[L]] + securitySchemes: Map[String, SecurityScheme[L]], + components: Tracker[Option[Components]] )(implicit Fw: FrameworkTerms[L, F], Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], S: ServerTerms[L, F], Sw: SwaggerTerms[L, F]): F[Servers[L]] = { import S._ import Sw._ @@ -58,7 +61,7 @@ object ServerGenerator { responseClsName <- formatTypeName(operationId, Some("Response")) responseDefinitions <- generateResponseDefinitions(responseClsName, responses, protocolElems) methodName <- formatMethodName(operationId) - parameters <- route.getParameters[L, F](protocolElems) + parameters <- route.getParameters[L, F](components, protocolElems) customExtractionField <- buildCustomExtractionFields(operation, className, context.customExtraction) tracingField <- buildTracingFields(operation, className, context.tracing) } yield ( diff --git a/modules/core/src/main/scala/dev/guardrail/terms/RouteMeta.scala b/modules/core/src/main/scala/dev/guardrail/terms/RouteMeta.scala index cda14c534a..9568409729 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/RouteMeta.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/RouteMeta.scala @@ -2,7 +2,7 @@ package dev.guardrail.terms import cats.data.State import cats.implicits._ -import io.swagger.v3.oas.models.Operation +import io.swagger.v3.oas.models.{ Components, Operation } import io.swagger.v3.oas.models.PathItem.HttpMethod import io.swagger.v3.oas.models.media._ import io.swagger.v3.oas.models.parameters.Parameter @@ -204,9 +204,10 @@ case class RouteMeta(path: Tracker[String], method: HttpMethod, operation: Track } def getParameters[L <: LA, F[_]]( + components: Tracker[Option[Components]], protocolElems: List[StrictProtocolElems[L]] )(implicit Fw: FrameworkTerms[L, F], Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F]): F[LanguageParameters[L]] = for { - a <- LanguageParameter.fromParameters(protocolElems).apply(parameters) + a <- LanguageParameter.fromParameters(protocolElems, components).apply(parameters) } yield new LanguageParameters[L](a) } From 959d83492d5137d35f98d9633c8f48e43b2e122e Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Tue, 15 Feb 2022 22:11:36 -0800 Subject: [PATCH 06/21] Adding dereference methods for following $refs through Components --- .../generators/SwaggerGenerator.scala | 36 +++++++++++++++++++ .../dev/guardrail/terms/SwaggerTerms.scala | 7 ++++ 2 files changed, 43 insertions(+) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala index 09c2e29596..ac8006254f 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala @@ -5,9 +5,12 @@ import cats.syntax.all._ import io.swagger.v3.oas.models.Components import io.swagger.v3.oas.models.Operation import io.swagger.v3.oas.models.PathItem +import io.swagger.v3.oas.models.headers import io.swagger.v3.oas.models.media.{ ArraySchema, Schema } import io.swagger.v3.oas.models.parameters.{ Parameter, RequestBody } +import io.swagger.v3.oas.models.responses.ApiResponse import io.swagger.v3.oas.models.security.{ SecurityRequirement, SecurityScheme => SwSecurityScheme } +import java.{ util => ju } import java.net.URI import scala.util.Try @@ -270,6 +273,39 @@ class SwaggerGenerator[L <: LA] extends SwaggerTerms[L, Target] { override def fallbackResolveElems(lazyElems: List[LazyProtocolElems[L]]) = Target.raiseUserError(s"Unable to resolve: ${lazyElems.map(_.name)}") + + private def buildExtractor[A](components: Tracker[Option[Components]], label: String, proj: Components => ju.Map[String, A])( + ref: Tracker[String] + ): Target[Tracker[A]] = { + val extract = s"^#/components/$label/([^/]*)$$".r + ref + .refine[Target[Tracker[A]]] { case extract(name) => name }(name => + components.indexedDistribute + .fold[Target[Tracker[A]]](Target.raiseException("Attempting to dereference a $ref, but no components defined"))(components => + Target.fromOption( + components.downField(label, proj).indexedDistribute.value.toMap.get(name.unwrapTracker), + UserError(s"Attempting to dereference a $$ref, but no object found at the specified pointer") + ) + ) + ) + .orRefineFallback(_ => + Target.raiseException( + s"While attempting to dereference '${label}', encountered a JSON pointer to a different component type: ${ref.unwrapTracker} (${ref.showHistory})" + ) + ) + } + + def dereferenceHeader(ref: Tracker[String], components: Tracker[Option[Components]]): dev.guardrail.Target[Tracker[headers.Header]] = + buildExtractor(components, "headers", _.getHeaders())(ref) + def dereferenceParameter(ref: Tracker[String], components: Tracker[Option[Components]]): dev.guardrail.Target[Tracker[Parameter]] = + buildExtractor(components, "parameters", _.getParameters())(ref) + def dereferenceRequestBodie(ref: Tracker[String], components: Tracker[Option[Components]]): dev.guardrail.Target[Tracker[RequestBody]] = + buildExtractor(components, "requestBodies", _.getRequestBodies())(ref) + def dereferenceResponse(ref: Tracker[String], components: Tracker[Option[Components]]): dev.guardrail.Target[Tracker[ApiResponse]] = + buildExtractor(components, "responses", _.getResponses())(ref) + def dereferenceSchema(ref: Tracker[String], components: Tracker[Option[Components]]): dev.guardrail.Target[Tracker[Schema[_]]] = + buildExtractor(components, "schemas", _.getSchemas())(ref) + override def log: SwaggerLogAdapter[Target] = new SwaggerLogAdapter[Target] { def function[A](name: String): Target[A] => Target[A] = Target.log.function(name) def push(name: String): Target[Unit] = Target.log.push(name) diff --git a/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala b/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala index d9364e9b15..99f80730af 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala @@ -58,5 +58,12 @@ abstract class SwaggerTerms[L <: LA, F[_]] { def fallbackPropertyTypeHandler(prop: Tracker[Schema[_]]): F[L#Type] def resolveType(name: String, protocolElems: List[StrictProtocolElems[L]]): F[StrictProtocolElems[L]] def fallbackResolveElems(lazyElems: List[LazyProtocolElems[L]]): F[List[StrictProtocolElems[L]]] + + def dereferenceHeader(ref: Tracker[String], components: Tracker[Option[Components]]): F[Tracker[headers.Header]] + def dereferenceParameter(ref: Tracker[String], components: Tracker[Option[Components]]): F[Tracker[Parameter]] + def dereferenceRequestBodie(ref: Tracker[String], components: Tracker[Option[Components]]): F[Tracker[RequestBody]] + def dereferenceResponse(ref: Tracker[String], components: Tracker[Option[Components]]): F[Tracker[ApiResponse]] + def dereferenceSchema(ref: Tracker[String], components: Tracker[Option[Components]]): F[Tracker[Schema[_]]] + def log: SwaggerLogAdapter[F] } From dc2a0a89879fddef52b927c2a6bf7f344c9c3303 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 13 Feb 2022 19:13:24 -0800 Subject: [PATCH 07/21] Using schema subtypes for default extraction instead of string comparison --- .../generators/LanguageParameter.scala | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index f110ef5639..a889c5d539 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -1,7 +1,7 @@ package dev.guardrail.generators import cats.syntax.all._ -import io.swagger.v3.oas.models.media.Schema +import io.swagger.v3.oas.models.media import io.swagger.v3.oas.models.parameters._ import io.swagger.v3.oas.models.Components @@ -64,21 +64,17 @@ object LanguageParameter { def paramMeta(param: Tracker[Parameter]): F[(core.ResolvedType[L], Boolean)] = { def getDefault[U <: Parameter: Default.GetDefault](_type: String, fmt: Tracker[Option[String]], p: Tracker[U]): F[Option[L#Term]] = - (_type, fmt.unwrapTracker) match { - case ("string", None) => - Default(p).extract[String].traverse(litString(_)) - case ("number", Some("float")) => - Default(p).extract[Float].traverse(litFloat(_)) - case ("number", Some("double")) => - Default(p).extract[Double].traverse(litDouble(_)) - case ("integer", Some("int32")) => - Default(p).extract[Int].traverse(litInt(_)) - case ("integer", Some("int64")) => - Default(p).extract[Long].traverse(litLong(_)) - case ("boolean", None) => - Default(p).extract[Boolean].traverse(litBoolean(_)) - case x => Option.empty[L#Term].pure[F] - } + for { + schema <- getBodyParameterSchema(p) + res <- schema + .refine[F[Option[L#Term]]] { case x: media.StringSchema => x }(schema => Default(schema).extract[String].traverse(litString)) + .orRefine { case x: media.NumberSchema if x.getFormat() == "float" => x }(schema => Default(schema).extract[Float].traverse(litFloat)) + .orRefine { case x: media.NumberSchema => x }(schema => Default(schema).extract[Double].traverse(litDouble)) + .orRefine { case x: media.IntegerSchema if x.getFormat() == "int32" => x }(schema => Default(schema).extract[Int].traverse(litInt)) + .orRefine { case x: media.IntegerSchema => x }(schema => Default(schema).extract[Long].traverse(litLong)) + .orRefine { case x: media.BooleanSchema => x }(schema => Default(schema).extract[Boolean].traverse(litBoolean)) + .orRefineFallback(_ => Option.empty[L#Term].pure[F]) + } yield res def resolveParam(param: Tracker[Parameter], typeFetcher: Tracker[Parameter] => F[Tracker[String]]): F[(ResolvedType[L], Boolean)] = for { @@ -86,7 +82,7 @@ object LanguageParameter { schema = param.downField("schema", _.getSchema) fmt = schema.flatDownField("format", _.getFormat) customParamTypeName <- SwaggerUtil.customTypeName(param) - customSchemaTypeName <- schema.unwrapTracker.flatTraverse(SwaggerUtil.customTypeName(_: Schema[_])) + customSchemaTypeName <- schema.unwrapTracker.flatTraverse(SwaggerUtil.customTypeName(_: media.Schema[_])) customTypeName = Tracker.cloneHistory(schema, customSchemaTypeName).fold(Tracker.cloneHistory(param, customParamTypeName))(_.map(Option.apply)) res <- (SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), fmt, customTypeName), getDefault(tpeName.unwrapTracker, fmt, param)) .mapN(core.Resolved[L](_, None, _, Some(tpeName.unwrapTracker), fmt.unwrapTracker)) From 4c1a30391d8029dc8b11bd45c051b6b540c6cfc8 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 13 Feb 2022 23:00:57 -0800 Subject: [PATCH 08/21] Using parameter schema exclusively instead of getType/getFormat --- .../dev/guardrail/generators/LanguageParameter.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index a889c5d539..fecb568e74 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -63,7 +63,7 @@ object LanguageParameter { import Sw._ def paramMeta(param: Tracker[Parameter]): F[(core.ResolvedType[L], Boolean)] = { - def getDefault[U <: Parameter: Default.GetDefault](_type: String, fmt: Tracker[Option[String]], p: Tracker[U]): F[Option[L#Term]] = + def getDefault[U <: Parameter: Default.GetDefault](p: Tracker[U]): F[Option[L#Term]] = for { schema <- getBodyParameterSchema(p) res <- schema @@ -79,12 +79,12 @@ object LanguageParameter { def resolveParam(param: Tracker[Parameter], typeFetcher: Tracker[Parameter] => F[Tracker[String]]): F[(ResolvedType[L], Boolean)] = for { tpeName <- typeFetcher(param) - schema = param.downField("schema", _.getSchema) - fmt = schema.flatDownField("format", _.getFormat) + schema <- getBodyParameterSchema(param) + fmt = schema.downField("format", _.getFormat) customParamTypeName <- SwaggerUtil.customTypeName(param) - customSchemaTypeName <- schema.unwrapTracker.flatTraverse(SwaggerUtil.customTypeName(_: media.Schema[_])) + customSchemaTypeName <- SwaggerUtil.customTypeName(schema.unwrapTracker) customTypeName = Tracker.cloneHistory(schema, customSchemaTypeName).fold(Tracker.cloneHistory(param, customParamTypeName))(_.map(Option.apply)) - res <- (SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), fmt, customTypeName), getDefault(tpeName.unwrapTracker, fmt, param)) + res <- (SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), fmt, customTypeName), getDefault(param)) .mapN(core.Resolved[L](_, None, _, Some(tpeName.unwrapTracker), fmt.unwrapTracker)) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (res, required) From 5cca6731aec131cb73f759e26188ed42183a9eec Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Wed, 16 Feb 2022 14:02:39 -0800 Subject: [PATCH 09/21] Deduplicate call to getBodyParameterSchema --- .../scala/dev/guardrail/generators/LanguageParameter.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index fecb568e74..d687744dcb 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -63,9 +63,8 @@ object LanguageParameter { import Sw._ def paramMeta(param: Tracker[Parameter]): F[(core.ResolvedType[L], Boolean)] = { - def getDefault[U <: Parameter: Default.GetDefault](p: Tracker[U]): F[Option[L#Term]] = + def getDefault[U <: Parameter: Default.GetDefault](schema: Tracker[media.Schema[_]]): F[Option[L#Term]] = for { - schema <- getBodyParameterSchema(p) res <- schema .refine[F[Option[L#Term]]] { case x: media.StringSchema => x }(schema => Default(schema).extract[String].traverse(litString)) .orRefine { case x: media.NumberSchema if x.getFormat() == "float" => x }(schema => Default(schema).extract[Float].traverse(litFloat)) @@ -84,7 +83,7 @@ object LanguageParameter { customParamTypeName <- SwaggerUtil.customTypeName(param) customSchemaTypeName <- SwaggerUtil.customTypeName(schema.unwrapTracker) customTypeName = Tracker.cloneHistory(schema, customSchemaTypeName).fold(Tracker.cloneHistory(param, customParamTypeName))(_.map(Option.apply)) - res <- (SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), fmt, customTypeName), getDefault(param)) + res <- (SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), fmt, customTypeName), getDefault(schema)) .mapN(core.Resolved[L](_, None, _, Some(tpeName.unwrapTracker), fmt.unwrapTracker)) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (res, required) From 209863b81c5704cc8c5c2363b5be266b1bae18f5 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 13 Feb 2022 23:03:49 -0800 Subject: [PATCH 10/21] Using Schema[_] for determineTypeName --- .../scala/dev/guardrail/SwaggerUtil.scala | 69 +++++++++++-------- .../generators/LanguageParameter.scala | 2 +- .../generators/ProtocolGenerator.scala | 25 ++----- 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala index 8f0fbc4e98..4d4861af06 100644 --- a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala +++ b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala @@ -84,12 +84,11 @@ object SwaggerUtil { } // Standard type conversions, as documented in http://swagger.io/specification/#data-types-12 - def typeName[L <: LA, F[_]]( - typeName: Tracker[Option[String]], - format: Tracker[Option[String]], + def determineTypeName[L <: LA, F[_]]( + schema: Tracker[Schema[_]], customType: Tracker[Option[String]] )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[L#Type] = - Sw.log.function(s"typeName(${typeName.unwrapTracker}, ${format.unwrapTracker}, ${customType.unwrapTracker})") { + Sw.log.function(s"determineTypeName(${schema.unwrapTracker}, ${customType.unwrapTracker})") { import Sc._ import Cl._ import Fw._ @@ -97,7 +96,8 @@ object SwaggerUtil { def log(fmt: Option[String], t: L#Type): L#Type = { fmt.foreach { fmt => println( - s"Warning: Deprecated behavior: Unsupported format '$fmt' for type '${typeName.unwrapTracker}', falling back to $t. Please switch definitions to x-scala-type for custom types. (${format.showHistory})" + s"Warning: Deprecated behavior: Unsupported format '$fmt' for type '${schema.unwrapTracker + .getType()}', falling back to $t. Please switch definitions to x-scala-type for custom types. (${schema.showHistory})" ) } @@ -107,31 +107,40 @@ object SwaggerUtil { for { customTpe <- customType.indexedDistribute.flatTraverse(x => liftCustomType[L, F](x)) result <- customTpe.fold { - (typeName.unwrapTracker, format.unwrapTracker) match { - case (Some("string"), Some("uuid")) => uuidType() - case (Some("string"), Some("password")) => stringType(None) - case (Some("string"), Some("email")) => stringType(None) - case (Some("string"), Some("date")) => dateType() - case (Some("string"), Some("date-time")) => dateTimeType() - case (Some("string"), Some("byte")) => bytesType() - case (Some("string"), fmt @ Some("binary")) => fileType(None).map(log(fmt, _)) - case (Some("string"), fmt) => stringType(None).map(log(fmt, _)) - case (Some("number"), Some("float")) => floatType() - case (Some("number"), Some("double")) => doubleType() - case (Some("number"), fmt) => numberType(fmt).map(log(fmt, _)) - case (Some("integer"), Some("int32")) => intType() - case (Some("integer"), Some("int64")) => longType() - case (Some("integer"), fmt) => integerType(fmt).map(log(fmt, _)) - case (Some("boolean"), fmt) => booleanType(fmt).map(log(fmt, _)) - case (Some("array"), fmt) => arrayType(fmt).map(log(fmt, _)) - case (Some("file"), fmt) => - fileType(None).map(log(fmt, _)) - case (Some("binary"), fmt) => - fileType(None).map(log(fmt, _)) - case (Some("object"), fmt) => objectType(fmt).map(log(fmt, _)) - case (tpe, fmt) => - fallbackType(tpe, fmt) + def const(value: F[L#Type]): Tracker[Schema[_]] => F[L#Type] = { _ => value } + def extractFormat(func: Option[String] => F[L#Type]): Tracker[Schema[_]] => F[L#Type] = { schema => + val fmt = schema.downField("format", _.getFormat()).unwrapTracker + func(fmt) } + schema + .refine[F[L#Type]] { case x: StringSchema if x.getFormat() == "uuid" => x }(const(uuidType())) + .orRefine { case x: StringSchema if x.getType() == "file" => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case x: StringSchema if x.getFormat() == "password" => x }(const(stringType(None))) + .orRefine { case x: StringSchema if x.getFormat() == "email" => x }(const(stringType(None))) + .orRefine { case x: StringSchema if x.getFormat() == "date" => x }(const(dateType())) + .orRefine { case x: StringSchema if x.getFormat() == "date-time" => x }(const(dateTimeType())) + .orRefine { case x: StringSchema if x.getFormat() == "byte" => x }(const(bytesType())) + .orRefine { case x: StringSchema if x.getFormat() == "binary" => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case x: StringSchema => x }(extractFormat(fmt => stringType(None).map(log(fmt, _)))) + .orRefine { case x: NumberSchema if x.getFormat() == "float" => x }(const(floatType())) + .orRefine { case x: NumberSchema if x.getFormat() == "double" => x }(const(doubleType())) + .orRefine { case x: NumberSchema => x }(extractFormat(fmt => numberType(fmt).map(log(fmt, _)))) + .orRefine { case x: IntegerSchema if x.getFormat() == "int32" => x }(const(intType())) + .orRefine { case x: IntegerSchema if x.getFormat() == "int64" => x }(const(longType())) + .orRefine { case x: IntegerSchema => x }(extractFormat(fmt => integerType(fmt).map(log(fmt, _)))) + .orRefine { case x: BooleanSchema => x }(extractFormat(fmt => booleanType(fmt).map(log(fmt, _)))) + .orRefine { case x: ArraySchema => x }(schema => + schema + .downField("items", _.getItems()) + .cotraverse(determineTypeName(_, Tracker.cloneHistory(schema, None)).flatMap(liftVectorType(_, None))) + .getOrElse(arrayType(None)) + ) + .orRefine { case x: FileSchema => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case x: BinarySchema => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case x: ObjectSchema => x }(extractFormat(fmt => objectType(fmt).map(log(fmt, _)))) + .orRefineFallback(schema => + fallbackType(schema.downField("type", _.getType()).unwrapTracker, schema.downField("format", _.getFormat()).unwrapTracker) + ) }(_.pure[F]) _ <- Sw.log.debug(s"Returning ${result}") } yield result @@ -186,7 +195,7 @@ object SwaggerUtil { for { customTpeName <- customTypeName(a) - tpe <- typeName[L, F](rawType, rawFormat, Tracker.cloneHistory(a, customTpeName)) + tpe <- determineTypeName[L, F](a, Tracker.cloneHistory(a, customTpeName)) } yield core.Resolved[L](tpe, None, None, rawType.unwrapTracker, rawFormat.unwrapTracker) } diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index d687744dcb..0637360943 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -83,7 +83,7 @@ object LanguageParameter { customParamTypeName <- SwaggerUtil.customTypeName(param) customSchemaTypeName <- SwaggerUtil.customTypeName(schema.unwrapTracker) customTypeName = Tracker.cloneHistory(schema, customSchemaTypeName).fold(Tracker.cloneHistory(param, customParamTypeName))(_.map(Option.apply)) - res <- (SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), fmt, customTypeName), getDefault(schema)) + res <- (SwaggerUtil.determineTypeName[L, F](schema, customTypeName), getDefault(schema)) .mapN(core.Resolved[L](_, None, _, Some(tpeName.unwrapTracker), fmt.unwrapTracker)) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (res, required) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala index 26e6cdbf94..91cd858611 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala @@ -132,15 +132,10 @@ object ProtocolGenerator { classType <- pureTypeName(clsName) } yield EnumDefinition[L](clsName, classType, fullType, wrappedValues, defn, staticDefns) - // Default to `string` for untyped enums. - // Currently, only plain strings are correctly supported anyway, so no big loss. - val tpeName = schema.downField("type", _.getType()).map(_.filterNot(_ == "object").orElse(Option("string"))) - val rawFormat = schema.downField("format", _.getFormat()) - for { enum <- extractEnum(schema.map(wrapEnumSchema)) customTpeName <- SwaggerUtil.customTypeName(schema) - tpe <- SwaggerUtil.typeName(tpeName, rawFormat, Tracker.cloneHistory(schema, customTpeName)) + tpe <- SwaggerUtil.determineTypeName(schema, Tracker.cloneHistory(schema, customTpeName)) fullType <- selectType(NonEmptyList.ofInitLast(dtoPackage, clsName)) res <- enum.traverse(validProg(_, tpe, fullType)) } yield res @@ -539,14 +534,9 @@ object ProtocolGenerator { .orRefineFallback(_ => None) for { tpe <- model.fold[F[L#Type]](objectType(None)) { m => - val raw = m.downField("type", _.getType()) for { tpeName <- SwaggerUtil.customTypeName[L, F, Tracker[ObjectSchema]](m) - res <- SwaggerUtil.typeName[L, F]( - raw, - m.downField("format", _.getFormat()), - Tracker.cloneHistory(m, tpeName) - ) + res <- SwaggerUtil.determineTypeName[L, F](m, Tracker.cloneHistory(m, tpeName)) } yield res } res <- typeAlias[L, F](clsName, tpe) @@ -669,7 +659,6 @@ object ProtocolGenerator { ): F[ProtocolDefinitions[L]] = { import P._ import Sc._ - import Sw._ val definitions = swagger.downField("components", _.getComponents()).flatDownField("schemas", _.getSchemas()).indexedCosequence Sw.log.function("ProtocolGenerator.fromSwagger")(for { @@ -750,19 +739,17 @@ object ProtocolGenerator { supportPackage.toList, defaultPropertyRequirement ) - tpeName <- getType(x) customTypeName <- SwaggerUtil.customTypeName(x) - tpe <- SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), x.downField("format", _.getFormat()), Tracker.cloneHistory(x, customTypeName)) - alias <- typeAlias[L, F](formattedClsName, tpe) + tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName)) + alias <- typeAlias[L, F](formattedClsName, tpe) } yield enum.orElse(model).getOrElse(alias) ) .valueOr(x => for { formattedClsName <- formatTypeName(clsName) - tpeName <- getType(x) customTypeName <- SwaggerUtil.customTypeName(x) - tpe <- SwaggerUtil.typeName[L, F](tpeName.map(Option(_)), x.downField("format", _.getFormat()), Tracker.cloneHistory(x, customTypeName)) - res <- typeAlias[L, F](formattedClsName, tpe) + tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName)) + res <- typeAlias[L, F](formattedClsName, tpe) } yield res ) } From eff310cc302e2d597592e8e2aac02922191c030c Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 19:32:36 -0800 Subject: [PATCH 11/21] Threading components through ProtocolGenerator --- .../scala/dev/guardrail/SwaggerUtil.scala | 108 ++++++++++-------- .../generators/LanguageParameter.scala | 14 ++- .../generators/ProtocolGenerator.scala | 79 +++++++------ .../generators/SwaggerGenerator.scala | 16 ++- .../dev/guardrail/terms/SwaggerTerms.scala | 6 +- 5 files changed, 135 insertions(+), 88 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala index 4d4861af06..340d1ca1a1 100644 --- a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala +++ b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala @@ -7,7 +7,7 @@ import io.swagger.v3.oas.models.security.{ SecurityScheme => SwSecurityScheme } import cats.syntax.all._ import dev.guardrail.core.Tracker import dev.guardrail.core.implicits._ -import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, SecurityScheme, SwaggerTerms } +import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, SchemaLiteral, SchemaProjection, SchemaRef, SecurityScheme, SwaggerTerms } import dev.guardrail.terms.framework.FrameworkTerms import dev.guardrail.core.extract.{ CustomArrayTypeName, CustomMapTypeName, CustomTypeName, Default, Extractable, VendorExtension } import dev.guardrail.core.extract.VendorExtension.VendorExtensible._ @@ -40,7 +40,7 @@ object SwaggerUtil { def modelMetaType[L <: LA, F[_]]( model: Tracker[Schema[_]] )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] = - propMetaImpl[L, F](model)(Left(_)) + propMetaImpl[L, F](model, Tracker.cloneHistory(model, None))(Left(_)) def extractConcreteTypes[L <: LA, F[_]]( definitions: List[(String, Tracker[Schema[_]])] @@ -85,10 +85,11 @@ object SwaggerUtil { // Standard type conversions, as documented in http://swagger.io/specification/#data-types-12 def determineTypeName[L <: LA, F[_]]( - schema: Tracker[Schema[_]], - customType: Tracker[Option[String]] + rawSchema: Tracker[Schema[_]], + customType: Tracker[Option[String]], + components: Tracker[Option[Components]] )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[L#Type] = - Sw.log.function(s"determineTypeName(${schema.unwrapTracker}, ${customType.unwrapTracker})") { + Sw.log.function(s"determineTypeName(${rawSchema.unwrapTracker}, ${customType.unwrapTracker})") { import Sc._ import Cl._ import Fw._ @@ -96,8 +97,8 @@ object SwaggerUtil { def log(fmt: Option[String], t: L#Type): L#Type = { fmt.foreach { fmt => println( - s"Warning: Deprecated behavior: Unsupported format '$fmt' for type '${schema.unwrapTracker - .getType()}', falling back to $t. Please switch definitions to x-scala-type for custom types. (${schema.showHistory})" + s"Warning: Deprecated behavior: Unsupported format '$fmt' for type '${rawSchema.unwrapTracker + .getType()}', falling back to $t. Please switch definitions to x-scala-type for custom types. (${rawSchema.showHistory})" ) } @@ -105,6 +106,12 @@ object SwaggerUtil { } for { + schemaProjection <- rawSchema + .downField("$ref", _.get$ref()) + .indexedDistribute + .fold[F[Tracker[SchemaProjection]]](rawSchema.map(SchemaLiteral(_): SchemaProjection).pure[F]) { ref => + Sw.dereferenceSchema(ref, components).map(_.map(schema => SchemaRef(SchemaLiteral(schema), ref.unwrapTracker))) + } customTpe <- customType.indexedDistribute.flatTraverse(x => liftCustomType[L, F](x)) result <- customTpe.fold { def const(value: F[L#Type]): Tracker[Schema[_]] => F[L#Type] = { _ => value } @@ -112,35 +119,41 @@ object SwaggerUtil { val fmt = schema.downField("format", _.getFormat()).unwrapTracker func(fmt) } - schema - .refine[F[L#Type]] { case x: StringSchema if x.getFormat() == "uuid" => x }(const(uuidType())) - .orRefine { case x: StringSchema if x.getType() == "file" => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) - .orRefine { case x: StringSchema if x.getFormat() == "password" => x }(const(stringType(None))) - .orRefine { case x: StringSchema if x.getFormat() == "email" => x }(const(stringType(None))) - .orRefine { case x: StringSchema if x.getFormat() == "date" => x }(const(dateType())) - .orRefine { case x: StringSchema if x.getFormat() == "date-time" => x }(const(dateTimeType())) - .orRefine { case x: StringSchema if x.getFormat() == "byte" => x }(const(bytesType())) - .orRefine { case x: StringSchema if x.getFormat() == "binary" => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) - .orRefine { case x: StringSchema => x }(extractFormat(fmt => stringType(None).map(log(fmt, _)))) - .orRefine { case x: NumberSchema if x.getFormat() == "float" => x }(const(floatType())) - .orRefine { case x: NumberSchema if x.getFormat() == "double" => x }(const(doubleType())) - .orRefine { case x: NumberSchema => x }(extractFormat(fmt => numberType(fmt).map(log(fmt, _)))) - .orRefine { case x: IntegerSchema if x.getFormat() == "int32" => x }(const(intType())) - .orRefine { case x: IntegerSchema if x.getFormat() == "int64" => x }(const(longType())) - .orRefine { case x: IntegerSchema => x }(extractFormat(fmt => integerType(fmt).map(log(fmt, _)))) - .orRefine { case x: BooleanSchema => x }(extractFormat(fmt => booleanType(fmt).map(log(fmt, _)))) - .orRefine { case x: ArraySchema => x }(schema => + schemaProjection + .refine[F[L#Type]] { case SchemaRef(_, ref) => ref }(ref => fallbackType(ref.unwrapTracker.split("/").lastOption, None)) + .orRefine { case SchemaLiteral(x: ObjectSchema) if Option(x.getEnum).map(_.asScala).exists(_.nonEmpty) => x }( + extractFormat(fmt => stringType(None).map(log(fmt, _))) + ) + .orRefine { case SchemaLiteral(x: UUIDSchema) => x }(const(uuidType())) + .orRefine { case SchemaLiteral(x: StringSchema) if x.getType() == "file" => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: StringSchema) if x.getFormat() == "password" => x }(const(stringType(None))) + .orRefine { case SchemaLiteral(x: StringSchema) if x.getFormat() == "email" => x }(const(stringType(None))) + .orRefine { case SchemaLiteral(x: DateSchema) => x }(const(dateType())) + .orRefine { case SchemaLiteral(x: DateTimeSchema) => x }(const(dateTimeType())) + .orRefine { case SchemaLiteral(x: StringSchema) if x.getFormat() == "byte" => x }(const(bytesType())) + .orRefine { case SchemaLiteral(x: StringSchema) if x.getFormat() == "binary" => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: StringSchema) => x }(extractFormat(fmt => stringType(None).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: NumberSchema) if x.getFormat() == "float" => x }(const(floatType())) + .orRefine { case SchemaLiteral(x: NumberSchema) if x.getFormat() == "double" => x }(const(doubleType())) + .orRefine { case SchemaLiteral(x: NumberSchema) => x }(extractFormat(fmt => numberType(fmt).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: IntegerSchema) if x.getFormat() == "int32" => x }(const(intType())) + .orRefine { case SchemaLiteral(x: IntegerSchema) if x.getFormat() == "int64" => x }(const(longType())) + .orRefine { case SchemaLiteral(x: IntegerSchema) => x }(extractFormat(fmt => integerType(fmt).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: BooleanSchema) => x }(extractFormat(fmt => booleanType(fmt).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: ArraySchema) => x }(schema => schema .downField("items", _.getItems()) - .cotraverse(determineTypeName(_, Tracker.cloneHistory(schema, None)).flatMap(liftVectorType(_, None))) + .cotraverse(determineTypeName(_, Tracker.cloneHistory(schema, None), components).flatMap(liftVectorType(_, None))) .getOrElse(arrayType(None)) ) - .orRefine { case x: FileSchema => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) - .orRefine { case x: BinarySchema => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) - .orRefine { case x: ObjectSchema => x }(extractFormat(fmt => objectType(fmt).map(log(fmt, _)))) - .orRefineFallback(schema => - fallbackType(schema.downField("type", _.getType()).unwrapTracker, schema.downField("format", _.getFormat()).unwrapTracker) - ) + .orRefine { case SchemaLiteral(x: FileSchema) => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: BinarySchema) => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: ObjectSchema) => x }(extractFormat(fmt => objectType(fmt).map(log(fmt, _)))) + .orRefine { case SchemaLiteral(x: MapSchema) => x }(extractFormat(fmt => objectType(fmt).map(log(fmt, _)))) + .orRefineFallback { schema => + println(schema) + ??? + } }(_.pure[F]) _ <- Sw.log.debug(s"Returning ${result}") } yield result @@ -159,7 +172,7 @@ object SwaggerUtil { Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F] - ): F[core.ResolvedType[L]] = propMetaImpl(property)(Left(_)) + ): F[core.ResolvedType[L]] = propMetaImpl(property, Tracker.cloneHistory(property, None))(Left(_)) private[this] def liftCustomType[L <: LA, F[_]](s: Tracker[String])(implicit Sc: LanguageTerms[L, F]): F[Option[L#Type]] = { import Sc._ @@ -169,13 +182,13 @@ object SwaggerUtil { } else Option.empty[L#Type].pure[F] } - def propMetaWithName[L <: LA, F[_]](tpe: L#Type, property: Tracker[Schema[_]])(implicit + def propMetaWithName[L <: LA, F[_]](tpe: L#Type, property: Tracker[Schema[_]], components: Tracker[Option[Components]])(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F] ): F[core.ResolvedType[L]] = - propMetaImpl(property)( + propMetaImpl(property, components)( _.refine[core.ResolvedType[L]] { case schema: ObjectSchema if Option(schema.getProperties).exists(p => !p.isEmpty) => schema }(_ => core.Resolved[L](tpe, None, None, None, None) ).orRefine { case c: ComposedSchema => c }(_ => core.Resolved[L](tpe, None, None, None, None)) @@ -186,7 +199,8 @@ object SwaggerUtil { ) private def resolveScalarTypes[L <: LA, F[_]]( - partial: Either[Tracker[Schema[_]], F[core.ResolvedType[L]]] + partial: Either[Tracker[Schema[_]], F[core.ResolvedType[L]]], + components: Tracker[Option[Components]] )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] = { import Sw._ def buildResolveNoDefault[A <: Schema[_]]: Tracker[A] => F[core.ResolvedType[L]] = { a => @@ -195,7 +209,7 @@ object SwaggerUtil { for { customTpeName <- customTypeName(a) - tpe <- determineTypeName[L, F](a, Tracker.cloneHistory(a, customTpeName)) + tpe <- determineTypeName[L, F](a, Tracker.cloneHistory(a, customTpeName), components) } yield core.Resolved[L](tpe, None, None, rawType.unwrapTracker, rawFormat.unwrapTracker) } @@ -236,7 +250,7 @@ object SwaggerUtil { .orRefineFallback(_ => resolved.pure[F]) } - private def propMetaImpl[L <: LA, F[_]](property: Tracker[Schema[_]])( + private def propMetaImpl[L <: LA, F[_]](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: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] = Sw.log.function("propMeta") { @@ -245,8 +259,10 @@ object SwaggerUtil { import Cl._ import Sw._ - log.debug(s"property:\n${log.schemaToString(property.unwrapTracker)} (${property.unwrapTracker.getExtensions()}, ${property.showHistory})") >> ( - strategy(property) + for { + _ <- log.debug(s"property:\n${log.schemaToString(property.unwrapTracker)} (${property.unwrapTracker.getExtensions()}, ${property.showHistory})") + + res <- strategy(property) .orRefine { case o: ObjectSchema => o }(o => for { customTpeName <- customTypeName(o) @@ -257,7 +273,7 @@ object SwaggerUtil { .orRefine { case arr: ArraySchema => arr }(arr => for { items <- getItems(arr) - meta <- propMetaImpl[L, F](items)(strategy) + meta <- propMetaImpl[L, F](items, components)(strategy) rawType = arr.downField("type", _.getType()) rawFormat = arr.downField("format", _.getFormat()) arrayType <- customArrayTypeName(arr).flatMap(_.flatTraverse(x => parseType(Tracker.cloneHistory(arr, x)))) @@ -281,7 +297,7 @@ object SwaggerUtil { .refine[F[core.ResolvedType[L]]] { case b: java.lang.Boolean => b }(_ => objectType(None).map(core.Resolved[L](_, None, None, rawType.unwrapTracker, rawFormat.unwrapTracker)) ) - .orRefine { case s: Schema[_] => s }(s => propMetaImpl[L, F](s)(strategy)) + .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, rawType.unwrapTracker, rawFormat.unwrapTracker)) @@ -296,10 +312,10 @@ object SwaggerUtil { } yield res } .orRefine { case ref: Schema[_] if Option(ref.get$ref).isDefined => ref }(ref => getSimpleRef(ref.map(Option.apply _)).map(core.Deferred[L])) - ) - .pure[F] - .flatMap(resolveScalarTypes[L, F]) - .flatMap(enrichWithDefault[L, F](property)) + .pure[F] + scalarResolved <- resolveScalarTypes[L, F](res, components) + withDefaults <- enrichWithDefault[L, F](property).apply(scalarResolved) + } yield withDefaults } def extractSecuritySchemes[L <: LA, F[_]]( diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index 0637360943..e17246809f 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -13,7 +13,7 @@ import dev.guardrail.languages.LA import dev.guardrail.shims._ import dev.guardrail.terms.framework.FrameworkTerms import dev.guardrail.terms.protocol._ -import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, SwaggerTerms } +import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, SchemaLiteral, SchemaRef, SwaggerTerms } case class RawParameterName private[generators] (value: String) class LanguageParameters[L <: LA](val parameters: List[LanguageParameter[L]]) { @@ -78,12 +78,15 @@ object LanguageParameter { def resolveParam(param: Tracker[Parameter], typeFetcher: Tracker[Parameter] => F[Tracker[String]]): F[(ResolvedType[L], Boolean)] = for { tpeName <- typeFetcher(param) - schema <- getBodyParameterSchema(param) + schema <- getParameterSchema(param, components).map(_.map { + case SchemaLiteral(schema) => schema + case SchemaRef(SchemaLiteral(schema), _) => schema + }) fmt = schema.downField("format", _.getFormat) customParamTypeName <- SwaggerUtil.customTypeName(param) customSchemaTypeName <- SwaggerUtil.customTypeName(schema.unwrapTracker) customTypeName = Tracker.cloneHistory(schema, customSchemaTypeName).fold(Tracker.cloneHistory(param, customParamTypeName))(_.map(Option.apply)) - res <- (SwaggerUtil.determineTypeName[L, F](schema, customTypeName), getDefault(schema)) + res <- (SwaggerUtil.determineTypeName[L, F](schema, customTypeName, components), getDefault(schema)) .mapN(core.Resolved[L](_, None, _, Some(tpeName.unwrapTracker), fmt.unwrapTracker)) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (res, required) @@ -105,7 +108,10 @@ object LanguageParameter { ) .orRefine { case x: Parameter if x.isInBody => x }(param => for { - schema <- getBodyParameterSchema(param) + schema <- getParameterSchema(param, components).map(_.map { + case SchemaLiteral(schema) => schema + case SchemaRef(SchemaLiteral(schema), _) => schema + }) resolved <- SwaggerUtil.modelMetaType[L, F](schema) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (resolved, required) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala index 91cd858611..76c63fb7fb 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala @@ -70,7 +70,8 @@ object ProtocolGenerator { private[this] def fromEnum[L <: LA, F[_], A]( clsName: String, schema: Tracker[Schema[A]], - dtoPackage: List[String] + dtoPackage: List[String], + components: Tracker[Option[Components]] )(implicit P: ProtocolTerms[L, F], F: FrameworkTerms[L, F], @@ -135,7 +136,7 @@ object ProtocolGenerator { for { enum <- extractEnum(schema.map(wrapEnumSchema)) customTpeName <- SwaggerUtil.customTypeName(schema) - tpe <- SwaggerUtil.determineTypeName(schema, Tracker.cloneHistory(schema, customTpeName)) + tpe <- SwaggerUtil.determineTypeName(schema, Tracker.cloneHistory(schema, customTpeName), components) fullType <- selectType(NonEmptyList.ofInitLast(dtoPackage, clsName)) res <- enum.traverse(validProg(_, tpe, fullType)) } yield res @@ -165,7 +166,8 @@ object ProtocolGenerator { definitions: List[(String, Tracker[Schema[_]])], dtoPackage: List[String], supportPackage: List[String], - defaultPropertyRequirement: PropertyRequirement + defaultPropertyRequirement: PropertyRequirement, + components: Tracker[Option[Components]] )(implicit F: FrameworkTerms[L, F], P: ProtocolTerms[L, F], @@ -188,7 +190,7 @@ object ProtocolGenerator { for { parents <- hierarchy.model .refine[F[List[SuperClass[L]]]] { case c: ComposedSchema => c }( - extractParents(_, definitions, concreteTypes, dtoPackage, supportPackage, defaultPropertyRequirement) + extractParents(_, definitions, concreteTypes, dtoPackage, supportPackage, defaultPropertyRequirement, components) ) .getOrElse(List.empty[SuperClass[L]].pure[F]) props <- extractProperties(hierarchy.model) @@ -230,13 +232,14 @@ object ProtocolGenerator { ) } - def extractParents[L <: LA, F[_]]( + private def extractParents[L <: LA, F[_]]( elem: Tracker[ComposedSchema], definitions: List[(String, Tracker[Schema[_]])], concreteTypes: List[PropMeta[L]], dtoPackage: List[String], supportPackage: List[String], - defaultPropertyRequirement: PropertyRequirement + defaultPropertyRequirement: PropertyRequirement, + components: Tracker[Option[Components]] )(implicit F: FrameworkTerms[L, F], P: ProtocolTerms[L, F], @@ -281,7 +284,8 @@ object ProtocolGenerator { definitions, dtoPackage, supportPackage, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) interfacesCls = interfaces.flatMap(_.downField("$ref", _.get$ref).unwrapTracker.map(_.split("/").last)) tpe <- parseTypeName(clsName) @@ -314,7 +318,8 @@ object ProtocolGenerator { definitions: List[(String, Tracker[Schema[_]])], dtoPackage: List[String], supportPackage: List[String], - defaultPropertyRequirement: PropertyRequirement + defaultPropertyRequirement: PropertyRequirement, + components: Tracker[Option[Components]] )(implicit F: FrameworkTerms[L, F], P: ProtocolTerms[L, F], @@ -337,7 +342,8 @@ object ProtocolGenerator { definitions, dtoPackage, supportPackage, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) defn <- renderDTOClass(clsName.last, supportPackage, params, parents) encoder <- encodeModel(clsName.last, dtoPackage, params, parents) @@ -381,7 +387,8 @@ object ProtocolGenerator { definitions: List[(String, Tracker[Schema[_]])], dtoPackage: List[String], supportPackage: List[String], - defaultPropertyRequirement: PropertyRequirement + defaultPropertyRequirement: PropertyRequirement, + components: Tracker[Option[Components]] )(implicit F: FrameworkTerms[L, F], P: ProtocolTerms[L, F], @@ -398,11 +405,12 @@ object ProtocolGenerator { nestedClassName <- formatTypeName(name).map(formattedName => getClsName(name).append(formattedName)) defn <- schema .refine[F[Option[Either[String, NestedProtocolElems[L]]]]] { case x: ObjectSchema => x }(_ => - fromModel(nestedClassName, schema, List.empty, concreteTypes, definitions, dtoPackage, supportPackage, defaultPropertyRequirement).map(Option(_)) + fromModel(nestedClassName, schema, List.empty, concreteTypes, definitions, dtoPackage, supportPackage, defaultPropertyRequirement, components) + .map(Option(_)) ) .orRefine { case o: ComposedSchema => o }(o => for { - parents <- extractParents(o, definitions, concreteTypes, dtoPackage, supportPackage, defaultPropertyRequirement) + parents <- extractParents(o, definitions, concreteTypes, dtoPackage, supportPackage, defaultPropertyRequirement, components) maybeClassDefinition <- fromModel( nestedClassName, schema, @@ -411,13 +419,14 @@ object ProtocolGenerator { definitions, dtoPackage, supportPackage, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) } yield Option(maybeClassDefinition) ) .orRefine { case a: ArraySchema => a }(_.downField("items", _.getItems()).indexedCosequence.flatTraverse(processProperty(name, _))) .orRefine { case s: StringSchema if Option(s.getEnum).map(_.asScala).exists(_.nonEmpty) => s }(s => - fromEnum(nestedClassName.last, s, dtoPackage).map(Option(_)) + fromEnum(nestedClassName.last, s, dtoPackage, components).map(Option(_)) ) .getOrElse(Option.empty[Either[String, NestedProtocolElems[L]]].pure[F]) } yield defn @@ -428,7 +437,7 @@ object ProtocolGenerator { typeName <- formatTypeName(name).map(formattedName => getClsName(name).append(formattedName)) tpe <- selectType(typeName) maybeNestedDefinition <- processProperty(name, schema) - resolvedType <- SwaggerUtil.propMetaWithName(tpe, schema) + resolvedType <- SwaggerUtil.propMetaWithName(tpe, schema, components) customType <- SwaggerUtil.customTypeName(schema) propertyRequirement = getPropertyRequirement(schema, requiredFields.contains(name), defaultPropertyRequirement) defValue <- defaultValue(typeName, schema, propertyRequirement, definitions) @@ -514,7 +523,7 @@ object ProtocolGenerator { } yield newParams } - def modelTypeAlias[L <: LA, F[_]](clsName: String, abstractModel: Tracker[Schema[_]])(implicit + def modelTypeAlias[L <: LA, F[_]](clsName: String, abstractModel: Tracker[Schema[_]], components: Tracker[Option[Components]])(implicit Fw: FrameworkTerms[L, F], Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], @@ -536,7 +545,7 @@ object ProtocolGenerator { tpe <- model.fold[F[L#Type]](objectType(None)) { m => for { tpeName <- SwaggerUtil.customTypeName[L, F, Tracker[ObjectSchema]](m) - res <- SwaggerUtil.determineTypeName[L, F](m, Tracker.cloneHistory(m, tpeName)) + res <- SwaggerUtil.determineTypeName[L, F](m, Tracker.cloneHistory(m, tpeName), components) } yield res } res <- typeAlias[L, F](clsName, tpe) @@ -660,18 +669,19 @@ object ProtocolGenerator { import P._ import Sc._ - val definitions = swagger.downField("components", _.getComponents()).flatDownField("schemas", _.getSchemas()).indexedCosequence + val components = swagger.downField("components", _.getComponents()) + val definitions = components.flatDownField("schemas", _.getSchemas()).indexedCosequence Sw.log.function("ProtocolGenerator.fromSwagger")(for { (hierarchies, definitionsWithoutPoly) <- groupHierarchies(definitions) concreteTypes <- SwaggerUtil.extractConcreteTypes[L, F](definitions.value) - polyADTs <- hierarchies.traverse(fromPoly(_, concreteTypes, definitions.value, dtoPackage, supportPackage.toList, defaultPropertyRequirement)) + polyADTs <- hierarchies.traverse(fromPoly(_, concreteTypes, definitions.value, dtoPackage, supportPackage.toList, defaultPropertyRequirement, components)) elems <- definitionsWithoutPoly.traverse { case (clsName, model) => model .refine { case m: StringSchema => m }(m => for { formattedClsName <- formatTypeName(clsName) - enum <- fromEnum(formattedClsName, m, dtoPackage) + enum <- fromEnum(formattedClsName, m, dtoPackage, components) model <- fromModel( NonEmptyList.of(formattedClsName), m, @@ -680,15 +690,16 @@ object ProtocolGenerator { definitions.value, dtoPackage, supportPackage.toList, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) - alias <- modelTypeAlias(clsName, m) + alias <- modelTypeAlias(clsName, m, components) } yield enum.orElse(model).getOrElse(alias) ) .orRefine { case c: ComposedSchema => c }(comp => for { formattedClsName <- formatTypeName(clsName) - parents <- extractParents(comp, definitions.value, concreteTypes, dtoPackage, supportPackage.toList, defaultPropertyRequirement) + parents <- extractParents(comp, definitions.value, concreteTypes, dtoPackage, supportPackage.toList, defaultPropertyRequirement, components) model <- fromModel( NonEmptyList.of(formattedClsName), comp, @@ -697,9 +708,10 @@ object ProtocolGenerator { definitions.value, dtoPackage, supportPackage.toList, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) - alias <- modelTypeAlias(formattedClsName, comp) + alias <- modelTypeAlias(formattedClsName, comp, components) } yield model.getOrElse(alias) ) .orRefine { case a: ArraySchema => a }(arr => @@ -711,7 +723,7 @@ object ProtocolGenerator { .orRefine { case o: ObjectSchema => o }(m => for { formattedClsName <- formatTypeName(clsName) - enum <- fromEnum(formattedClsName, m, dtoPackage) + enum <- fromEnum(formattedClsName, m, dtoPackage, components) model <- fromModel( NonEmptyList.of(formattedClsName), m, @@ -720,15 +732,16 @@ object ProtocolGenerator { definitions.value, dtoPackage, supportPackage.toList, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) - alias <- modelTypeAlias(formattedClsName, m) + alias <- modelTypeAlias(formattedClsName, m, components) } yield enum.orElse(model).getOrElse(alias) ) .orRefine { case x: IntegerSchema => x }(x => for { formattedClsName <- formatTypeName(clsName) - enum <- fromEnum(formattedClsName, x, dtoPackage) + enum <- fromEnum(formattedClsName, x, dtoPackage, components) model <- fromModel( NonEmptyList.of(formattedClsName), x, @@ -737,10 +750,11 @@ object ProtocolGenerator { definitions.value, dtoPackage, supportPackage.toList, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) customTypeName <- SwaggerUtil.customTypeName(x) - tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName)) + tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName), components) alias <- typeAlias[L, F](formattedClsName, tpe) } yield enum.orElse(model).getOrElse(alias) ) @@ -748,7 +762,7 @@ object ProtocolGenerator { for { formattedClsName <- formatTypeName(clsName) customTypeName <- SwaggerUtil.customTypeName(x) - tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName)) + tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName), components) res <- typeAlias[L, F](formattedClsName, tpe) } yield res ) @@ -816,6 +830,5 @@ object ProtocolGenerator { .orRefine { case p: StringSchema => p }(p => Default(p).extract[String].fold(empty)(litString(_).map(Some(_)))) .getOrElse(empty) } - } } diff --git a/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala index ac8006254f..603b5ed49e 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala @@ -199,10 +199,18 @@ class SwaggerGenerator[L <: LA] extends SwaggerTerms[L, Target] { .raiseErrorIfEmpty("Name not specified") .map(_.unwrapTracker) - override def getBodyParameterSchema(parameter: Tracker[Parameter]) = - parameter - .downField("schema", _.getSchema()) - .raiseErrorIfEmpty("Schema not specified") + override def getParameterSchema(parameter: Tracker[Parameter], components: Tracker[Option[Components]]) = + for { + schema <- parameter + .downField("schema", _.getSchema()) + .raiseErrorIfEmpty("Schema not specified") + dereferenced <- schema + .downField("$ref", _.get$ref()) + .indexedDistribute + .fold[Target[Tracker[SchemaProjection]]](Target.pure(schema.map(SchemaLiteral)))(ref => + dereferenceSchema(ref, components).map(_.map(schema => SchemaRef(SchemaLiteral(schema), ref.unwrapTracker))) + ) + } yield dereferenced override def getHeaderParameterType(parameter: Tracker[Parameter]) = parameterSchemaType(parameter) diff --git a/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala b/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala index 99f80730af..2496364f65 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala @@ -13,6 +13,10 @@ import dev.guardrail.core.{ Mappish, Tracker } import dev.guardrail.languages.LA import dev.guardrail.terms.protocol._ +sealed trait SchemaProjection +case class SchemaLiteral(schema: Schema[_]) extends SchemaProjection +case class SchemaRef(schema: SchemaLiteral, ref: String) extends SchemaProjection + abstract class SwaggerLogAdapter[F[_]] { def schemaToString(value: Schema[_]): String = " " + value.toString().linesIterator.filterNot(_.contains(": null")).mkString("\n ") def function[A](name: String): F[A] => F[A] @@ -41,7 +45,7 @@ abstract class SwaggerTerms[L <: LA, F[_]] { def extractMutualTLSSecurityScheme(schemeName: String, securityScheme: Tracker[SwSecurityScheme], tpe: Option[L#Type]): F[MutualTLSSecurityScheme[L]] def getClassName(operation: Tracker[Operation], vendorPrefixes: List[String], tagBehaviour: Context.TagsBehaviour): F[List[String]] def getParameterName(parameter: Tracker[Parameter]): F[String] - def getBodyParameterSchema(parameter: Tracker[Parameter]): F[Tracker[Schema[_]]] + def getParameterSchema(parameter: Tracker[Parameter], components: Tracker[Option[Components]]): F[Tracker[SchemaProjection]] def getHeaderParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] def getPathParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] def getQueryParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] From b4c2673266e84787eb69dba69d54798924623be1 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 19:34:27 -0800 Subject: [PATCH 12/21] Adding Vector type matcher to akka-http server generator --- .../generators/scala/akkaHttp/AkkaHttpServerGenerator.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/scala-akka-http/src/main/scala/dev/guardrail/generators/scala/akkaHttp/AkkaHttpServerGenerator.scala b/modules/scala-akka-http/src/main/scala/dev/guardrail/generators/scala/akkaHttp/AkkaHttpServerGenerator.scala index 9d758c04ee..0cb0217378 100644 --- a/modules/scala-akka-http/src/main/scala/dev/guardrail/generators/scala/akkaHttp/AkkaHttpServerGenerator.scala +++ b/modules/scala-akka-http/src/main/scala/dev/guardrail/generators/scala/akkaHttp/AkkaHttpServerGenerator.scala @@ -792,6 +792,10 @@ class AkkaHttpServerGenerator private (akkaHttpVersion: AkkaHttpVersion, modelGe val (realType, getFunc, transformResponse): (Type, Term.Name, (Term => Term)) = param.argType match { case t"Iterable[$x]" => (x, q"getAll", (x: Term) => q"${x}.map(Option.apply)") case t"Option[Iterable[$x]]" => (x, q"getAll", (x: Term) => q"${x}.map(Option.apply)") + case t"List[$x]" => (x, q"getAll", (x: Term) => q"${x}.map(Option.apply)") + case t"Option[List[$x]]" => (x, q"getAll", (x: Term) => q"${x}.map(Option.apply)") + case t"Vector[$x]" => (x, q"getAll", (x: Term) => q"${x}.map(xs => Option(xs.toVector))") + case t"Option[Vector[$x]]" => (x, q"getAll", (x: Term) => q"${x}.map(xs => Option(xs.toVector))") case t"Option[$x]" => (x, q"get", (x: Term) => x) case x => (x, q"get", (x: Term) => x) } From 5cee0d118bde50d50175b317dc9ae719c4c2a543 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 19:34:49 -0800 Subject: [PATCH 13/21] Using lift instead of hardcoding toList --- .../generators/scala/http4s/Http4sServerGenerator.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala b/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala index f4bfef2588..2f53f545c4 100644 --- a/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala +++ b/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala @@ -555,15 +555,15 @@ class Http4sServerGenerator private (version: Http4sVersion) extends ServerTerms ) }, arg => { - case t"String" => _ => Target.pure(Param(None, None, q"urlForm.values.get(${arg.argName.toLit}).map(_.toList)")) + case t"String" => lift => Target.pure(Param(None, None, q"urlForm.values.get(${arg.argName.toLit}).map(${lift(q"_")})")) case tpe => - _ => + lift => Target.pure( Param( None, Some( ( - q"urlForm.values.get(${arg.argName.toLit}).flatMap(_.toList).map(Json.fromString(_).as[$tpe]).sequence.sequence", + q"${lift(q"urlForm.values.get(${arg.argName.toLit})")}.flatMap(${lift(q"_")}).map(Json.fromString(_).as[$tpe]).sequence.sequence", p"Right(${Pat.Var(arg.paramName)})" ) ), From 255fa1ea3139ffc7ae27bf50d8ee8385ea0c441c Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Fri, 18 Feb 2022 23:40:22 -0800 Subject: [PATCH 14/21] Creating and threading through reified raw types for core.Resolved --- .../scala/dev/guardrail/SwaggerUtil.scala | 50 +++++++++++-------- .../dev/guardrail/core/ResolvedType.scala | 37 +++++++------- .../generators/LanguageParameter.scala | 17 +++++-- .../scala/dev/guardrail/terms/Responses.scala | 2 +- .../java/jackson/JacksonGenerator.scala | 4 +- .../scala/circe/CirceProtocolGenerator.scala | 8 +-- 6 files changed, 65 insertions(+), 53 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala index 340d1ca1a1..eda9a8bdff 100644 --- a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala +++ b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala @@ -5,7 +5,7 @@ import io.swagger.v3.oas.models.media._ import io.swagger.v3.oas.models.parameters.RequestBody import io.swagger.v3.oas.models.security.{ SecurityScheme => SwSecurityScheme } import cats.syntax.all._ -import dev.guardrail.core.Tracker +import dev.guardrail.core.{ ReifiedRawType, Tracker, VectorRawType } import dev.guardrail.core.implicits._ import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, SchemaLiteral, SchemaProjection, SchemaRef, SecurityScheme, SwaggerTerms } import dev.guardrail.terms.framework.FrameworkTerms @@ -54,7 +54,7 @@ object SwaggerUtil { formattedClsName <- formatTypeName(clsName) typeName <- pureTypeName(formattedClsName) widenedTypeName <- widenTypeName(typeName) - } yield (clsName, core.Resolved[L](widenedTypeName, None, None, None, None): core.ResolvedType[L]) + } yield (clsName, core.Resolved[L](widenedTypeName, None, None, ReifiedRawType.unsafeEmpty): core.ResolvedType[L]) ) .orRefine { case comp: ComposedSchema => comp }(comp => for { @@ -68,7 +68,7 @@ object SwaggerUtil { .flatMap(_.downField("$ref", _.get$ref).indexedDistribute) .map(_.unwrapTracker.split("/").last) parentTerm <- parentSimpleRef.traverse(n => pureTermName(n)) - resolvedType = core.Resolved[L](widenedTypeName, parentTerm, None, None, None): core.ResolvedType[L] + resolvedType = core.Resolved[L](widenedTypeName, parentTerm, None, ReifiedRawType.unsafeEmpty): core.ResolvedType[L] } yield (clsName, resolvedType) ) .getOrElse( @@ -78,8 +78,8 @@ object SwaggerUtil { ) } result <- core.ResolvedType.resolveReferences[L, F](entries) - } yield result.map { case (clsName, core.Resolved(tpe, _, _, _, _)) => - PropMeta[L](clsName, tpe) + } yield result.map { case (clsName, core.Resolved(tpe, _, _, _)) => + PropMeta[L](clsName, tpe) // TODO: We're losing ReifiedRawType here. Perhaps maintain through PropMeta? } } @@ -190,10 +190,10 @@ object SwaggerUtil { ): F[core.ResolvedType[L]] = propMetaImpl(property, components)( _.refine[core.ResolvedType[L]] { case schema: ObjectSchema if Option(schema.getProperties).exists(p => !p.isEmpty) => schema }(_ => - core.Resolved[L](tpe, None, None, None, None) - ).orRefine { case c: ComposedSchema => c }(_ => core.Resolved[L](tpe, None, None, None, None)) + 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, None, None) + core.Resolved[L](tpe, None, None, ReifiedRawType.unsafeEmpty) ) .map(_.pure[F]) ) @@ -210,7 +210,7 @@ object SwaggerUtil { for { customTpeName <- customTypeName(a) tpe <- determineTypeName[L, F](a, Tracker.cloneHistory(a, customTpeName), components) - } yield core.Resolved[L](tpe, None, None, rawType.unwrapTracker, rawFormat.unwrapTracker) + } yield core.Resolved[L](tpe, None, None, ReifiedRawType.of(rawType.unwrapTracker, rawFormat.unwrapTracker)) } partial @@ -225,7 +225,9 @@ object SwaggerUtil { .orRefine { case f: FileSchema => f }(buildResolveNoDefault) .orRefine { case b: BinarySchema => b }(buildResolveNoDefault) .orRefine { case u: UUIDSchema => u }(buildResolveNoDefault) - .orRefineFallback(x => fallbackPropertyTypeHandler(x).map(core.Resolved[L](_, None, None, None, None))) // This may need to be rawType=string? + .orRefineFallback(x => + fallbackPropertyTypeHandler(x).map(core.Resolved[L](_, None, None, ReifiedRawType.unsafeEmpty)) + ) // This may need to be rawType=string? } private def enrichWithDefault[L <: LA, F[_]](schema: Tracker[Schema[_]])(implicit @@ -268,19 +270,23 @@ object SwaggerUtil { customTpeName <- customTypeName(o) customTpe <- customTpeName.flatTraverse(x => liftCustomType[L, F](Tracker.cloneHistory(o, x))) fallback <- objectType(None) - } yield core.Resolved[L](customTpe.getOrElse(fallback), None, None, None, None) + } yield core.Resolved[L](customTpe.getOrElse(fallback), None, None, ReifiedRawType.unsafeEmpty) ) .orRefine { case arr: ArraySchema => arr }(arr => for { items <- getItems(arr) - meta <- propMetaImpl[L, F](items, components)(strategy) - rawType = arr.downField("type", _.getType()) - rawFormat = arr.downField("format", _.getFormat()) + dereferencedItems <- items + .downField("$ref", _.get$ref()) + .indexedDistribute + .fold[F[Tracker[Schema[_]]]](items.pure[F])(ref => dereferenceSchema(ref, components)) + meta <- propMetaImpl[L, F](items, components)(strategy) + itemsRawType = dereferencedItems.downField("type", _.getType()) + itemsRawFormat = dereferencedItems.downField("format", _.getFormat()) arrayType <- customArrayTypeName(arr).flatMap(_.flatTraverse(x => parseType(Tracker.cloneHistory(arr, x)))) res <- meta match { - case core.Resolved(inner, dep, default, _, _) => + case core.Resolved(inner, dep, default, _) => (liftVectorType(inner, arrayType), default.traverse(liftVectorTerm)) - .mapN(core.Resolved[L](_, dep, _, rawType.unwrapTracker, rawFormat.unwrapTracker)) + .mapN(core.Resolved[L](_, dep, _, VectorRawType(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) @@ -295,19 +301,19 @@ object SwaggerUtil { .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, rawType.unwrapTracker, rawFormat.unwrapTracker)) + objectType(None).map(core.Resolved[L](_, 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, rawType.unwrapTracker, rawFormat.unwrapTracker)) + .map(core.Resolved[L](_, None, None, ReifiedRawType.ofMap(ReifiedRawType.of(rawType.unwrapTracker, rawFormat.unwrapTracker)))) } mapType <- customMapTypeName(map).flatMap(_.flatTraverse(x => parseType(Tracker.cloneHistory(map, x)))) res <- rec match { - case core.Resolved(inner, dep, _, tpe, fmt) => liftMapType(inner, mapType).map(core.Resolved[L](_, dep, None, tpe, fmt)) - 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 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) } } yield res } diff --git a/modules/core/src/main/scala/dev/guardrail/core/ResolvedType.scala b/modules/core/src/main/scala/dev/guardrail/core/ResolvedType.scala index 15a97a0533..9733c87da9 100644 --- a/modules/core/src/main/scala/dev/guardrail/core/ResolvedType.scala +++ b/modules/core/src/main/scala/dev/guardrail/core/ResolvedType.scala @@ -20,12 +20,11 @@ case class VectorRawType(items: ReifiedRawType) ex 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: Option[String], rawFormat: Option[String]) - extends ResolvedType[L] -sealed trait LazyResolvedType[L <: LA] extends ResolvedType[L] -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] +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] +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[_]]( @@ -80,20 +79,20 @@ object ResolvedType { import Cl._ import Sw._ log.debug(s"value: ${value} in ${protocolElems.length} protocol elements") >> (value match { - case x @ Resolved(_, _, _, _, _) => x.pure[F] + 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, None, None).pure[F] + Resolved[L](tpe, None, None, ReifiedRawType.unsafeEmpty).pure[F] case ClassDefinition(name, _, fullType, cls, _, _) => - Resolved[L](fullType, None, None, None, None).pure[F] + Resolved[L](fullType, None, None, ReifiedRawType.unsafeEmpty).pure[F] case EnumDefinition(name, _, fullType, _, cls, _) => - Resolved[L](fullType, None, None, Some("string"), None).pure[F] + Resolved[L](fullType, None, None, ReifiedRawType.of(Some("string"), None)).pure[F] case ADT(_, _, fullType, _, _) => - Resolved[L](fullType, None, None, None, None).pure[F] + Resolved[L](fullType, None, None, ReifiedRawType.unsafeEmpty).pure[F] } } yield resolved case DeferredArray(name, containerTpe) => @@ -102,13 +101,13 @@ object ResolvedType { resolved <- resolveType(formattedName, protocolElems) .flatMap { case RandomType(name, tpe) => - liftVectorType(tpe, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftVectorType(tpe, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty))) case ClassDefinition(name, _, fullType, cls, _, _) => - liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty))) case EnumDefinition(name, _, fullType, _, cls, _) => - liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty))) case ADT(_, _, fullType, _, _) => - liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftVectorType(fullType, containerTpe).map(Resolved[L](_, None, None, VectorRawType(ReifiedRawType.unsafeEmpty))) } } yield resolved case DeferredMap(name, containerTpe) => @@ -117,13 +116,13 @@ object ResolvedType { resolved <- resolveType(formattedName, protocolElems) .flatMap { case RandomType(name, tpe) => - liftMapType(tpe, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftMapType(tpe, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty))) case ClassDefinition(_, _, fullType, _, _, _) => - liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty))) case EnumDefinition(_, _, fullType, _, _, _) => - liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty))) case ADT(_, _, fullType, _, _) => - liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, None, None)) + liftMapType(fullType, containerTpe).map(Resolved[L](_, None, None, MapRawType(ReifiedRawType.unsafeEmpty))) } } yield resolved }) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index e17246809f..2b719b163b 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -7,7 +7,7 @@ import io.swagger.v3.oas.models.Components import dev.guardrail._ import dev.guardrail.core.extract.{ Default, FileHashAlgorithm } -import dev.guardrail.core.{ ReifiedRawType, ResolvedType, Tracker } +import dev.guardrail.core.{ LiteralRawType, MapRawType, ReifiedRawType, ResolvedType, Tracker, VectorRawType } import dev.guardrail.generators.syntax._ import dev.guardrail.languages.LA import dev.guardrail.shims._ @@ -87,7 +87,7 @@ object LanguageParameter { customSchemaTypeName <- SwaggerUtil.customTypeName(schema.unwrapTracker) customTypeName = Tracker.cloneHistory(schema, customSchemaTypeName).fold(Tracker.cloneHistory(param, customParamTypeName))(_.map(Option.apply)) res <- (SwaggerUtil.determineTypeName[L, F](schema, customTypeName, components), getDefault(schema)) - .mapN(core.Resolved[L](_, None, _, Some(tpeName.unwrapTracker), fmt.unwrapTracker)) + .mapN(core.Resolved[L](_, None, _, ReifiedRawType.of(Some(tpeName.unwrapTracker), fmt.unwrapTracker))) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (res, required) @@ -126,9 +126,16 @@ object LanguageParameter { log.function(s"fromParameter")( for { - _ <- log.debug(parameter.unwrapTracker.showNotNull) - (meta, required) <- paramMeta(parameter) - core.Resolved(paramType, _, baseDefaultValue, rawType, rawFormat) <- core.ResolvedType.resolve[L, F](meta, protocolElems) + _ <- log.debug(parameter.unwrapTracker.showNotNull) + (meta, required) <- paramMeta(parameter) + core.Resolved(paramType, _, baseDefaultValue, reifiedRawType) <- core.ResolvedType.resolve[L, F](meta, protocolElems) + + (rawType, rawFormat) = reifiedRawType match { + case LiteralRawType(rawType, rawFormat) => (rawType, rawFormat) + case VectorRawType(LiteralRawType(rawType, rawFormat)) => (rawType, rawFormat) + case MapRawType(LiteralRawType(rawType, rawFormat)) => (rawType, rawFormat) + case _ => ??? // TODO: Currently highly unlikely, will need to fix. RawParameterType needs to just be replaced with ReifiedRawType + } declType <- if (!required) { diff --git a/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala b/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala index 47f0b8b946..591da9427e 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala @@ -51,7 +51,7 @@ object Responses { for { meta <- SwaggerUtil.propMeta[L, F](prop) resolved <- core.ResolvedType.resolve[L, F](meta, protocolElems) - core.Resolved(baseType, _, baseDefaultValue, _, _) = resolved + core.Resolved(baseType, _, baseDefaultValue, _) = resolved // TODO: ReifiedRawType is just dropped, should it be considered? } yield (contentType, baseType, baseDefaultValue) } headers <- resp.downField("headers", _.getHeaders()).unwrapTracker.value.toList.traverse { case (name, header) => diff --git a/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala b/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala index ff06f210f5..6e87cc2516 100644 --- a/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala +++ b/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala @@ -870,7 +870,7 @@ class JacksonGenerator private (implicit Cl: CollectionsLibTerms[JavaLanguage, T val dataRedaction = DataRedaction(property).getOrElse(DataVisible) for { tpeClassDep <- meta match { - case core.Resolved(declType, classDep, _, _, _) => + case core.Resolved(declType, classDep, _, _) => Target.pure((declType, classDep)) case core.Deferred(tpeName) => val tpe = concreteTypes.find(_.clsName == tpeName).map(x => Target.pure(x.tpe)).getOrElse { @@ -960,7 +960,7 @@ class JacksonGenerator private (implicit Cl: CollectionsLibTerms[JavaLanguage, T ): Target[Type] = for { result <- arr match { - case core.Resolved(tpe, dep, default, _, _) => Target.pure(tpe) + case core.Resolved(tpe, dep, default, _) => Target.pure(tpe) case core.Deferred(tpeName) => Target.fromOption(lookupTypeName(tpeName, concreteTypes), UserError(s"Unresolved reference ${tpeName}")) case core.DeferredArray(tpeName, containerTpe) => diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 29a05929bd..4f4dea3d7f 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -10,7 +10,7 @@ import scala.reflect.runtime.universe.typeTag import dev.guardrail.core import dev.guardrail.core.extract.{ DataRedaction, EmptyValueIsNull } import dev.guardrail.core.implicits._ -import dev.guardrail.core.{ DataVisible, EmptyIsEmpty, EmptyIsNull, ReifiedRawType, ResolvedType, SupportDefinition, Tracker } +import dev.guardrail.core.{ DataVisible, EmptyIsEmpty, EmptyIsNull, LiteralRawType, ReifiedRawType, ResolvedType, SupportDefinition, Tracker } import dev.guardrail.generators.spi.ProtocolGeneratorLoader import dev.guardrail.generators.scala.{ CirceModelGenerator, ScalaGenerator, ScalaLanguage } import dev.guardrail.generators.RawParameterName @@ -170,10 +170,10 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator) extends dataRedaction = DataRedaction(property).getOrElse(DataVisible) (tpe, classDep) = meta match { - case core.Resolved(declType, classDep, _, Some(rawType), rawFormat) if SwaggerUtil.isFile(rawType, rawFormat) && !isCustomType => + case core.Resolved(declType, classDep, _, LiteralRawType(Some(rawType), rawFormat)) if SwaggerUtil.isFile(rawType, rawFormat) && !isCustomType => // assume that binary data are represented as a string. allow users to override. (t"String", classDep) - case core.Resolved(declType, classDep, _, _, _) => + case core.Resolved(declType, classDep, _, _) => (declType, classDep) case core.Deferred(tpeName) => val tpe = concreteTypes.find(_.clsName == tpeName).map(_.tpe).getOrElse { @@ -452,7 +452,7 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator) extends override def extractArrayType(arr: core.ResolvedType[ScalaLanguage], concreteTypes: List[PropMeta[ScalaLanguage]]) = for { result <- arr match { - case core.Resolved(tpe, dep, default, _, _) => Target.pure(tpe) + case core.Resolved(tpe, dep, default, _) => Target.pure(tpe) case core.Deferred(tpeName) => Target.fromOption(lookupTypeName(tpeName, concreteTypes)(identity), UserError(s"Unresolved reference ${tpeName}")) case core.DeferredArray(tpeName, containerTpe) => From bc93a4829be2326837608cab70cb74daa7e72f99 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 00:13:06 -0800 Subject: [PATCH 15/21] Hamfisted attempt at just shoving ReifiedRawType in there --- .../scala/dev/guardrail/SwaggerUtil.scala | 84 ++++++++++++------- .../generators/ClientGenerator.scala | 2 +- .../generators/LanguageParameter.scala | 32 +++---- .../generators/ProtocolGenerator.scala | 27 +++--- .../generators/ServerGenerator.scala | 2 +- .../generators/SwaggerGenerator.scala | 26 ------ .../dev/guardrail/terms/LanguageTerms.scala | 3 +- .../scala/dev/guardrail/terms/Responses.scala | 10 ++- .../dev/guardrail/terms/SwaggerTerms.scala | 6 -- .../java/jackson/JacksonGenerator.scala | 20 ++--- .../scala/circe/CirceProtocolGenerator.scala | 20 ++--- 11 files changed, 112 insertions(+), 120 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala index eda9a8bdff..c639545da5 100644 --- a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala +++ b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala @@ -5,7 +5,7 @@ import io.swagger.v3.oas.models.media._ import io.swagger.v3.oas.models.parameters.RequestBody import io.swagger.v3.oas.models.security.{ SecurityScheme => SwSecurityScheme } import cats.syntax.all._ -import dev.guardrail.core.{ ReifiedRawType, Tracker, VectorRawType } +import dev.guardrail.core.{ ReifiedRawType, Tracker } import dev.guardrail.core.implicits._ import dev.guardrail.terms.{ CollectionsLibTerms, LanguageTerms, SchemaLiteral, SchemaProjection, SchemaRef, SecurityScheme, SwaggerTerms } import dev.guardrail.terms.framework.FrameworkTerms @@ -38,12 +38,14 @@ object SwaggerUtil { } def modelMetaType[L <: LA, F[_]]( - model: Tracker[Schema[_]] + model: Tracker[Schema[_]], + components: Tracker[Option[Components]] )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] = - propMetaImpl[L, F](model, Tracker.cloneHistory(model, None))(Left(_)) + propMetaImpl[L, F](model, components)(Left(_)) def extractConcreteTypes[L <: LA, F[_]]( - definitions: List[(String, Tracker[Schema[_]])] + definitions: List[(String, Tracker[Schema[_]])], + components: Tracker[Option[Components]] )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], F: FrameworkTerms[L, F]): F[List[PropMeta[L]]] = { import Sc._ for { @@ -73,7 +75,7 @@ object SwaggerUtil { ) .getOrElse( for { - resolved <- modelMetaType[L, F](schema) + resolved <- modelMetaType[L, F](schema, components) } yield (clsName, resolved) ) } @@ -88,7 +90,7 @@ object SwaggerUtil { rawSchema: Tracker[Schema[_]], customType: Tracker[Option[String]], components: Tracker[Option[Components]] - )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[L#Type] = + )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[(L#Type, ReifiedRawType)] = Sw.log.function(s"determineTypeName(${rawSchema.unwrapTracker}, ${customType.unwrapTracker})") { import Sc._ import Cl._ @@ -112,15 +114,28 @@ object SwaggerUtil { .fold[F[Tracker[SchemaProjection]]](rawSchema.map(SchemaLiteral(_): SchemaProjection).pure[F]) { ref => Sw.dereferenceSchema(ref, components).map(_.map(schema => SchemaRef(SchemaLiteral(schema), ref.unwrapTracker))) } - customTpe <- customType.indexedDistribute.flatTraverse(x => liftCustomType[L, F](x)) - result <- customTpe.fold { - def const(value: F[L#Type]): Tracker[Schema[_]] => F[L#Type] = { _ => value } - def extractFormat(func: Option[String] => F[L#Type]): Tracker[Schema[_]] => F[L#Type] = { schema => - val fmt = schema.downField("format", _.getFormat()).unwrapTracker - func(fmt) + (renderType, reifiedRawType) <- { + def extractFormat(func: Option[String] => F[L#Type]): Tracker[Schema[_]] => F[(L#Type, ReifiedRawType)] = { schema => + val rawType = schema.downField("type", _.getType()) + val rawFormat = schema.downField("format", _.getFormat()) + for { + rendered <- func(rawFormat.unwrapTracker) + } yield (rendered, ReifiedRawType.of(rawType.unwrapTracker, rawFormat.unwrapTracker)) } + def const(value: F[L#Type]): Tracker[Schema[_]] => F[(L#Type, ReifiedRawType)] = extractFormat(_ => value) schemaProjection - .refine[F[L#Type]] { case SchemaRef(_, ref) => ref }(ref => fallbackType(ref.unwrapTracker.split("/").lastOption, None)) + .refine[F[(L#Type, ReifiedRawType)]] { case SchemaRef(_, ref) => ref }(ref => + for { + refName <- fallbackType(ref.unwrapTracker.split("/").lastOption, None) + underlyingSchema <- Sw.dereferenceSchema(ref, components) + } yield ( + refName, + ReifiedRawType.of( + underlyingSchema.downField("type", _.getType()).unwrapTracker, + underlyingSchema.downField("format", _.getFormat()).unwrapTracker + ) + ) + ) .orRefine { case SchemaLiteral(x: ObjectSchema) if Option(x.getEnum).map(_.asScala).exists(_.nonEmpty) => x }( extractFormat(fmt => stringType(None).map(log(fmt, _))) ) @@ -143,20 +158,34 @@ object SwaggerUtil { .orRefine { case SchemaLiteral(x: ArraySchema) => x }(schema => schema .downField("items", _.getItems()) - .cotraverse(determineTypeName(_, Tracker.cloneHistory(schema, None), components).flatMap(liftVectorType(_, None))) - .getOrElse(arrayType(None)) + .cotraverse(itemsSchema => + for { + (found, innerRawType) <- determineTypeName(itemsSchema, Tracker.cloneHistory(schema, None), components) + lifted <- liftVectorType(found, None) + } yield (lifted, ReifiedRawType.ofVector(innerRawType): ReifiedRawType) + ) + .getOrElse(arrayType(None).map((_, ReifiedRawType.unsafeEmpty))) ) .orRefine { case SchemaLiteral(x: FileSchema) => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) .orRefine { case SchemaLiteral(x: BinarySchema) => x }(extractFormat(fmt => fileType(None).map(log(fmt, _)))) .orRefine { case SchemaLiteral(x: ObjectSchema) => x }(extractFormat(fmt => objectType(fmt).map(log(fmt, _)))) .orRefine { case SchemaLiteral(x: MapSchema) => x }(extractFormat(fmt => objectType(fmt).map(log(fmt, _)))) - .orRefineFallback { schema => - println(schema) - ??? + .orRefineFallback { schemaProjection => + val schema = schemaProjection.map { + case SchemaLiteral(x) => x + case SchemaRef(SchemaLiteral(x), _) => x + } + val rawType = schema.downField("type", _.getType()) + val rawFormat = schema.downField("format", _.getFormat()) + for { + declType <- fallbackType(rawType.unwrapTracker, rawFormat.unwrapTracker) + } yield (declType, ReifiedRawType.of(rawType.unwrapTracker, rawFormat.unwrapTracker)) } - }(_.pure[F]) + } + customTpe <- customType.indexedDistribute.flatTraverse(x => liftCustomType[L, F](x)) + result = customTpe.getOrElse(renderType) _ <- Sw.log.debug(s"Returning ${result}") - } yield result + } yield (result, reifiedRawType) } def isFile(typeName: String, format: Option[String]): Boolean = @@ -167,12 +196,12 @@ object SwaggerUtil { case _ => false } - def propMeta[L <: LA, F[_]](property: Tracker[Schema[_]])(implicit + def propMeta[L <: LA, F[_]](property: Tracker[Schema[_]], components: Tracker[Option[Components]])(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F] - ): F[core.ResolvedType[L]] = propMetaImpl(property, Tracker.cloneHistory(property, None))(Left(_)) + ): F[core.ResolvedType[L]] = propMetaImpl(property, components)(Left(_)) private[this] def liftCustomType[L <: LA, F[_]](s: Tracker[String])(implicit Sc: LanguageTerms[L, F]): F[Option[L#Type]] = { import Sc._ @@ -204,13 +233,10 @@ object SwaggerUtil { )(implicit Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], Sw: SwaggerTerms[L, F], Fw: FrameworkTerms[L, F]): F[core.ResolvedType[L]] = { import Sw._ def buildResolveNoDefault[A <: Schema[_]]: Tracker[A] => F[core.ResolvedType[L]] = { a => - val rawType = a.downField("type", _.getType()) - val rawFormat = a.downField("format", _.getFormat()) - for { - customTpeName <- customTypeName(a) - tpe <- determineTypeName[L, F](a, Tracker.cloneHistory(a, customTpeName), components) - } yield core.Resolved[L](tpe, None, None, ReifiedRawType.of(rawType.unwrapTracker, rawFormat.unwrapTracker)) + customTpeName <- customTypeName(a) + (tpe, rawType) <- determineTypeName[L, F](a, Tracker.cloneHistory(a, customTpeName), components) + } yield core.Resolved[L](tpe, None, None, rawType) } partial @@ -286,7 +312,7 @@ object SwaggerUtil { res <- meta match { case core.Resolved(inner, dep, default, _) => (liftVectorType(inner, arrayType), default.traverse(liftVectorTerm)) - .mapN(core.Resolved[L](_, dep, _, VectorRawType(ReifiedRawType.of(itemsRawType.unwrapTracker, itemsRawFormat.unwrapTracker)))) + .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) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala index 80c1bb9ca1..3691beeff7 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala @@ -53,7 +53,7 @@ object ClientGenerator { responseClientPair <- routes.traverse { case route @ RouteMeta(path, method, operation, securityRequirements) => for { operationId <- getOperationId(operation) - responses <- Responses.getResponses[L, F](operationId, operation, protocolElems) + responses <- Responses.getResponses[L, F](operationId, operation, protocolElems, components) responseClsName <- formatTypeName(operationId, Some("Response")) responseDefinitions <- generateResponseDefinitions(responseClsName, responses, protocolElems) parameters <- route.getParameters[L, F](components, protocolElems) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala index 2b719b163b..fa220fad8a 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -7,7 +7,7 @@ import io.swagger.v3.oas.models.Components import dev.guardrail._ import dev.guardrail.core.extract.{ Default, FileHashAlgorithm } -import dev.guardrail.core.{ LiteralRawType, MapRawType, ReifiedRawType, ResolvedType, Tracker, VectorRawType } +import dev.guardrail.core.{ ReifiedRawType, ResolvedType, Tracker } import dev.guardrail.generators.syntax._ import dev.guardrail.languages.LA import dev.guardrail.shims._ @@ -75,19 +75,18 @@ object LanguageParameter { .orRefineFallback(_ => Option.empty[L#Term].pure[F]) } yield res - def resolveParam(param: Tracker[Parameter], typeFetcher: Tracker[Parameter] => F[Tracker[String]]): F[(ResolvedType[L], Boolean)] = + def resolveParam(param: Tracker[Parameter]): F[(ResolvedType[L], Boolean)] = for { - tpeName <- typeFetcher(param) schema <- getParameterSchema(param, components).map(_.map { case SchemaLiteral(schema) => schema case SchemaRef(SchemaLiteral(schema), _) => schema }) - fmt = schema.downField("format", _.getFormat) customParamTypeName <- SwaggerUtil.customTypeName(param) customSchemaTypeName <- SwaggerUtil.customTypeName(schema.unwrapTracker) customTypeName = Tracker.cloneHistory(schema, customSchemaTypeName).fold(Tracker.cloneHistory(param, customParamTypeName))(_.map(Option.apply)) - res <- (SwaggerUtil.determineTypeName[L, F](schema, customTypeName, components), getDefault(schema)) - .mapN(core.Resolved[L](_, None, _, ReifiedRawType.of(Some(tpeName.unwrapTracker), fmt.unwrapTracker))) + (declType, rawType) <- SwaggerUtil.determineTypeName[L, F](schema, customTypeName, components) + defaultValue <- getDefault(schema) + res = core.Resolved[L](declType, None, defaultValue, rawType) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (res, required) @@ -112,15 +111,15 @@ object LanguageParameter { case SchemaLiteral(schema) => schema case SchemaRef(SchemaLiteral(schema), _) => schema }) - resolved <- SwaggerUtil.modelMetaType[L, F](schema) + resolved <- SwaggerUtil.modelMetaType[L, F](schema, components) required = param.downField("required", _.getRequired()).unwrapTracker.getOrElse(false) } yield (resolved, required) ) - .orRefine { case x: Parameter if x.isInHeader => x }(x => resolveParam(x, getHeaderParameterType)) - .orRefine { case x: Parameter if x.isInPath => x }(x => resolveParam(x, getPathParameterType)) - .orRefine { case x: Parameter if x.isInQuery => x }(x => resolveParam(x, getQueryParameterType)) - .orRefine { case x: Parameter if x.isInCookies => x }(x => resolveParam(x, getCookieParameterType)) - .orRefine { case x: Parameter if x.isInFormData => x }(x => resolveParam(x, getFormParameterType)) + .orRefine { case x: Parameter if x.isInHeader => x }(resolveParam) + .orRefine { case x: Parameter if x.isInPath => x }(resolveParam) + .orRefine { case x: Parameter if x.isInQuery => x }(resolveParam) + .orRefine { case x: Parameter if x.isInCookies => x }(resolveParam) + .orRefine { case x: Parameter if x.isInFormData => x }(resolveParam) .orRefineFallback(fallbackParameterHandler) } @@ -130,13 +129,6 @@ object LanguageParameter { (meta, required) <- paramMeta(parameter) core.Resolved(paramType, _, baseDefaultValue, reifiedRawType) <- core.ResolvedType.resolve[L, F](meta, protocolElems) - (rawType, rawFormat) = reifiedRawType match { - case LiteralRawType(rawType, rawFormat) => (rawType, rawFormat) - case VectorRawType(LiteralRawType(rawType, rawFormat)) => (rawType, rawFormat) - case MapRawType(LiteralRawType(rawType, rawFormat)) => (rawType, rawFormat) - case _ => ??? // TODO: Currently highly unlikely, will need to fix. RawParameterType needs to just be replaced with ReifiedRawType - } - declType <- if (!required) { liftOptionalType(paramType) @@ -179,7 +171,7 @@ object LanguageParameter { paramTermName, RawParameterName(name), declType, - ReifiedRawType.of(rawType, rawFormat), + reifiedRawType, required, FileHashAlgorithm(parameter), isFileType diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala index 76c63fb7fb..16f36d4c1d 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala @@ -136,7 +136,7 @@ object ProtocolGenerator { for { enum <- extractEnum(schema.map(wrapEnumSchema)) customTpeName <- SwaggerUtil.customTypeName(schema) - tpe <- SwaggerUtil.determineTypeName(schema, Tracker.cloneHistory(schema, customTpeName), components) + (tpe, _) <- SwaggerUtil.determineTypeName(schema, Tracker.cloneHistory(schema, customTpeName), components) fullType <- selectType(NonEmptyList.ofInitLast(dtoPackage, clsName)) res <- enum.traverse(validProg(_, tpe, fullType)) } yield res @@ -202,7 +202,8 @@ object ProtocolGenerator { customType <- SwaggerUtil.customTypeName(prop) resolvedType <- SwaggerUtil .propMeta[L, F]( - prop + prop, + components ) // TODO: This should be resolved via an alternate mechanism that maintains references all the way through, instead of re-deriving and assuming that references are valid defValue <- defaultValue(typeName, prop, propertyRequirement, definitions) fieldName <- formatFieldName(name) @@ -544,9 +545,9 @@ object ProtocolGenerator { for { tpe <- model.fold[F[L#Type]](objectType(None)) { m => for { - tpeName <- SwaggerUtil.customTypeName[L, F, Tracker[ObjectSchema]](m) - res <- SwaggerUtil.determineTypeName[L, F](m, Tracker.cloneHistory(m, tpeName), components) - } yield res + tpeName <- SwaggerUtil.customTypeName[L, F, Tracker[ObjectSchema]](m) + (declType, _) <- SwaggerUtil.determineTypeName[L, F](m, Tracker.cloneHistory(m, tpeName), components) + } yield declType } res <- typeAlias[L, F](clsName, tpe) } yield res @@ -565,7 +566,7 @@ object ProtocolGenerator { def typeAlias[L <: LA, F[_]: Monad](clsName: String, tpe: L#Type): F[ProtocolElems[L]] = (RandomType[L](clsName, tpe): ProtocolElems[L]).pure[F] - def fromArray[L <: LA, F[_]](clsName: String, arr: Tracker[ArraySchema], concreteTypes: List[PropMeta[L]])(implicit + def fromArray[L <: LA, F[_]](clsName: String, arr: Tracker[ArraySchema], concreteTypes: List[PropMeta[L]], components: Tracker[Option[Components]])(implicit F: FrameworkTerms[L, F], P: ProtocolTerms[L, F], Sc: LanguageTerms[L, F], @@ -574,7 +575,7 @@ object ProtocolGenerator { ): F[ProtocolElems[L]] = { import P._ for { - deferredTpe <- SwaggerUtil.modelMetaType(arr) + deferredTpe <- SwaggerUtil.modelMetaType(arr, components) tpe <- extractArrayType(deferredTpe, concreteTypes) ret <- typeAlias[L, F](clsName, tpe) } yield ret @@ -674,7 +675,7 @@ object ProtocolGenerator { Sw.log.function("ProtocolGenerator.fromSwagger")(for { (hierarchies, definitionsWithoutPoly) <- groupHierarchies(definitions) - concreteTypes <- SwaggerUtil.extractConcreteTypes[L, F](definitions.value) + concreteTypes <- SwaggerUtil.extractConcreteTypes[L, F](definitions.value, components) polyADTs <- hierarchies.traverse(fromPoly(_, concreteTypes, definitions.value, dtoPackage, supportPackage.toList, defaultPropertyRequirement, components)) elems <- definitionsWithoutPoly.traverse { case (clsName, model) => model @@ -717,7 +718,7 @@ object ProtocolGenerator { .orRefine { case a: ArraySchema => a }(arr => for { formattedClsName <- formatTypeName(clsName) - array <- fromArray(formattedClsName, arr, concreteTypes) + array <- fromArray(formattedClsName, arr, concreteTypes, components) } yield array ) .orRefine { case o: ObjectSchema => o }(m => @@ -754,16 +755,16 @@ object ProtocolGenerator { components ) customTypeName <- SwaggerUtil.customTypeName(x) - tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName), components) - alias <- typeAlias[L, F](formattedClsName, tpe) + (declType, _) <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName), components) + alias <- typeAlias[L, F](formattedClsName, declType) } yield enum.orElse(model).getOrElse(alias) ) .valueOr(x => for { formattedClsName <- formatTypeName(clsName) customTypeName <- SwaggerUtil.customTypeName(x) - tpe <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName), components) - res <- typeAlias[L, F](formattedClsName, tpe) + (declType, _) <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName), components) + res <- typeAlias[L, F](formattedClsName, declType) } yield res ) } diff --git a/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala index 50105f3951..b7158566b7 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala @@ -57,7 +57,7 @@ object ServerGenerator { responseServerPair <- routes.traverse { case route @ RouteMeta(path, method, operation, securityRequirements) => for { operationId <- getOperationId(operation) - responses <- Responses.getResponses(operationId, operation, protocolElems) + responses <- Responses.getResponses(operationId, operation, protocolElems, components) responseClsName <- formatTypeName(operationId, Some("Response")) responseDefinitions <- generateResponseDefinitions(responseClsName, responses, protocolElems) methodName <- formatMethodName(operationId) diff --git a/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala index 603b5ed49e..d4c01dcddc 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala @@ -32,14 +32,6 @@ object SwaggerGenerator { class SwaggerGenerator[L <: LA] extends SwaggerTerms[L, Target] { override def MonadF: Monad[Target] = Target.targetInstances - private def parameterSchemaType(parameter: Tracker[Parameter]): Target[Tracker[String]] = { - val parameterName: String = parameter.downField("name", _.getName).unwrapTracker.fold("no name")(s => s"named: ${s}") - for { - schema <- parameter.downField("schema", _.getSchema).raiseErrorIfEmpty(s"Parameter (${parameterName}) has no schema") - tpe <- schema.downField("type", _.getType).raiseErrorIfEmpty(s"Parameter (${parameterName}) has no schema type") - } yield tpe - } - private def splitOperationParts(operationId: String): (List[String], String) = { val parts = operationId.split('.') (parts.drop(1).toList, parts.last) @@ -212,24 +204,6 @@ class SwaggerGenerator[L <: LA] extends SwaggerTerms[L, Target] { ) } yield dereferenced - override def getHeaderParameterType(parameter: Tracker[Parameter]) = - parameterSchemaType(parameter) - - override def getPathParameterType(parameter: Tracker[Parameter]) = - parameterSchemaType(parameter) - - override def getQueryParameterType(parameter: Tracker[Parameter]) = - parameterSchemaType(parameter) - - override def getCookieParameterType(parameter: Tracker[Parameter]) = - parameterSchemaType(parameter) - - override def getFormParameterType(parameter: Tracker[Parameter]) = - parameterSchemaType(parameter) - - override def getSerializableParameterType(parameter: Tracker[Parameter]) = - parameterSchemaType(parameter) - override def getRefParameterRef(parameter: Tracker[Parameter]) = parameter .downField("$ref", _.get$ref()) diff --git a/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala b/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala index 317fb61e74..1c72c7f1f0 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala @@ -5,11 +5,10 @@ import cats.data.NonEmptyList import java.nio.file.Path import dev.guardrail._ -import dev.guardrail.core.Tracker +import dev.guardrail.core.{ ReifiedRawType, Tracker } import dev.guardrail.generators.{ Client, Server } import dev.guardrail.languages.LA import dev.guardrail.terms.protocol.StrictProtocolElems -import dev.guardrail.core.ReifiedRawType abstract class LanguageTerms[L <: LA, F[_]] { self => def MonadF: Monad[F] diff --git a/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala b/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala index 591da9427e..2417b13efd 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/Responses.scala @@ -8,6 +8,7 @@ import dev.guardrail.terms.framework.FrameworkTerms import dev.guardrail.{ SwaggerUtil, monadForFrameworkTerms } import dev.guardrail.terms.protocol.StrictProtocolElems import io.swagger.v3.oas.models.Operation +import io.swagger.v3.oas.models.Components class Response[L <: LA]( val statusCodeName: L#TermName, @@ -26,7 +27,12 @@ class Responses[L <: LA](val value: List[Response[L]]) { override def toString: String = s"Responses($value)" } object Responses { - def getResponses[L <: LA, F[_]](operationId: String, operation: Tracker[Operation], protocolElems: List[StrictProtocolElems[L]])(implicit + def getResponses[L <: LA, F[_]]( + operationId: String, + operation: Tracker[Operation], + protocolElems: List[StrictProtocolElems[L]], + components: Tracker[Option[Components]] + )(implicit Fw: FrameworkTerms[L, F], Sc: LanguageTerms[L, F], Cl: CollectionsLibTerms[L, F], @@ -49,7 +55,7 @@ object Responses { schema <- content.downField("schema", _.getSchema()).indexedDistribute.toList } yield (contentType, schema)).traverse { case (contentType, prop) => for { - meta <- SwaggerUtil.propMeta[L, F](prop) + meta <- SwaggerUtil.propMeta[L, F](prop, components) resolved <- core.ResolvedType.resolve[L, F](meta, protocolElems) core.Resolved(baseType, _, baseDefaultValue, _) = resolved // TODO: ReifiedRawType is just dropped, should it be considered? } yield (contentType, baseType, baseDefaultValue) diff --git a/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala b/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala index 2496364f65..64f8a7b510 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/SwaggerTerms.scala @@ -46,13 +46,7 @@ abstract class SwaggerTerms[L <: LA, F[_]] { def getClassName(operation: Tracker[Operation], vendorPrefixes: List[String], tagBehaviour: Context.TagsBehaviour): F[List[String]] def getParameterName(parameter: Tracker[Parameter]): F[String] def getParameterSchema(parameter: Tracker[Parameter], components: Tracker[Option[Components]]): F[Tracker[SchemaProjection]] - def getHeaderParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] - def getPathParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] - def getQueryParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] - def getCookieParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] - def getFormParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] def getRefParameterRef(parameter: Tracker[Parameter]): F[Tracker[String]] - def getSerializableParameterType(parameter: Tracker[Parameter]): F[Tracker[String]] def fallbackParameterHandler(parameter: Tracker[Parameter]): F[(core.ResolvedType[L], Boolean)] def getOperationId(operation: Tracker[Operation]): F[String] def getResponses(operationId: String, operation: Tracker[Operation]): F[NonEmptyList[(String, Tracker[ApiResponse])]] diff --git a/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala b/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala index 6e87cc2516..01ff669983 100644 --- a/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala +++ b/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala @@ -15,7 +15,7 @@ import com.github.javaparser.ast.stmt._ import scala.reflect.runtime.universe.typeTag import dev.guardrail.core -import dev.guardrail.core.{ ReifiedRawType, Tracker } +import dev.guardrail.core.{ LiteralRawType, ReifiedRawType, Tracker } import dev.guardrail.core.extract.{ DataRedaction, EmptyValueIsNull } import dev.guardrail.core.implicits._ import dev.guardrail.core.{ DataRedacted, DataVisible, EmptyIsEmpty, EmptyIsNull, EmptyToNullBehaviour, RedactionBehaviour } @@ -868,32 +868,32 @@ class JacksonGenerator private (implicit Cl: CollectionsLibTerms[JavaLanguage, T .flatten .getOrElse(EmptyIsEmpty) val dataRedaction = DataRedaction(property).getOrElse(DataVisible) + + val fallbackRawType = LiteralRawType(property.downField("type", _.getType()).unwrapTracker, property.downField("format", _.getFormat()).unwrapTracker) + for { - tpeClassDep <- meta match { - case core.Resolved(declType, classDep, _, _) => - Target.pure((declType, classDep)) + (tpe, classDep, rawType) <- meta match { + case core.Resolved(declType, classDep, _, rawType) => + Target.pure((declType, classDep, rawType)) case core.Deferred(tpeName) => val tpe = concreteTypes.find(_.clsName == tpeName).map(x => Target.pure(x.tpe)).getOrElse { println(s"Unable to find definition for ${tpeName}, just inlining") safeParseType(tpeName) } - tpe.map((_, Option.empty)) + tpe.map((_, Option.empty, fallbackRawType)) case core.DeferredArray(tpeName, containerTpe) => val concreteType = lookupTypeName(tpeName, concreteTypes) for { innerType <- concreteType.fold(safeParseType(tpeName))(Target.pure) tpe <- Cl.liftVectorType(innerType, containerTpe) - } yield (tpe, Option.empty) + } yield (tpe, Option.empty, ReifiedRawType.ofVector(fallbackRawType)) case core.DeferredMap(tpeName, containerTpe) => val concreteType = lookupTypeName(tpeName, concreteTypes) for { innerType <- concreteType.fold(safeParseType(tpeName))(Target.pure) tpe <- Cl.liftMapType(innerType, containerTpe) - } yield (tpe, Option.empty) + } yield (tpe, Option.empty, ReifiedRawType.ofVector(fallbackRawType)) } - (tpe, classDep) = tpeClassDep - - rawType = ReifiedRawType.of(property.downField("type", _.getType()).unwrapTracker, property.downField("format", _.getFormat()).unwrapTracker) expressionDefaultValue <- (defaultValue match { case Some(e: Expression) => Target.pure(Some(e)) diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 4f4dea3d7f..0777457594 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -153,11 +153,10 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator) extends defaultValue: Option[scala.meta.Term] ): Target[ProtocolParameter[ScalaLanguage]] = Target.log.function(s"transformProperty") { + val fallbackRawType = ReifiedRawType.of(property.downField("type", _.getType()).unwrapTracker, property.downField("format", _.getFormat()).unwrapTracker) for { _ <- Target.log.debug(s"Args: (${clsName}, ${name}, ...)") - rawType = ReifiedRawType.of(property.downField("type", _.getType()).unwrapTracker, property.downField("format", _.getFormat()).unwrapTracker) - readOnlyKey = Option(name).filter(_ => property.downField("readOnly", _.getReadOnly()).unwrapTracker.contains(true)) emptyToNull = property .refine { case d: DateSchema => d }(d => EmptyValueIsNull(d)) @@ -169,26 +168,27 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator) extends dataRedaction = DataRedaction(property).getOrElse(DataVisible) - (tpe, classDep) = meta match { - case core.Resolved(declType, classDep, _, LiteralRawType(Some(rawType), rawFormat)) if SwaggerUtil.isFile(rawType, rawFormat) && !isCustomType => + (tpe, classDep, rawType) = meta match { + case core.Resolved(declType, classDep, _, rawType @ LiteralRawType(Some(rawTypeStr), rawFormat)) + if SwaggerUtil.isFile(rawTypeStr, rawFormat) && !isCustomType => // assume that binary data are represented as a string. allow users to override. - (t"String", classDep) - case core.Resolved(declType, classDep, _, _) => - (declType, classDep) + (t"String", classDep, rawType) + case core.Resolved(declType, classDep, _, rawType) => + (declType, classDep, rawType) case core.Deferred(tpeName) => val tpe = concreteTypes.find(_.clsName == tpeName).map(_.tpe).getOrElse { println(s"Unable to find definition for ${tpeName}, just inlining") Type.Name(tpeName) } - (tpe, Option.empty) + (tpe, Option.empty, fallbackRawType) case core.DeferredArray(tpeName, containerTpe) => val concreteType = lookupTypeName(tpeName, concreteTypes)(identity) val innerType = concreteType.getOrElse(Type.Name(tpeName)) - (t"${containerTpe.getOrElse(t"_root_.scala.Vector")}[$innerType]", Option.empty) + (t"${containerTpe.getOrElse(t"_root_.scala.Vector")}[$innerType]", Option.empty, ReifiedRawType.ofVector(fallbackRawType)) case core.DeferredMap(tpeName, customTpe) => val concreteType = lookupTypeName(tpeName, concreteTypes)(identity) val innerType = concreteType.getOrElse(Type.Name(tpeName)) - (t"${customTpe.getOrElse(t"_root_.scala.Predef.Map")}[_root_.scala.Predef.String, $innerType]", Option.empty) + (t"${customTpe.getOrElse(t"_root_.scala.Predef.Map")}[_root_.scala.Predef.String, $innerType]", Option.empty, ReifiedRawType.ofMap(fallbackRawType)) } presence <- ScalaGenerator().selectTerm(NonEmptyList.ofInitLast(supportPackage, "Presence")) presenceType <- ScalaGenerator().selectType(NonEmptyList.ofInitLast(supportPackage, "Presence")) From 8d495ad11fc641307327753ac5fd51a279c21cb2 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 13:02:41 -0800 Subject: [PATCH 16/21] Inline transform call to avoid random val res = ... --- .../generators/scala/http4s/Http4sServerGenerator.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala b/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala index 2f53f545c4..a8f8bb9742 100644 --- a/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala +++ b/modules/scala-http4s/src/main/scala/dev/guardrail/generators/scala/http4s/Http4sServerGenerator.scala @@ -1076,11 +1076,11 @@ class Http4sServerGenerator private (version: Http4sVersion) extends ServerTerms q""" object ${matcherName} { def unapply(params: Map[String, collection.Seq[String]]): Option[${container}[${tpe}]] = { - val res = params + ${transform(q"""params .get(${argName}) .flatMap(values => values.toList.traverse(s => QueryParamDecoder[${tpe}].decode(QueryParameterValue(s)).toOption)) - ${transform(q"res")} + """)} } } """ From 01f3c9ec39f5d3765d87b7f3e31faabf3d127e24 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 13:25:18 -0800 Subject: [PATCH 17/21] Correcting previously incorrect types in tests --- .../src/test/scala/core/issues/Issue1218.scala | 16 ++++++++-------- .../src/test/scala/core/issues/Issue1218.scala | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1218.scala b/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1218.scala index dd2d443615..65256081e7 100644 --- a/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1218.scala +++ b/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1218.scala @@ -96,17 +96,17 @@ class Issue1218Suite extends AnyFunSuite with Matchers with EitherValues with Sc compareOMQPDM[Long, List](resource.DoFooOptreflistMatcher, "optreflist").apply(longCases) compareOMQPDM[Long, Seq](resource.DoFooOptrefseqMatcher, "optrefseq").apply(longCases) compareOMQPDM[Long, Vector](resource.DoFooOptrefvecMatcher, "optrefvec").apply(longCases) - compareOMQPDM[String, Iterable](resource.DoFooOptidxseqMatcher, "optidxseq").apply(stringCases) - compareOMQPDM[String, Iterable](resource.DoFooOptlistMatcher, "optlist").apply(stringCases) - compareOMQPDM[String, Iterable](resource.DoFooOptseqMatcher, "optseq").apply(stringCases) - compareOMQPDM[String, Iterable](resource.DoFooOptvectorMatcher, "optvector").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptidxseqMatcher, "optidxseq").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptlistMatcher, "optlist").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptseqMatcher, "optseq").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptvectorMatcher, "optvector").apply(stringCases) compareQPDM[Long, IndexedSeq](resource.DoFooRefidxseqMatcher, "refidxseq").apply(longCases) compareQPDM[Long, List](resource.DoFooReflistMatcher, "reflist").apply(longCases) compareQPDM[Long, Seq](resource.DoFooRefseqMatcher, "refseq").apply(longCases) compareQPDM[Long, Vector](resource.DoFooRefvecMatcher, "refvec").apply(longCases) - compareQPDM[String, Iterable](resource.DoFooIdxseqMatcher, "idxseq").apply(stringCases) - compareQPDM[String, Iterable](resource.DoFooListMatcher, "list").apply(stringCases) - compareQPDM[String, Iterable](resource.DoFooSeqMatcher, "seq").apply(stringCases) - compareQPDM[String, Iterable](resource.DoFooVectorMatcher, "vector").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooIdxseqMatcher, "idxseq").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooListMatcher, "list").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooSeqMatcher, "seq").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooVectorMatcher, "vector").apply(stringCases) } } diff --git a/modules/sample-http4s/src/test/scala/core/issues/Issue1218.scala b/modules/sample-http4s/src/test/scala/core/issues/Issue1218.scala index b71f4501b3..397a66469c 100644 --- a/modules/sample-http4s/src/test/scala/core/issues/Issue1218.scala +++ b/modules/sample-http4s/src/test/scala/core/issues/Issue1218.scala @@ -96,17 +96,17 @@ class Issue1218Suite extends AnyFunSuite with Matchers with EitherValues with Sc compareOMQPDM[Long, List](resource.DoFooOptreflistMatcher, "optreflist").apply(longCases) compareOMQPDM[Long, Seq](resource.DoFooOptrefseqMatcher, "optrefseq").apply(longCases) compareOMQPDM[Long, Vector](resource.DoFooOptrefvecMatcher, "optrefvec").apply(longCases) - compareOMQPDM[String, Iterable](resource.DoFooOptidxseqMatcher, "optidxseq").apply(stringCases) - compareOMQPDM[String, Iterable](resource.DoFooOptlistMatcher, "optlist").apply(stringCases) - compareOMQPDM[String, Iterable](resource.DoFooOptseqMatcher, "optseq").apply(stringCases) - compareOMQPDM[String, Iterable](resource.DoFooOptvectorMatcher, "optvector").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptidxseqMatcher, "optidxseq").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptlistMatcher, "optlist").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptseqMatcher, "optseq").apply(stringCases) + compareOMQPDM[Long, Iterable](resource.DoFooOptvectorMatcher, "optvector").apply(stringCases) compareQPDM[Long, IndexedSeq](resource.DoFooRefidxseqMatcher, "refidxseq").apply(longCases) compareQPDM[Long, List](resource.DoFooReflistMatcher, "reflist").apply(longCases) compareQPDM[Long, Seq](resource.DoFooRefseqMatcher, "refseq").apply(longCases) compareQPDM[Long, Vector](resource.DoFooRefvecMatcher, "refvec").apply(longCases) - compareQPDM[String, Iterable](resource.DoFooIdxseqMatcher, "idxseq").apply(stringCases) - compareQPDM[String, Iterable](resource.DoFooListMatcher, "list").apply(stringCases) - compareQPDM[String, Iterable](resource.DoFooSeqMatcher, "seq").apply(stringCases) - compareQPDM[String, Iterable](resource.DoFooVectorMatcher, "vector").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooIdxseqMatcher, "idxseq").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooListMatcher, "list").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooSeqMatcher, "seq").apply(stringCases) + compareQPDM[Long, Iterable](resource.DoFooVectorMatcher, "vector").apply(stringCases) } } From a1e1b02d244783ef4166f1bbf823f83fbe071d5a Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 13:34:33 -0800 Subject: [PATCH 18/21] Adding support in for customArrayTypeName --- modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala index c639545da5..26c943ce21 100644 --- a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala +++ b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala @@ -161,7 +161,10 @@ object SwaggerUtil { .cotraverse(itemsSchema => for { (found, innerRawType) <- determineTypeName(itemsSchema, Tracker.cloneHistory(schema, None), components) - lifted <- liftVectorType(found, None) + customArrayType <- SwaggerUtil + .customArrayTypeName(schema.unwrapTracker) + .flatMap(_.flatTraverse(x => parseType(Tracker.cloneHistory(schema, x)))) + lifted <- liftVectorType(found, customArrayType) } yield (lifted, ReifiedRawType.ofVector(innerRawType): ReifiedRawType) ) .getOrElse(arrayType(None).map((_, ReifiedRawType.unsafeEmpty))) From 2bcfb99f0d5b3dbd254e0609fdad63c0529fdb8f Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sat, 19 Feb 2022 19:35:09 -0800 Subject: [PATCH 19/21] Rewriting tests to use Vector --- .../core/AkkaHttp/AkkaHttpRoundTripTest.scala | 20 ++++++++-------- .../AkkaHttpJacksonRoundTripTest.scala | 20 ++++++++-------- .../core/Http4s/Http4sRoundTripTest.scala | 24 +++++++++---------- .../test/scala/core/issues/Issue1229.scala | 2 +- .../Http4s/RoundTrip/Http4sFormDataTest.scala | 12 +++++----- .../core/Http4s/Http4sRoundTripTest.scala | 24 +++++++++---------- .../test/scala/core/issues/Issue1229.scala | 2 +- .../Http4s/RoundTrip/Http4sFormDataTest.scala | 12 +++++----- 8 files changed, 58 insertions(+), 58 deletions(-) diff --git a/modules/sample-akkaHttp/src/test/scala/core/AkkaHttp/AkkaHttpRoundTripTest.scala b/modules/sample-akkaHttp/src/test/scala/core/AkkaHttp/AkkaHttpRoundTripTest.scala index 866b7991d8..26d6ff18aa 100644 --- a/modules/sample-akkaHttp/src/test/scala/core/AkkaHttp/AkkaHttpRoundTripTest.scala +++ b/modules/sample-akkaHttp/src/test/scala/core/AkkaHttp/AkkaHttpRoundTripTest.scala @@ -61,9 +61,9 @@ class AkkaHttpRoundTripTest extends AnyFunSuite with Matchers with EitherValues def deletePet( respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -124,8 +124,8 @@ class AkkaHttpRoundTripTest extends AnyFunSuite with Matchers with EitherValues def deletePet( respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -165,7 +165,7 @@ class AkkaHttpRoundTripTest extends AnyFunSuite with Matchers with EitherValues test("round-trip: 404 response") { val httpClient = Route.toFunction(PetResource.routes(new PetHandler { - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]): Future[PetResource.FindPetsByStatusResponse] = + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]): Future[PetResource.FindPetsByStatusResponse] = Future.successful(respond.NotFound) def addPet(respond: PetResource.AddPetResponse.type)(body: sdefs.Pet) = ??? @@ -173,7 +173,7 @@ class AkkaHttpRoundTripTest extends AnyFunSuite with Matchers with EitherValues respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -215,9 +215,9 @@ class AkkaHttpRoundTripTest extends AnyFunSuite with Matchers with EitherValues else Future.successful(respond.NotFound) def addPet(respond: PetResource.AddPetResponse.type)(body: sdefs.Pet) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -257,9 +257,9 @@ class AkkaHttpRoundTripTest extends AnyFunSuite with Matchers with EitherValues def deletePet( respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? diff --git a/modules/sample-akkaHttpJackson/src/test/scala/core/AkkaHttpJackson/AkkaHttpJacksonRoundTripTest.scala b/modules/sample-akkaHttpJackson/src/test/scala/core/AkkaHttpJackson/AkkaHttpJacksonRoundTripTest.scala index df4af51261..09141151e1 100644 --- a/modules/sample-akkaHttpJackson/src/test/scala/core/AkkaHttpJackson/AkkaHttpJacksonRoundTripTest.scala +++ b/modules/sample-akkaHttpJackson/src/test/scala/core/AkkaHttpJackson/AkkaHttpJacksonRoundTripTest.scala @@ -50,9 +50,9 @@ class AkkaHttpJacksonRoundTripTest extends AnyFunSuite with TestImplicits with M def deletePet( respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -113,8 +113,8 @@ class AkkaHttpJacksonRoundTripTest extends AnyFunSuite with TestImplicits with M def deletePet( respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -154,7 +154,7 @@ class AkkaHttpJacksonRoundTripTest extends AnyFunSuite with TestImplicits with M test("round-trip: 404 response") { val httpClient = Route.toFunction(PetResource.routes(new PetHandler { - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]): Future[PetResource.FindPetsByStatusResponse] = + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]): Future[PetResource.FindPetsByStatusResponse] = Future.successful(respond.NotFound) def addPet(respond: PetResource.AddPetResponse.type)(body: sdefs.Pet) = ??? @@ -162,7 +162,7 @@ class AkkaHttpJacksonRoundTripTest extends AnyFunSuite with TestImplicits with M respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -204,9 +204,9 @@ class AkkaHttpJacksonRoundTripTest extends AnyFunSuite with TestImplicits with M else Future.successful(respond.NotFound) def addPet(respond: PetResource.AddPetResponse.type)(body: sdefs.Pet) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? @@ -246,9 +246,9 @@ class AkkaHttpJacksonRoundTripTest extends AnyFunSuite with TestImplicits with M def deletePet( respond: PetResource.DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.PetStatus], _apiKey: Option[String] = None) = ??? - def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: PetResource.FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: PetResource.FindPetsByStatusEnumResponse.type)(status: sdefs.PetStatus) = ??? - def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: PetResource.FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: PetResource.GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: PetResource.UpdatePetResponse.type)(body: sdefs.Pet) = ??? def updatePetWithForm(respond: PetResource.UpdatePetWithFormResponse.type)(petId: Long, name: Option[String] = None, status: Option[String] = None) = ??? diff --git a/modules/sample-http4s-v0_22/src/test/scala/core/Http4s/Http4sRoundTripTest.scala b/modules/sample-http4s-v0_22/src/test/scala/core/Http4s/Http4sRoundTripTest.scala index 2ecbbabeb3..e273bd91d9 100644 --- a/modules/sample-http4s-v0_22/src/test/scala/core/Http4s/Http4sRoundTripTest.scala +++ b/modules/sample-http4s-v0_22/src/test/scala/core/Http4s/Http4sRoundTripTest.scala @@ -57,9 +57,9 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { def deletePet( respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( @@ -121,10 +121,10 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { def deletePet( respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? - def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? - def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? + def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? + def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( respond: UpdatePetWithFormResponse.type )(petId: Long, name: Option[String] = None, status: Option[String] = None) = @@ -165,7 +165,7 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { val httpService = new PetResource().routes(new PetHandler[IO] { def findPetsByStatus( respond: FindPetsByStatusResponse.type - )(status: Iterable[String]): IO[sdefs.pet.PetResource.FindPetsByStatusResponse] = + )(status: Vector[String]): IO[sdefs.pet.PetResource.FindPetsByStatusResponse] = IO.pure(respond.NotFound) def addPet(respond: AddPetResponse.type)(body: sdefs.definitions.Pet) = ??? @@ -173,7 +173,7 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( @@ -212,9 +212,9 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { else IO.pure(respond.NotFound) def addPet(respond: AddPetResponse.type)(body: sdefs.definitions.Pet) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( @@ -250,9 +250,9 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { def deletePet( respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( diff --git a/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1229.scala b/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1229.scala index 859b50ad85..c3065aed7e 100644 --- a/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1229.scala +++ b/modules/sample-http4s-v0_22/src/test/scala/core/issues/Issue1229.scala @@ -20,7 +20,7 @@ class Issue1229Suite extends AnyFunSuite with Matchers { def getDepartment(respond: GetDepartmentResponse.type)(id: String): cats.effect.IO[GetDepartmentResponse] = IO.pure(respond.Ok(fooDept)) def searchDepartments( respond: SearchDepartmentsResponse.type - )(query: Option[String], page: Int, pageSize: Int, sort: Option[Iterable[String]]): cats.effect.IO[SearchDepartmentsResponse] = + )(query: Option[String], page: Int, pageSize: Int, sort: Option[Vector[String]]): cats.effect.IO[SearchDepartmentsResponse] = IO.pure(respond.Ok(sdefs.DepartmentSearchResponse(Vector(fooDept), 0, 0, 0))) }) diff --git a/modules/sample-http4s-v0_22/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala b/modules/sample-http4s-v0_22/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala index ff896fea5d..1d3825f10a 100644 --- a/modules/sample-http4s-v0_22/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala +++ b/modules/sample-http4s-v0_22/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala @@ -27,7 +27,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { IO.pure(respond.NotAcceptable) } def doBar(respond: DoBarResponse.type)(status: Option[sdefs.definitions.Status], description: Option[String]): IO[DoBarResponse] = ??? - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -46,7 +46,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { IO.pure(respond.NotAcceptable) } def doBar(respond: DoBarResponse.type)(status: Option[sdefs.definitions.Status], description: Option[String]): IO[DoBarResponse] = ??? - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -66,7 +66,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { } else { IO.pure(respond.NotAcceptable) } - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -87,7 +87,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { } else { IO.pure(respond.NotAcceptable) } - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -102,7 +102,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { .routes(new FooHandler[IO] { def doFoo(respond: DoFooResponse.type)(status: sdefs.definitions.Status, description: String): IO[DoFooResponse] = ??? def doBar(respond: DoBarResponse.type)(status: Option[sdefs.definitions.Status], description: Option[String]): IO[DoBarResponse] = ??? - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = if (status.size == 1 && status.iterator.next() == sdefs.definitions.Status.Ok.toString) { IO.pure(respond.Ok) } else { @@ -112,6 +112,6 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { .orNotFound ) ) - fooClient.doBaz(Seq(cdefs.definitions.Status.Ok.toString)).attempt.unsafeRunSync().value shouldBe cdefs.foo.DoBazResponse.Ok + fooClient.doBaz(Vector(cdefs.definitions.Status.Ok.toString)).attempt.unsafeRunSync().value shouldBe cdefs.foo.DoBazResponse.Ok } } diff --git a/modules/sample-http4s/src/test/scala/core/Http4s/Http4sRoundTripTest.scala b/modules/sample-http4s/src/test/scala/core/Http4s/Http4sRoundTripTest.scala index ecb2ef3b77..dcf531e16c 100644 --- a/modules/sample-http4s/src/test/scala/core/Http4s/Http4sRoundTripTest.scala +++ b/modules/sample-http4s/src/test/scala/core/Http4s/Http4sRoundTripTest.scala @@ -55,9 +55,9 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { def deletePet( respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( @@ -119,10 +119,10 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { def deletePet( respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? - def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? - def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? + def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? + def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( respond: UpdatePetWithFormResponse.type )(petId: Long, name: Option[String] = None, status: Option[String] = None) = @@ -163,7 +163,7 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { val httpService = new PetResource().routes(new PetHandler[IO] { def findPetsByStatus( respond: FindPetsByStatusResponse.type - )(status: Iterable[String]): IO[sdefs.pet.PetResource.FindPetsByStatusResponse] = + )(status: Vector[String]): IO[sdefs.pet.PetResource.FindPetsByStatusResponse] = IO.pure(respond.NotFound) def addPet(respond: AddPetResponse.type)(body: sdefs.definitions.Pet) = ??? @@ -171,7 +171,7 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( @@ -210,9 +210,9 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { else IO.pure(respond.NotFound) def addPet(respond: AddPetResponse.type)(body: sdefs.definitions.Pet) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(tags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( @@ -248,9 +248,9 @@ class Http4sRoundTripTest extends AnyFunSuite with Matchers with EitherValues { def deletePet( respond: DeletePetResponse.type )(_petId: Long, includeChildren: Option[Boolean], status: Option[sdefs.definitions.PetStatus], apiKey: Option[String]) = ??? - def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Iterable[String]) = ??? + def findPetsByStatus(respond: FindPetsByStatusResponse.type)(status: Vector[String]) = ??? def findPetsByStatusEnum(respond: FindPetsByStatusEnumResponse.type)(status: sdefs.definitions.PetStatus) = ??? - def findPetsByTags(respond: FindPetsByTagsResponse.type)(ags: Iterable[String]) = ??? + def findPetsByTags(respond: FindPetsByTagsResponse.type)(ags: Vector[String]) = ??? def getPetById(respond: GetPetByIdResponse.type)(petId: Long) = ??? def updatePet(respond: UpdatePetResponse.type)(body: sdefs.definitions.Pet) = ??? def updatePetWithForm( diff --git a/modules/sample-http4s/src/test/scala/core/issues/Issue1229.scala b/modules/sample-http4s/src/test/scala/core/issues/Issue1229.scala index d0661a585e..b03d5612ee 100644 --- a/modules/sample-http4s/src/test/scala/core/issues/Issue1229.scala +++ b/modules/sample-http4s/src/test/scala/core/issues/Issue1229.scala @@ -21,7 +21,7 @@ class Issue1229Suite extends AnyFunSuite with Matchers { def getDepartment(respond: GetDepartmentResponse.type)(id: String): cats.effect.IO[GetDepartmentResponse] = IO.pure(respond.Ok(fooDept)) def searchDepartments( respond: SearchDepartmentsResponse.type - )(query: Option[String], page: Int, pageSize: Int, sort: Option[Iterable[String]]): cats.effect.IO[SearchDepartmentsResponse] = + )(query: Option[String], page: Int, pageSize: Int, sort: Option[Vector[String]]): cats.effect.IO[SearchDepartmentsResponse] = IO.pure(respond.Ok(sdefs.DepartmentSearchResponse(Vector(fooDept), 0, 0, 0))) }) diff --git a/modules/sample-http4s/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala b/modules/sample-http4s/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala index 4b4597cc73..f510b929a2 100644 --- a/modules/sample-http4s/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala +++ b/modules/sample-http4s/src/test/scala/generators/Http4s/RoundTrip/Http4sFormDataTest.scala @@ -28,7 +28,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { IO.pure(respond.NotAcceptable) } def doBar(respond: DoBarResponse.type)(status: Option[sdefs.definitions.Status], description: Option[String]): IO[DoBarResponse] = ??? - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -47,7 +47,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { IO.pure(respond.NotAcceptable) } def doBar(respond: DoBarResponse.type)(status: Option[sdefs.definitions.Status], description: Option[String]): IO[DoBarResponse] = ??? - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -67,7 +67,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { } else { IO.pure(respond.NotAcceptable) } - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -88,7 +88,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { } else { IO.pure(respond.NotAcceptable) } - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = ??? + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = ??? }) .orNotFound ) @@ -103,7 +103,7 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { .routes(new FooHandler[IO] { def doFoo(respond: DoFooResponse.type)(status: sdefs.definitions.Status, description: String): IO[DoFooResponse] = ??? def doBar(respond: DoBarResponse.type)(status: Option[sdefs.definitions.Status], description: Option[String]): IO[DoBarResponse] = ??? - def doBaz(respond: DoBazResponse.type)(status: Iterable[String], description: Option[Iterable[String]]): IO[DoBazResponse] = + def doBaz(respond: DoBazResponse.type)(status: Vector[String], description: Option[Vector[String]]): IO[DoBazResponse] = if (status.size == 1 && status.iterator.next() == sdefs.definitions.Status.Ok.toString) { IO.pure(respond.Ok) } else { @@ -113,6 +113,6 @@ class Http4sFormDataTest extends AnyFunSuite with Matchers with EitherValues { .orNotFound ) ) - fooClient.doBaz(Seq(cdefs.definitions.Status.Ok.toString)).attempt.unsafeRunSync().value shouldBe cdefs.foo.DoBazResponse.Ok + fooClient.doBaz(Vector(cdefs.definitions.Status.Ok.toString)).attempt.unsafeRunSync().value shouldBe cdefs.foo.DoBazResponse.Ok } } From 2d80e33269ee173dc85aa2887de333a1579a7ff1 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 22 May 2022 11:29:40 -0700 Subject: [PATCH 20/21] Ensuring samples get cleaned as well --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a90ff931d..98b4abc496 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run tests - run: sbt ++${{ matrix.scala.version }} clean guardrail/clean checkFormatting coverage guardrail/test coverageAggregate versionPolicyCheck microsite/compile + run: sbt ++${{ matrix.scala.version }} clean samples/clean checkFormatting coverage guardrail/test coverageAggregate versionPolicyCheck microsite/compile env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GUARDRAIL_CI: true @@ -83,7 +83,7 @@ jobs: restore-keys: | ${{ runner.os }}-scala-${{ matrix.scala.version }}- - name: Run tests - run: sbt ++${{ matrix.scala.version }} clean guardrail/clean coverage "runExample java ${{ matrix.framework.framework }}" ${{ matrix.framework.project }}/test coverageAggregate + run: sbt ++${{ matrix.scala.version }} clean samples/clean coverage "runExample java ${{ matrix.framework.framework }}" ${{ matrix.framework.project }}/test coverageAggregate - uses: codecov/codecov-action@v1 with: file: ./target/scala-${{ matrix.scala.bincompat }}/scoverage-report/scoverage.xml @@ -124,7 +124,7 @@ jobs: ${{ runner.os }}-scala-${{ matrix.scala.version }}- - name: Run tests if: ${{ env.combo_enabled == 'true' }} - run: sbt ++${{ matrix.scala.version }} clean guardrail/clean coverage "runExample scala ${{ matrix.framework.framework }}" ${{ matrix.framework.project }}/test coverageAggregate + run: sbt ++${{ matrix.scala.version }} clean samples/clean coverage "runExample scala ${{ matrix.framework.framework }}" ${{ matrix.framework.project }}/test coverageAggregate - uses: codecov/codecov-action@v1 if: ${{ env.combo_enabled == 'true' }} with: From 5d875085814343ac8c323c3ea6247225a0fb2bef Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 22 May 2022 13:28:14 -0700 Subject: [PATCH 21/21] Adding a note about the Iterator -> Vector change --- MIGRATING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/MIGRATING.md b/MIGRATING.md index 9ce8b11856..cc294c964d 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -1,3 +1,31 @@ +Migrating to guardrail-core 0.71.0 +================================== + +[#1407](https://github.com/guardrail-dev/guardrail/pull/1407) resolved some long-standing technical debt, unifying two different type resolution codepaths. + +The major deviation between the two was how `array` was handled. Previously, many parameters were generated as `Iterator`, now they are generated as `Vector`. + +Should you prefer the previous functionality, `x-scala-array-type` can be added to your specification in order to override the new default. + +```diff + openapi: 3.0.2 + paths: + /foo: + post: + parameters: + - name: vector + in: query + required: true + schema: + type: array ++ x-scala-array-type: Iterator + items: + type: integer + format: int64 +``` + +There is a [scalafix rule](https://raw.githubusercontent.com/guardrail-dev/guardrail-scalafix-rules/master/rules/src/main/scala/fix/GuardrailIteratorToVector.scala) that will also attempt to apply this change to method signatures throughout your codebase. + Migrating to 0.62.0 ===================