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: 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 =================== 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/SwaggerUtil.scala b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala index 8f0fbc4e98..26c943ce21 100644 --- a/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala +++ b/modules/core/src/main/scala/dev/guardrail/SwaggerUtil.scala @@ -5,9 +5,9 @@ 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 } 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._ @@ -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)(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 { @@ -54,7 +56,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,28 +70,28 @@ 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( for { - resolved <- modelMetaType[L, F](schema) + resolved <- modelMetaType[L, F](schema, components) } yield (clsName, resolved) ) } 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? } } // 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]], - 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})") { + def determineTypeName[L <: LA, F[_]]( + 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, ReifiedRawType)] = + Sw.log.function(s"determineTypeName(${rawSchema.unwrapTracker}, ${customType.unwrapTracker})") { import Sc._ import Cl._ import Fw._ @@ -97,7 +99,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 '${rawSchema.unwrapTracker + .getType()}', falling back to $t. Please switch definitions to x-scala-type for custom types. (${rawSchema.showHistory})" ) } @@ -105,36 +108,87 @@ 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) + 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))) + } + (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)) } - }(_.pure[F]) + def const(value: F[L#Type]): Tracker[Schema[_]] => F[(L#Type, ReifiedRawType)] = extractFormat(_ => value) + schemaProjection + .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, _))) + ) + .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(itemsSchema => + for { + (found, innerRawType) <- determineTypeName(itemsSchema, Tracker.cloneHistory(schema, None), components) + 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))) + ) + .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 { 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)) + } + } + 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 = @@ -145,12 +199,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)(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._ @@ -160,34 +214,32 @@ 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)) + 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]) ) 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 => - val rawType = a.downField("type", _.getType()) - val rawFormat = a.downField("format", _.getFormat()) - for { - customTpeName <- customTypeName(a) - tpe <- typeName[L, F](rawType, rawFormat, Tracker.cloneHistory(a, customTpeName)) - } yield core.Resolved[L](tpe, None, None, 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 @@ -202,7 +254,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 @@ -227,7 +281,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") { @@ -236,26 +290,32 @@ 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) 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)(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, _, 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) @@ -270,27 +330,27 @@ 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)(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)) + .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 } .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/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/ClientGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ClientGenerator.scala index 571bd48fb7..3691beeff7 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._ @@ -50,10 +53,10 @@ 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](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..fa220fad8a 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/LanguageParameter.scala @@ -1,19 +1,19 @@ 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 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 +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]]) { @@ -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], @@ -62,33 +63,30 @@ 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]] = - (_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] - } - - def resolveParam(param: Tracker[Parameter], typeFetcher: Tracker[Parameter] => F[Tracker[String]]): F[(ResolvedType[L], Boolean)] = + def getDefault[U <: Parameter: Default.GetDefault](schema: Tracker[media.Schema[_]]): F[Option[L#Term]] = for { - tpeName <- typeFetcher(param) - schema = param.downField("schema", _.getSchema) - fmt = schema.flatDownField("format", _.getFormat) + 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]): F[(ResolvedType[L], Boolean)] = + for { + schema <- getParameterSchema(param, components).map(_.map { + case SchemaLiteral(schema) => schema + case SchemaRef(SchemaLiteral(schema), _) => schema + }) customParamTypeName <- SwaggerUtil.customTypeName(param) - customSchemaTypeName <- schema.unwrapTracker.flatTraverse(SwaggerUtil.customTypeName(_: 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)) - .mapN(core.Resolved[L](_, None, _, 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) @@ -109,24 +107,27 @@ object LanguageParameter { ) .orRefine { case x: Parameter if x.isInBody => x }(param => for { - schema <- getBodyParameterSchema(param) - resolved <- SwaggerUtil.modelMetaType[L, F](schema) + schema <- getParameterSchema(param, components).map(_.map { + case SchemaLiteral(schema) => schema + case SchemaRef(SchemaLiteral(schema), _) => 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) } 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) declType <- if (!required) { @@ -170,7 +171,7 @@ object LanguageParameter { paramTermName, RawParameterName(name), declType, - ReifiedRawType.of(rawType, rawFormat), + reifiedRawType, required, FileHashAlgorithm(parameter), isFileType @@ -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/ProtocolGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala index 51a7a723db..16f36d4c1d 100644 --- a/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala +++ b/modules/core/src/main/scala/dev/guardrail/generators/ProtocolGenerator.scala @@ -69,8 +69,9 @@ object ProtocolGenerator { private[this] def fromEnum[L <: LA, F[_], A]( clsName: String, - swagger: Tracker[Schema[A]], - dtoPackage: List[String] + schema: Tracker[Schema[A]], + dtoPackage: List[String], + components: Tracker[Option[Components]] )(implicit P: ProtocolTerms[L, F], F: FrameworkTerms[L, F], @@ -132,15 +133,11 @@ 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 = swagger.downField("type", _.getType()).map(_.filterNot(_ == "object").orElse(Option("string"))) - for { - enum <- extractEnum(swagger.map(wrapEnumSchema)) - customTpeName <- SwaggerUtil.customTypeName(swagger) - tpe <- SwaggerUtil.typeName(tpeName, swagger.downField("format", _.getFormat()), Tracker.cloneHistory(swagger, customTpeName)) - fullType <- selectType(NonEmptyList.fromList(dtoPackage :+ clsName).getOrElse(NonEmptyList.of(clsName))) + enum <- extractEnum(schema.map(wrapEnumSchema)) + customTpeName <- SwaggerUtil.customTypeName(schema) + (tpe, _) <- SwaggerUtil.determineTypeName(schema, Tracker.cloneHistory(schema, customTpeName), components) + fullType <- selectType(NonEmptyList.ofInitLast(dtoPackage, clsName)) res <- enum.traverse(validProg(_, tpe, fullType)) } yield res } @@ -169,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], @@ -192,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) @@ -204,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) @@ -234,13 +233,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], @@ -285,7 +285,8 @@ object ProtocolGenerator { definitions, dtoPackage, supportPackage, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) interfacesCls = interfaces.flatMap(_.downField("$ref", _.get$ref).unwrapTracker.map(_.split("/").last)) tpe <- parseTypeName(clsName) @@ -318,7 +319,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], @@ -341,7 +343,8 @@ object ProtocolGenerator { definitions, dtoPackage, supportPackage, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) defn <- renderDTOClass(clsName.last, supportPackage, params, parents) encoder <- encodeModel(clsName.last, dtoPackage, params, parents) @@ -385,7 +388,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], @@ -402,11 +406,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, @@ -415,13 +420,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 @@ -432,7 +438,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) @@ -518,7 +524,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], @@ -538,15 +544,10 @@ 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) - ) - } 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 @@ -668,20 +669,20 @@ object ProtocolGenerator { ): F[ProtocolDefinitions[L]] = { import P._ import Sc._ - import Sw._ - 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)) + 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 .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, @@ -690,15 +691,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, @@ -707,21 +709,22 @@ 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 => for { formattedClsName <- formatTypeName(clsName) - array <- fromArray(formattedClsName, arr, concreteTypes) + array <- fromArray(formattedClsName, arr, concreteTypes, components) } yield array ) .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, @@ -730,15 +733,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, @@ -747,21 +751,20 @@ object ProtocolGenerator { definitions.value, dtoPackage, supportPackage.toList, - defaultPropertyRequirement + defaultPropertyRequirement, + components ) - 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) + (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) - 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) + (declType, _) <- SwaggerUtil.determineTypeName[L, F](x, Tracker.cloneHistory(x, customTypeName), components) + res <- typeAlias[L, F](formattedClsName, declType) } yield res ) } @@ -828,6 +831,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/ServerGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/ServerGenerator.scala index 9e1b7f8f8c..b7158566b7 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._ @@ -54,11 +57,11 @@ 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) - 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/generators/SwaggerGenerator.scala b/modules/core/src/main/scala/dev/guardrail/generators/SwaggerGenerator.scala index 09c2e29596..d4c01dcddc 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 @@ -29,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) @@ -196,28 +191,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 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 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 getRefParameterRef(parameter: Tracker[Parameter]) = parameter @@ -270,6 +255,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/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 47f0b8b946..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,9 +55,9 @@ 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 + 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/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) } 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..64f8a7b510 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,14 +45,8 @@ 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 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 getParameterSchema(parameter: Tracker[Parameter], components: Tracker[Option[Components]]): F[Tracker[SchemaProjection]] 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])]] @@ -58,5 +56,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] } 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..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)) @@ -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/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/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-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/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) } } 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/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`)) ) ) ) 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 } } 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) } 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..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 @@ -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( @@ -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)})" ) ), @@ -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")} + """)} } } """ 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..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 @@ -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 @@ -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, _, 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")) @@ -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) =>