diff --git a/compiler/src/main/scala/de/zalando/apifirst/generators/commonDataSteps.scala b/compiler/src/main/scala/de/zalando/apifirst/generators/commonDataSteps.scala index 9337ab7..c6077eb 100644 --- a/compiler/src/main/scala/de/zalando/apifirst/generators/commonDataSteps.scala +++ b/compiler/src/main/scala/de/zalando/apifirst/generators/commonDataSteps.scala @@ -81,6 +81,7 @@ trait CommonData { case TypeRef(ref) => app.findType(ref) match { case p: PrimitiveType => useType(p.name, suffix, "") + case d: TypeDef => useType(d.name, suffix, "") case _ => useType(ref, suffix, "") } case p: PrimitiveType => useType(t.name, suffix, "") diff --git a/compiler/src/main/scala/de/zalando/apifirst/generators/modelSteps.scala b/compiler/src/main/scala/de/zalando/apifirst/generators/modelSteps.scala index a7b24a3..9266b3a 100644 --- a/compiler/src/main/scala/de/zalando/apifirst/generators/modelSteps.scala +++ b/compiler/src/main/scala/de/zalando/apifirst/generators/modelSteps.scala @@ -34,7 +34,7 @@ trait ClassesStep extends EnrichmentStep[Type] { "fields" -> typeFields(table, k).map { f => Map( "name" -> escape(f.name.simple), - "type_name" -> typeNameDenotation(table, f.tpe.name) + TYPE_NAME -> typeNameDenotation(table, f.tpe.name) ) }, "imports" -> t.imports diff --git a/compiler/src/test/resources/examples/cross_spec_references.yaml b/compiler/src/test/resources/examples/cross_spec_references.yaml new file mode 100644 index 0000000..c5e9123 --- /dev/null +++ b/compiler/src/test/resources/examples/cross_spec_references.yaml @@ -0,0 +1,22 @@ +swagger: '2.0' +info: + version: "1.0.0" + title: An Example of cross - specification references +host: localhost:9000 +schemes: + - http +paths: + /: + post: + parameters: + - name: root + in: body + required: true + schema: + $ref: "hackweek.yaml#/definitions/ModelSchemaRoot" + responses: + 200: + description: Must have correct imports for cross-references specifications + schema: + $ref: "parts/split.petstore.definitions.yaml#/definitions/Pet" + diff --git a/compiler/src/test/resources/expected_results/controllers/cross_spec_references.yaml.base.scala b/compiler/src/test/resources/expected_results/controllers/cross_spec_references.yaml.base.scala new file mode 100644 index 0000000..890e1cb --- /dev/null +++ b/compiler/src/test/resources/expected_results/controllers/cross_spec_references.yaml.base.scala @@ -0,0 +1,80 @@ +package cross_spec_references.yaml + +import play.api.mvc.{Action, Controller, Results} +import play.api.http._ +import Results.Status +import de.zalando.play.controllers.{PlayBodyParsing, ParsingError, ResponseWriters} +import PlayBodyParsing._ +import scala.util._ +import de.zalando.play.controllers.ArrayWrapper + + + + +trait Cross_spec_referencesYamlBase extends Controller with PlayBodyParsing { + private type postActionRequestType = (ModelSchemaRoot) + private type postActionType = postActionRequestType => Try[(Int, Any)] + + private val errorToStatuspost: PartialFunction[Throwable, Status] = PartialFunction.empty[Throwable, Status] + + private def postParser(acceptedTypes: Seq[String], maxLength: Int = parse.DefaultMaxTextLength) = { + def bodyMimeType: Option[MediaType] => String = mediaType => { + val requestType = mediaType.toSeq.map { + case m: MediaRange => m + case MediaType(a,b,c) => new MediaRange(a,b,c,None,Nil) + } + negotiateContent(requestType, acceptedTypes).orElse(acceptedTypes.headOption).getOrElse("application/json") + } + + import de.zalando.play.controllers.WrappedBodyParsers + + val customParsers = WrappedBodyParsers.anyParser[ModelSchemaRoot] + anyParser[ModelSchemaRoot](bodyMimeType, customParsers, "Invalid ModelSchemaRoot", maxLength) + } + + val postActionConstructor = Action + def postAction = (f: postActionType) => postActionConstructor(postParser(Seq[String]())) { request => + val providedTypes = Seq[String]() + + negotiateContent(request.acceptedTypes, providedTypes).map { postResponseMimeType => + val possibleWriters = Map( + 200 -> anyToWritable[Pet] + ) + val root = request.body + + + + val result = + new PostValidator(root).errors match { + case e if e.isEmpty => processValidpostRequest(f)((root))(possibleWriters, postResponseMimeType) + case l => + implicit val marshaller: Writeable[Seq[ParsingError]] = parsingErrors2Writable(postResponseMimeType) + BadRequest(l) + } + result + + }.getOrElse(Status(415)("The server doesn't support any of the requested mime types")) + } + + private def processValidpostRequest[T <: Any](f: postActionType)(request: postActionRequestType) + (writers: Map[Int, String => Writeable[T]], mimeType: String)(implicit m: Manifest[T]) = { + import de.zalando.play.controllers.ResponseWriters + + val callerResult = f(request) + val status = callerResult match { + case Failure(error) => (errorToStatuspost orElse defaultErrorMapping)(error) + case Success((code: Int, result: T @ unchecked)) => + val writerOpt = ResponseWriters.choose(mimeType)[T]().orElse(writers.get(code).map(_.apply(mimeType))) + writerOpt.map { implicit writer => + Status(code)(result) + }.getOrElse { + implicit val errorWriter = anyToWritable[IllegalStateException](mimeType) + Status(500)(new IllegalStateException(s"Response code was not defined in specification: $code")) + } + case Success(other) => + implicit val errorWriter = anyToWritable[IllegalStateException](mimeType) + Status(500)(new IllegalStateException(s"Expected pair (responseCode, response) from the controller, but was: other")) + } + status + } +} diff --git a/compiler/src/test/resources/expected_results/controllers/cross_spec_references.yaml.scala b/compiler/src/test/resources/expected_results/controllers/cross_spec_references.yaml.scala new file mode 100644 index 0000000..20808ee --- /dev/null +++ b/compiler/src/test/resources/expected_results/controllers/cross_spec_references.yaml.scala @@ -0,0 +1,30 @@ + +import play.api.mvc.{Action, Controller} + +import play.api.data.validation.Constraint + +import de.zalando.play.controllers._ + +import PlayBodyParsing._ + +import PlayValidations._ + +import scala.util._ + + +/** + * This controller is re-generated after each change in the specification. + * Please only place your hand-written code between appropriate comments in the body of the controller. + */ + +package cross_spec_references.yaml { + + class Cross_spec_referencesYaml extends Cross_spec_referencesYamlBase { + val post = postAction { (root: ModelSchemaRoot) => + // ----- Start of unmanaged code area for action Cross_spec_referencesYaml.post + Failure(???) + // ----- End of unmanaged code area for action Cross_spec_referencesYaml.post + } + + } +} diff --git a/compiler/src/test/resources/expected_results/flat_types/cross_spec_references.yaml.types b/compiler/src/test/resources/expected_results/flat_types/cross_spec_references.yaml.types new file mode 100644 index 0000000..0c06a91 --- /dev/null +++ b/compiler/src/test/resources/expected_results/flat_types/cross_spec_references.yaml.types @@ -0,0 +1,68 @@ +⌿definitions⌿Meta⌿copyright -> + Opt(Str) +⌿definitions⌿ModelSchema⌿keywords -> + Opt(Str) +⌿definitions⌿ModelSchema⌿specialDescriptions -> + Opt(TypeRef(⌿definitions⌿ModelSchema⌿specialDescriptions⌿Opt)) +⌿definitions⌿ModelSchemaRoot⌿data -> + Opt(TypeRef(⌿definitions⌿ModelSchemaRoot⌿data⌿Opt)) +⌿definitions⌿Pet⌿id -> + Opt(Lng) +⌿definitions⌿ModelSchemaRoot⌿links -> + Opt(TypeRef(⌿definitions⌿ModelSchemaRoot⌿links⌿Opt)) +⌿definitions⌿Pet⌿tags -> + Opt(TypeRef(⌿definitions⌿Pet⌿tags⌿Opt)) +⌿definitions⌿Pet⌿photoUrls -> + ArrResult(Str) +⌿definitions⌿ModelSchema⌿lengthRegister -> + Opt(Str) +⌿definitions⌿ModelSchema⌿ageGroups -> + Arr(Str) +⌿definitions⌿Pet⌿category -> + Opt(TypeRef(⌿definitions⌿Pet⌿category⌿Opt)) +⌿definitions⌿ModelSchemaRoot⌿meta -> + Opt(TypeRef(⌿definitions⌿ModelSchemaRoot⌿meta⌿Opt)) +⌿definitions⌿ModelSchema⌿specialDescriptions⌿Opt -> + Arr(Str) +⌿definitions⌿Pet⌿category⌿Opt -> + TypeDef(⌿definitions⌿Category, Seq( + Field(⌿definitions⌿Category⌿id, TypeRef(⌿definitions⌿Pet⌿id)), + Field(⌿definitions⌿Category⌿name, TypeRef(⌿definitions⌿Meta⌿copyright)))) +⌿definitions⌿ModelSchemaRoot⌿data⌿Opt -> + TypeDef(⌿definitions⌿ModelSchema, Seq( + Field(⌿definitions⌿ModelSchema⌿name, Str), + Field(⌿definitions⌿ModelSchema⌿sizeRegister, Str), + Field(⌿definitions⌿ModelSchema⌿brand, Str), + Field(⌿definitions⌿ModelSchema⌿partnerArticleModelId, Intgr), + Field(⌿definitions⌿ModelSchema⌿silhouetteId, Str), + Field(⌿definitions⌿ModelSchema⌿description, TypeRef(⌿definitions⌿Meta⌿copyright)), + Field(⌿definitions⌿ModelSchema⌿ageGroups, TypeRef(⌿definitions⌿ModelSchema⌿ageGroups)), + Field(⌿definitions⌿ModelSchema⌿keywords, TypeRef(⌿definitions⌿ModelSchema⌿keywords)), + Field(⌿definitions⌿ModelSchema⌿lengthRegister, TypeRef(⌿definitions⌿ModelSchema⌿lengthRegister)), + Field(⌿definitions⌿ModelSchema⌿specialDescriptions, TypeRef(⌿definitions⌿ModelSchema⌿specialDescriptions)), + Field(⌿definitions⌿ModelSchema⌿articleModelAttributes, TypeRef(⌿definitions⌿ModelSchema⌿specialDescriptions)))) +⌿definitions⌿ModelSchemaRoot⌿meta⌿Opt -> + TypeDef(⌿definitions⌿Meta, Seq( + Field(⌿definitions⌿Meta⌿copyright, TypeRef(⌿definitions⌿Meta⌿copyright)))) +⌿paths⌿/⌿post⌿root -> + TypeDef(⌿definitions⌿ModelSchemaRoot, Seq( + Field(⌿definitions⌿ModelSchemaRoot⌿data, TypeRef(⌿definitions⌿ModelSchemaRoot⌿data)), + Field(⌿definitions⌿ModelSchemaRoot⌿meta, TypeRef(⌿definitions⌿ModelSchemaRoot⌿meta)), + Field(⌿definitions⌿ModelSchemaRoot⌿links, TypeRef(⌿definitions⌿ModelSchemaRoot⌿links)))) +⌿definitions⌿ModelSchemaRoot⌿links⌿Opt -> + TypeDef(⌿definitions⌿Links, Seq( + Field(⌿definitions⌿Links⌿self, TypeRef(⌿definitions⌿Meta⌿copyright)), + Field(⌿definitions⌿Links⌿related, TypeRef(⌿definitions⌿Meta⌿copyright)))) +⌿definitions⌿Pet⌿tags⌿Opt -> + ArrResult(TypeRef(⌿definitions⌿Pet⌿category⌿Opt)) +⌿paths⌿/⌿post⌿responses⌿200 -> + TypeDef(⌿definitions⌿Pet, Seq( + Field(⌿definitions⌿Pet⌿name, Str), + Field(⌿definitions⌿Pet⌿tags, TypeRef(⌿definitions⌿Pet⌿tags)), + Field(⌿definitions⌿Pet⌿photoUrls, TypeRef(⌿definitions⌿Pet⌿photoUrls)), + Field(⌿definitions⌿Pet⌿id, TypeRef(⌿definitions⌿Pet⌿id)), + Field(⌿definitions⌿Pet⌿status, TypeRef(⌿definitions⌿Meta⌿copyright)), + Field(⌿definitions⌿Pet⌿category, TypeRef(⌿definitions⌿Pet⌿category)))) +-- params -- + +⌿paths⌿/⌿post⌿root -> Parameter(root,TypeRef(⌿paths⌿/⌿post⌿root),None,None,.+,false,body) \ No newline at end of file diff --git a/compiler/src/test/resources/expected_results/marshallers/cross_spec_references.yaml.scala b/compiler/src/test/resources/expected_results/marshallers/cross_spec_references.yaml.scala new file mode 100644 index 0000000..b28b04f --- /dev/null +++ b/compiler/src/test/resources/expected_results/marshallers/cross_spec_references.yaml.scala @@ -0,0 +1,3 @@ + + + diff --git a/compiler/src/test/resources/expected_results/model/cross_spec_references.yaml.scala b/compiler/src/test/resources/expected_results/model/cross_spec_references.yaml.scala new file mode 100644 index 0000000..d8b25e8 --- /dev/null +++ b/compiler/src/test/resources/expected_results/model/cross_spec_references.yaml.scala @@ -0,0 +1,33 @@ +package cross_spec_references + +package object yaml { + + import de.zalando.play.controllers.ArrayWrapper + + + + type ModelSchemaSpecialDescriptionsOpt = ArrayWrapper[String] + type MetaCopyright = Option[String] + type ModelSchemaKeywords = Option[String] + type ModelSchemaSpecialDescriptions = Option[ModelSchemaSpecialDescriptionsOpt] + type ModelSchemaRootData = Option[ModelSchemaRootDataOpt] + type PetId = Option[Long] + type ModelSchemaRootLinks = Option[ModelSchemaRootLinksOpt] + type PetTags = Option[PetTagsOpt] + type PetPhotoUrls = Seq[String] + type ModelSchemaLengthRegister = Option[String] + type ModelSchemaAgeGroups = ArrayWrapper[String] + type PetCategory = Option[PetCategoryOpt] + type PetTagsOpt = Seq[PetCategoryOpt] + type ModelSchemaRootMeta = Option[ModelSchemaRootMetaOpt] + + + case class PetCategoryOpt(id: PetId, name: MetaCopyright) + case class ModelSchemaRootDataOpt(name: String, sizeRegister: String, brand: String, partnerArticleModelId: Int, silhouetteId: String, description: MetaCopyright, ageGroups: ModelSchemaAgeGroups, keywords: ModelSchemaKeywords, lengthRegister: ModelSchemaLengthRegister, specialDescriptions: ModelSchemaSpecialDescriptions, articleModelAttributes: ModelSchemaSpecialDescriptions) + case class ModelSchemaRootMetaOpt(copyright: MetaCopyright) + case class ModelSchemaRoot(data: ModelSchemaRootData, meta: ModelSchemaRootMeta, links: ModelSchemaRootLinks) + case class Pet(name: String, tags: PetTags, photoUrls: PetPhotoUrls, id: PetId, status: MetaCopyright, category: PetCategory) + case class ModelSchemaRootLinksOpt(self: MetaCopyright, related: MetaCopyright) + + +} diff --git a/compiler/src/test/resources/expected_results/test_data/cross_spec_references.yaml.scala b/compiler/src/test/resources/expected_results/test_data/cross_spec_references.yaml.scala new file mode 100644 index 0000000..adb6dbd --- /dev/null +++ b/compiler/src/test/resources/expected_results/test_data/cross_spec_references.yaml.scala @@ -0,0 +1,99 @@ +package cross_spec_references.yaml + +import org.scalacheck.Gen +import org.scalacheck.Arbitrary +import Arbitrary._ +import de.zalando.play.controllers.ArrayWrapper + +object Generators { + + + + def createModelSchemaSpecialDescriptionsOptGenerator = _generate(ModelSchemaSpecialDescriptionsOptGenerator) + def createMetaCopyrightGenerator = _generate(MetaCopyrightGenerator) + def createModelSchemaKeywordsGenerator = _generate(ModelSchemaKeywordsGenerator) + def createModelSchemaSpecialDescriptionsGenerator = _generate(ModelSchemaSpecialDescriptionsGenerator) + def createModelSchemaRootDataGenerator = _generate(ModelSchemaRootDataGenerator) + def createPetIdGenerator = _generate(PetIdGenerator) + def createModelSchemaRootLinksGenerator = _generate(ModelSchemaRootLinksGenerator) + def createPetTagsGenerator = _generate(PetTagsGenerator) + def createPetPhotoUrlsGenerator = _generate(PetPhotoUrlsGenerator) + def createModelSchemaLengthRegisterGenerator = _generate(ModelSchemaLengthRegisterGenerator) + def createModelSchemaAgeGroupsGenerator = _generate(ModelSchemaAgeGroupsGenerator) + def createPetCategoryGenerator = _generate(PetCategoryGenerator) + def createPetTagsOptGenerator = _generate(PetTagsOptGenerator) + def createModelSchemaRootMetaGenerator = _generate(ModelSchemaRootMetaGenerator) + + + + def ModelSchemaSpecialDescriptionsOptGenerator = _genList(arbitrary[String], "csv") + def MetaCopyrightGenerator = Gen.option(arbitrary[String]) + def ModelSchemaKeywordsGenerator = Gen.option(arbitrary[String]) + def ModelSchemaSpecialDescriptionsGenerator = Gen.option(ModelSchemaSpecialDescriptionsOptGenerator) + def ModelSchemaRootDataGenerator = Gen.option(ModelSchemaRootDataOptGenerator) + def PetIdGenerator = Gen.option(arbitrary[Long]) + def ModelSchemaRootLinksGenerator = Gen.option(ModelSchemaRootLinksOptGenerator) + def PetTagsGenerator = Gen.option(PetTagsOptGenerator) + def PetPhotoUrlsGenerator = Gen.containerOf[List,String](arbitrary[String]) + def ModelSchemaLengthRegisterGenerator = Gen.option(arbitrary[String]) + def ModelSchemaAgeGroupsGenerator = _genList(arbitrary[String], "csv") + def PetCategoryGenerator = Gen.option(PetCategoryOptGenerator) + def PetTagsOptGenerator = Gen.containerOf[List,PetCategoryOpt](PetCategoryOptGenerator) + def ModelSchemaRootMetaGenerator = Gen.option(ModelSchemaRootMetaOptGenerator) + + + def createPetCategoryOptGenerator = _generate(PetCategoryOptGenerator) + def createModelSchemaRootDataOptGenerator = _generate(ModelSchemaRootDataOptGenerator) + def createModelSchemaRootMetaOptGenerator = _generate(ModelSchemaRootMetaOptGenerator) + def createModelSchemaRootGenerator = _generate(ModelSchemaRootGenerator) + def createPetGenerator = _generate(PetGenerator) + def createModelSchemaRootLinksOptGenerator = _generate(ModelSchemaRootLinksOptGenerator) + + + def PetCategoryOptGenerator = for { + id <- PetIdGenerator + name <- MetaCopyrightGenerator + } yield PetCategoryOpt(id, name) + def ModelSchemaRootDataOptGenerator = for { + name <- arbitrary[String] + sizeRegister <- arbitrary[String] + brand <- arbitrary[String] + partnerArticleModelId <- arbitrary[Int] + silhouetteId <- arbitrary[String] + description <- MetaCopyrightGenerator + ageGroups <- ModelSchemaAgeGroupsGenerator + keywords <- ModelSchemaKeywordsGenerator + lengthRegister <- ModelSchemaLengthRegisterGenerator + specialDescriptions <- ModelSchemaSpecialDescriptionsGenerator + articleModelAttributes <- ModelSchemaSpecialDescriptionsGenerator + } yield ModelSchemaRootDataOpt(name, sizeRegister, brand, partnerArticleModelId, silhouetteId, description, ageGroups, keywords, lengthRegister, specialDescriptions, articleModelAttributes) + def ModelSchemaRootMetaOptGenerator = for { + copyright <- MetaCopyrightGenerator + } yield ModelSchemaRootMetaOpt(copyright) + def ModelSchemaRootGenerator = for { + data <- ModelSchemaRootDataGenerator + meta <- ModelSchemaRootMetaGenerator + links <- ModelSchemaRootLinksGenerator + } yield ModelSchemaRoot(data, meta, links) + def PetGenerator = for { + name <- arbitrary[String] + tags <- PetTagsGenerator + photoUrls <- PetPhotoUrlsGenerator + id <- PetIdGenerator + status <- MetaCopyrightGenerator + category <- PetCategoryGenerator + } yield Pet(name, tags, photoUrls, id, status, category) + def ModelSchemaRootLinksOptGenerator = for { + self <- MetaCopyrightGenerator + related <- MetaCopyrightGenerator + } yield ModelSchemaRootLinksOpt(self, related) + + def _generate[T](gen: Gen[T]) = (count: Int) => for (i <- 1 to count) yield gen.sample + + def _genList[T](gen: Gen[T], format: String): Gen[ArrayWrapper[T]] = for { + items <- Gen.containerOf[List,T](gen) + } yield ArrayWrapper(format)(items) + + + +} \ No newline at end of file diff --git a/compiler/src/test/resources/expected_results/tests/cross_spec_references.yaml.scala b/compiler/src/test/resources/expected_results/tests/cross_spec_references.yaml.scala new file mode 100644 index 0000000..f3a73d0 --- /dev/null +++ b/compiler/src/test/resources/expected_results/tests/cross_spec_references.yaml.scala @@ -0,0 +1,161 @@ +package cross_spec_references.yaml + +import de.zalando.play.controllers._ +import org.scalacheck._ +import org.scalacheck.Arbitrary._ +import org.scalacheck.Prop._ +import org.scalacheck.Test._ +import org.specs2.mutable._ +import play.api.test.Helpers._ +import play.api.test._ +import play.api.mvc.MultipartFormData.FilePart +import play.api.mvc._ + +import org.junit.runner.RunWith +import org.specs2.runner.JUnitRunner +import java.net.URLEncoder +import com.fasterxml.jackson.databind.ObjectMapper + +import play.api.http.Writeable +import play.api.libs.Files.TemporaryFile +import play.api.test.Helpers.{status => requestStatusCode_} +import play.api.test.Helpers.{contentAsString => requestContentAsString_} +import play.api.test.Helpers.{contentType => requestContentType_} + +import de.zalando.play.controllers.ArrayWrapper + +import Generators._ + + @RunWith(classOf[JUnitRunner]) + class Cross_spec_referencesYamlSpec extends Specification { + def toPath[T](value: T)(implicit binder: PathBindable[T]): String = Option(binder.unbind("", value)).getOrElse("") + def toQuery[T](key: String, value: T)(implicit binder: QueryStringBindable[T]): String = Option(binder.unbind(key, value)).getOrElse("") + def toHeader[T](value: T)(implicit binder: PathBindable[T]): String = Option(binder.unbind("", value)).getOrElse("") + + def checkResult(props: Prop) = + Test.check(Test.Parameters.default, props).status match { + case Failed(_, labels) => failure(labels.mkString("\n")) + case Proved(_) | Exhausted | Passed => success + case PropException(_, e, labels) => + val error = if (labels.isEmpty) e.getLocalizedMessage() else labels.mkString("\n") + failure(error) + } + + private def parserConstructor(mimeType: String) = PlayBodyParsing.jacksonMapper(mimeType) + + def parseResponseContent[T](mapper: ObjectMapper, content: String, mimeType: Option[String], expectedType: Class[T]) = + mapper.readValue(content, expectedType) + + + "POST /" should { + def testInvalidInput(root: ModelSchemaRoot) = { + + + val url = s"""/""" + val acceptHeaders: Seq[String] = Seq() + val propertyList = acceptHeaders.map { acceptHeader => + val headers = + Seq() :+ ("Accept" -> acceptHeader) + + val parsed_root = PlayBodyParsing.jacksonMapper("application/json").writeValueAsString(root) + + val request = FakeRequest(POST, url).withHeaders(headers:_*).withBody(parsed_root) + val path = + if (acceptHeader == "multipart/form-data") { + import de.zalando.play.controllers.WriteableWrapper.anyContentAsMultipartFormWritable + + val files: Seq[FilePart[TemporaryFile]] = Nil + val data = Map.empty[String, Seq[String]] + val form = new MultipartFormData(data, files, Nil, Nil) + + route(request.withMultipartFormDataBody(form)).get + } else if (acceptHeader == "application/x-www-form-urlencoded") { + val form = Nil + route(request.withFormUrlEncodedBody(form:_*)).get + } else route(request).get + + val errors = new PostValidator(root).errors + + lazy val validations = errors flatMap { _.messages } map { m => contentAsString(path).contains(m) ?= true } + + ("given 'Accept' header '" + acceptHeader + "' and URL: [" + url + "]" + "and body [" + parsed_root + "]") |: all( + requestStatusCode_(path) ?= BAD_REQUEST , + requestContentType_(path) ?= Some(acceptHeader), + errors.nonEmpty ?= true, + all(validations:_*) + ) + } + if (propertyList.isEmpty) throw new IllegalStateException(s"No 'produces' defined for the $url") + propertyList.reduce(_ && _) + } + def testValidInput(root: ModelSchemaRoot) = { + + val parsed_root = parserConstructor("application/json").writeValueAsString(root) + + val url = s"""/""" + val acceptHeaders: Seq[String] = Seq() + val propertyList = acceptHeaders.map { acceptHeader => + val headers = + Seq() :+ ("Accept" -> acceptHeader) + + val request = FakeRequest(POST, url).withHeaders(headers:_*).withBody(parsed_root) + val path = + if (acceptHeader == "multipart/form-data") { + import de.zalando.play.controllers.WriteableWrapper.anyContentAsMultipartFormWritable + + val files: Seq[FilePart[TemporaryFile]] = Nil + val data = Map.empty[String, Seq[String]] + val form = new MultipartFormData(data, files, Nil, Nil) + + route(request.withMultipartFormDataBody(form)).get + } else if (acceptHeader == "application/x-www-form-urlencoded") { + val form = Nil + route(request.withFormUrlEncodedBody(form:_*)).get + } else route(request).get + + val errors = new PostValidator(root).errors + val possibleResponseTypes: Map[Int,Class[_ <: Any]] = Map( + 200 -> classOf[Pet] + ) + + val expectedCode = requestStatusCode_(path) + val mimeType = requestContentType_(path) + val mapper = parserConstructor(mimeType.getOrElse("application/json")) + + val parsedApiResponse = scala.util.Try { + parseResponseContent(mapper, requestContentAsString_(path), mimeType, possibleResponseTypes(expectedCode)) + } + + ("given response code " + expectedCode + " and 'Accept' header '" + acceptHeader + "' and URL: [" + url + "]" + "and body [" + parsed_root + "]") |: all( + possibleResponseTypes.contains(expectedCode) ?= true, + parsedApiResponse.isSuccess ?= true, + requestContentType_(path) ?= Some(acceptHeader), + errors.isEmpty ?= true + ) + } + if (propertyList.isEmpty) throw new IllegalStateException(s"No 'produces' defined for the $url") + propertyList.reduce(_ && _) + } + "discard invalid data" in new WithApplication { + val genInputs = for { + root <- ModelSchemaRootGenerator + } yield root + val inputs = genInputs suchThat { root => + new PostValidator(root).errors.nonEmpty + } + val props = forAll(inputs) { i => testInvalidInput(i) } + checkResult(props) + } + "do something with valid data" in new WithApplication { + val genInputs = for { + root <- ModelSchemaRootGenerator + } yield root + val inputs = genInputs suchThat { root => + new PostValidator(root).errors.isEmpty + } + val props = forAll(inputs) { i => testValidInput(i) } + checkResult(props) + } + + } +} diff --git a/compiler/src/test/resources/expected_results/types/cross_spec_references.yaml.types b/compiler/src/test/resources/expected_results/types/cross_spec_references.yaml.types new file mode 100644 index 0000000..7a40a97 --- /dev/null +++ b/compiler/src/test/resources/expected_results/types/cross_spec_references.yaml.types @@ -0,0 +1,31 @@ +⌿paths⌿/⌿post⌿root -> + TypeDef(⌿definitions⌿ModelSchemaRoot, Seq( + Field(⌿definitions⌿ModelSchemaRoot⌿data, Opt(TypeDef(⌿definitions⌿ModelSchema, Seq( + Field(⌿definitions⌿ModelSchema⌿name, Str), + Field(⌿definitions⌿ModelSchema⌿description, Opt(Str)), + Field(⌿definitions⌿ModelSchema⌿sizeRegister, Str), + Field(⌿definitions⌿ModelSchema⌿brand, Str), + Field(⌿definitions⌿ModelSchema⌿ageGroups, Arr(Str)), + Field(⌿definitions⌿ModelSchema⌿partnerArticleModelId, Intgr), + Field(⌿definitions⌿ModelSchema⌿keywords, Opt(Str)), + Field(⌿definitions⌿ModelSchema⌿lengthRegister, Opt(Str)), + Field(⌿definitions⌿ModelSchema⌿silhouetteId, Str), + Field(⌿definitions⌿ModelSchema⌿specialDescriptions, Opt(Arr(Str))), + Field(⌿definitions⌿ModelSchema⌿articleModelAttributes, Opt(Arr(Str))))))), + Field(⌿definitions⌿ModelSchemaRoot⌿meta, Opt(TypeDef(⌿definitions⌿Meta, Seq( + Field(⌿definitions⌿Meta⌿copyright, Opt(Str)))))), + Field(⌿definitions⌿ModelSchemaRoot⌿links, Opt(TypeDef(⌿definitions⌿Links, Seq( + Field(⌿definitions⌿Links⌿self, Opt(Str)), + Field(⌿definitions⌿Links⌿related, Opt(Str)))))))) +⌿paths⌿/⌿post⌿responses⌿200 -> + TypeDef(⌿definitions⌿Pet, Seq( + Field(⌿definitions⌿Pet⌿name, Str), + Field(⌿definitions⌿Pet⌿tags, Opt(ArrResult(TypeDef(⌿definitions⌿Tag, Seq( + Field(⌿definitions⌿Tag⌿id, Opt(Lng)), + Field(⌿definitions⌿Tag⌿name, Opt(Str))))))), + Field(⌿definitions⌿Pet⌿photoUrls, ArrResult(Str)), + Field(⌿definitions⌿Pet⌿id, Opt(Lng)), + Field(⌿definitions⌿Pet⌿status, Opt(Str)), + Field(⌿definitions⌿Pet⌿category, Opt(TypeDef(⌿definitions⌿Category, Seq( + Field(⌿definitions⌿Category⌿id, Opt(Lng)), + Field(⌿definitions⌿Category⌿name, Opt(Str)))))))) \ No newline at end of file diff --git a/compiler/src/test/resources/expected_results/validation/cross_spec_references.yaml.scala b/compiler/src/test/resources/expected_results/validation/cross_spec_references.yaml.scala new file mode 100644 index 0000000..2b36066 --- /dev/null +++ b/compiler/src/test/resources/expected_results/validation/cross_spec_references.yaml.scala @@ -0,0 +1,158 @@ +package cross_spec_references.yaml +import play.api.mvc.{Action, Controller} +import play.api.data.validation.Constraint +import de.zalando.play.controllers._ +import PlayBodyParsing._ +import PlayValidations._ + +import de.zalando.play.controllers.ArrayWrapper +// ----- constraints and wrapper validations ----- +class ModelSchemaNameConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq() +} +class ModelSchemaNameValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaNameConstraints(instance)) +} +class ModelSchemaSizeRegisterConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq(maxLength(10), minLength(10), pattern("""/[1-9][A-Z0-9]*/""".r)) +} +class ModelSchemaSizeRegisterValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaSizeRegisterConstraints(instance)) +} +class ModelSchemaBrandConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq(maxLength(3), minLength(3), pattern("""/[A-Z0-9]{3,3}/""".r)) +} +class ModelSchemaBrandValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaBrandConstraints(instance)) +} +class ModelSchemaPartnerArticleModelIdConstraints(override val instance: Int) extends ValidationBase[Int] { + override def constraints: Seq[Constraint[Int]] = + Seq() +} +class ModelSchemaPartnerArticleModelIdValidator(instance: Int) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaPartnerArticleModelIdConstraints(instance)) +} +class ModelSchemaSilhouetteIdConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq(enum("ankle_boots,nightdress,low_shoe,ballerina_shoe,voucher,belt,skates,eye_cosmetic,dress,sleeping_bag,system,other_accessoires,bag,etui,bikini_top,hair,undershirt,bathroom,bedroom,one_piece_nightwear,combination_clothing,sun,t_shirt_top,watch,night_shirt,pumps,stocking,boots,beach_trouser,tent,lip_cosmetic,underpant,skincare,backpack,pullover,lounge,sandals,suit_accessoires,coat,other_equipment,beach_shirt,bicycle,ski,cardigan,protector,beach_accessoires,jacket,one_piece_beachwear,headgear,shoe_accessoires,sneaker,headphones,kitchen,bicycle_equipment,ball,nightwear_combination,fitness,tights,one_piece_suit,vest,bustier,first_shoe,one_piece_underwear,bikini_combination,face_cosmetic,fragrance,glasses,shirt,trouser,racket,travel_equipment,case,backless_slipper,umbrella,underwear_combination,jewellery,shave,skirt,bathrobe,wallet,cleansing,night_trouser,corsage,peeling,beauty_equipment,nail,toys,bra,gloves,living,keychain,scarf,boards")) +} +class ModelSchemaSilhouetteIdValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaSilhouetteIdConstraints(instance)) +} +class MetaCopyrightOptConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq() +} +class MetaCopyrightOptValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new MetaCopyrightOptConstraints(instance)) +} +class ModelSchemaAgeGroupsArrConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq(enum("baby,kid,teen,adult")) +} +class ModelSchemaAgeGroupsArrValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaAgeGroupsArrConstraints(instance)) +} +class ModelSchemaKeywordsOptConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq(pattern("""/([\w\s]{1,255}|([\w\s]{1,255}, )+[\w\s]{1,255})/""".r)) +} +class ModelSchemaKeywordsOptValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaKeywordsOptConstraints(instance)) +} +class ModelSchemaLengthRegisterOptConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq(maxLength(10), minLength(10), pattern("""/[1-9][A-Z0-9]*/""".r)) +} +class ModelSchemaLengthRegisterOptValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaLengthRegisterOptConstraints(instance)) +} +class ModelSchemaSpecialDescriptionsOptArrConstraints(override val instance: String) extends ValidationBase[String] { + override def constraints: Seq[Constraint[String]] = + Seq() +} +class ModelSchemaSpecialDescriptionsOptArrValidator(instance: String) extends RecursiveValidator { + override val validators = Seq(new ModelSchemaSpecialDescriptionsOptArrConstraints(instance)) +} +// ----- complex type validators ----- +class PostRootValidator(instance: ModelSchemaRoot) extends RecursiveValidator { + override val validators = Seq( + new ModelSchemaRootDataValidator(instance.data), + new ModelSchemaRootMetaValidator(instance.meta), + new ModelSchemaRootLinksValidator(instance.links) + ) +} +class ModelSchemaRootDataOptValidator(instance: ModelSchemaRootDataOpt) extends RecursiveValidator { + override val validators = Seq( + new ModelSchemaNameValidator(instance.name), + new ModelSchemaSizeRegisterValidator(instance.sizeRegister), + new ModelSchemaBrandValidator(instance.brand), + new ModelSchemaPartnerArticleModelIdValidator(instance.partnerArticleModelId), + new ModelSchemaSilhouetteIdValidator(instance.silhouetteId), + new MetaCopyrightValidator(instance.description), + new ModelSchemaAgeGroupsValidator(instance.ageGroups), + new ModelSchemaKeywordsValidator(instance.keywords), + new ModelSchemaLengthRegisterValidator(instance.lengthRegister), + new ModelSchemaSpecialDescriptionsValidator(instance.specialDescriptions), + new ModelSchemaSpecialDescriptionsValidator(instance.articleModelAttributes) + ) +} +class ModelSchemaRootMetaOptValidator(instance: ModelSchemaRootMetaOpt) extends RecursiveValidator { + override val validators = Seq( + new MetaCopyrightValidator(instance.copyright) + ) +} +class ModelSchemaRootLinksOptValidator(instance: ModelSchemaRootLinksOpt) extends RecursiveValidator { + override val validators = Seq( + new MetaCopyrightValidator(instance.self), + new MetaCopyrightValidator(instance.related) + ) +} +// ----- option delegating validators ----- +class ModelSchemaRootDataValidator(instance: ModelSchemaRootData) extends RecursiveValidator { + override val validators = instance.toSeq.map { new ModelSchemaRootDataOptValidator(_) } +} +class MetaCopyrightValidator(instance: MetaCopyright) extends RecursiveValidator { + override val validators = instance.toSeq.map { new MetaCopyrightOptValidator(_) } +} +class ModelSchemaKeywordsValidator(instance: ModelSchemaKeywords) extends RecursiveValidator { + override val validators = instance.toSeq.map { new ModelSchemaKeywordsOptValidator(_) } +} +class ModelSchemaLengthRegisterValidator(instance: ModelSchemaLengthRegister) extends RecursiveValidator { + override val validators = instance.toSeq.map { new ModelSchemaLengthRegisterOptValidator(_) } +} +class ModelSchemaSpecialDescriptionsValidator(instance: ModelSchemaSpecialDescriptions) extends RecursiveValidator { + override val validators = instance.toSeq.map { new ModelSchemaSpecialDescriptionsOptValidator(_) } +} +class ModelSchemaRootMetaValidator(instance: ModelSchemaRootMeta) extends RecursiveValidator { + override val validators = instance.toSeq.map { new ModelSchemaRootMetaOptValidator(_) } +} +class ModelSchemaRootLinksValidator(instance: ModelSchemaRootLinks) extends RecursiveValidator { + override val validators = instance.toSeq.map { new ModelSchemaRootLinksOptValidator(_) } +} +// ----- array delegating validators ----- +class ModelSchemaAgeGroupsConstraints(override val instance: ModelSchemaAgeGroups) extends ValidationBase[ModelSchemaAgeGroups] { + override def constraints: Seq[Constraint[ModelSchemaAgeGroups]] = + Seq(maxItems(4)) +} +class ModelSchemaAgeGroupsValidator(instance: ModelSchemaAgeGroups) extends RecursiveValidator { + override val validators = new ModelSchemaAgeGroupsConstraints(instance) +: instance.map { new ModelSchemaAgeGroupsArrValidator(_)} +} +class ModelSchemaSpecialDescriptionsOptConstraints(override val instance: ModelSchemaSpecialDescriptionsOpt) extends ValidationBase[ModelSchemaSpecialDescriptionsOpt] { + override def constraints: Seq[Constraint[ModelSchemaSpecialDescriptionsOpt]] = + Seq() +} +class ModelSchemaSpecialDescriptionsOptValidator(instance: ModelSchemaSpecialDescriptionsOpt) extends RecursiveValidator { + override val validators = new ModelSchemaSpecialDescriptionsOptConstraints(instance) +: instance.map { new ModelSchemaSpecialDescriptionsOptArrValidator(_)} +} +// ----- catch all simple validators ----- +// ----- call validations ----- +class PostValidator(root: ModelSchemaRoot) extends RecursiveValidator { + override val validators = Seq( + new PostRootValidator(root) + + ) +} diff --git a/compiler/src/test/scala/de/zalando/apifirst/generators/ScalaModelGeneratorIntegrationTest.scala b/compiler/src/test/scala/de/zalando/apifirst/generators/ScalaModelGeneratorIntegrationTest.scala index 2ae6789..867fc1e 100644 --- a/compiler/src/test/scala/de/zalando/apifirst/generators/ScalaModelGeneratorIntegrationTest.scala +++ b/compiler/src/test/scala/de/zalando/apifirst/generators/ScalaModelGeneratorIntegrationTest.scala @@ -39,5 +39,5 @@ class ScalaModelGeneratorIntegrationTest extends FunSpec with MustMatchers with } } - def clean(str: String) = str.split("\n").map(_.trim).filter(_.nonEmpty).mkString("\n") + def clean(str: String): String = str.split("\n").map(_.trim).filter(_.nonEmpty).mkString("\n") } diff --git a/plugin/src/sbt-test/swagger/compile/conf/cross_spec_references.yaml b/plugin/src/sbt-test/swagger/compile/conf/cross_spec_references.yaml new file mode 100644 index 0000000..c5e9123 --- /dev/null +++ b/plugin/src/sbt-test/swagger/compile/conf/cross_spec_references.yaml @@ -0,0 +1,22 @@ +swagger: '2.0' +info: + version: "1.0.0" + title: An Example of cross - specification references +host: localhost:9000 +schemes: + - http +paths: + /: + post: + parameters: + - name: root + in: body + required: true + schema: + $ref: "hackweek.yaml#/definitions/ModelSchemaRoot" + responses: + 200: + description: Must have correct imports for cross-references specifications + schema: + $ref: "parts/split.petstore.definitions.yaml#/definitions/Pet" +