Skip to content
This repository has been archived by the owner on Apr 10, 2019. It is now read-only.

Commit

Permalink
#112, chore: Merging downstream changes into the branch
Browse files Browse the repository at this point in the history
  • Loading branch information
slavaschmidt committed May 10, 2016
2 parents de001ac + c6f54d7 commit 4d9f1ad
Show file tree
Hide file tree
Showing 187 changed files with 15,742 additions and 5,086 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ addons:
- oracle-java8-installer

before_install:
# - pip install --user codecov
- pip install --user codecov

after_success:
# - codecov
- codecov

script:
- sbt -jvm-opts travis/jvmopts compile test +publishLocal
- sbt -jvm-opts travis/jvmopts scripted
- sbt -jvm-opts travis/jvmopts < travis_coverage_script.txt


# cache:
# directories:
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Play-Swagger

[![Build Status](https://travis-ci.org/zalando/play-swagger.svg)](https://travis-ci.org/zalando/play-swagger)
[![Codecov](https://img.shields.io/codecov/c/github/zalando/play-swagger.svg)](http://codecov.io/github/zalando/play-swagger)
[![codecov](https://codecov.io/gh/zalando/play-swagger/branch/master/graph/badge.svg)](https://codecov.io/gh/zalando/play-swagger)
[![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/zalando/play-swagger?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

## Status
Expand Down Expand Up @@ -786,3 +786,11 @@ sbt doesn't allow sub-projects to depend on each other as sbt plugins. To test a
## Plugin Testing

We're using the sbt scripted framework for testing. You can find the tests in `plugin/src/sbt-test`, and run them by running `scripted` in the sbt console.

## Code quality

There are some quality checks embedded into the build script:
* the source code is (re)formatted using scalariform each time it is compiled.
* `scalastyle` sbt command shall be used to perform code style checks before putting changes into the repository.
* `lint:compile` sbt command shall be used to perform static code analysis before putting changes into the repository.
* code coverage for api and compiler modules can be executed by issuing `sbt clean coverage test` command for these projects. Coverage statistics can be generatend using `coverageReport` sbt command.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.zalando.play.controllers

import java.nio.file.{Files, Paths}

import play.api.http.{HeaderNames, Writeable}
import play.api.libs.Files.TemporaryFile
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc.{Codec, MultipartFormData}

/**
* @author slasch
* @since 04.05.2016.
*
* taken from <a href="http://tech.fongmun.com/post/125479939452/test-multipartformdata-in-play">here</a>
*/
object MultipartFormDataWritable {
import scala.concurrent.ExecutionContext.Implicits.global

val boundary = "--------ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"

def formatDataParts(data: Map[String, Seq[String]]) = {
val dataParts = data.flatMap { case (key, values) =>
values.map { value =>
val name = s""""$key""""
s"--$boundary\r\n${HeaderNames.CONTENT_DISPOSITION}: form-data; name=$name\r\n\r\n$value\r\n"
}
}.mkString("")
Codec.utf_8.encode(dataParts)
}

def filePartHeader(file: FilePart[TemporaryFile]) = {
val name = s""""${file.key}""""
val filename = s""""${file.filename}""""
val contentType = file.contentType.map { ct =>
s"${HeaderNames.CONTENT_TYPE}: $ct\r\n"
}.getOrElse("")
Codec.utf_8.encode(s"--$boundary\r\n${HeaderNames.CONTENT_DISPOSITION}: form-data; name=$name; filename=$filename\r\n$contentType\r\n")
}

val singleton = Writeable[MultipartFormData[TemporaryFile]](
transform = { form: MultipartFormData[TemporaryFile] =>
formatDataParts(form.dataParts) ++
form.files.flatMap { file =>
val fileBytes = Files.readAllBytes(Paths.get(file.ref.file.getAbsolutePath))
filePartHeader(file) ++ fileBytes ++ Codec.utf_8.encode("\r\n")
} ++
Codec.utf_8.encode(s"--$boundary--")
},
contentType = Some(s"multipart/form-data; boundary=$boundary")
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.zalando.play.controllers

import java.io.File

import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
Expand All @@ -8,14 +10,16 @@ import de.zalando.play.controllers.WrappedBodyParsers.Parser
import play.api.Logger
import play.api.http.Status._
import play.api.http._
import play.api.libs.Files.TemporaryFile
import play.api.libs.iteratee._
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc.Results.Status
import play.api.mvc._

import scala.concurrent.Future
import scala.language.implicitConversions
import scala.reflect.ClassTag
import scala.util.{Left, Right, Either}
import scala.util.{Either, Left, Right}
import scala.util.control.NonFatal

/**
Expand All @@ -28,10 +32,10 @@ object WriterFactories {
* Contains proper Jackson Factories for different mime types
* JsonFactory is a default
*/
val factories = Map(
val factories: Map[String, JsonFactory] = Map(
"application/json" -> jsonFactory,
"text/x-yaml" -> new YAMLFactory() // TODO implement workaround for bug in yaml parser
)
).withDefaultValue(jsonFactory)
}
object PlayBodyParsing extends PlayBodyParsing {

Expand Down Expand Up @@ -65,7 +69,7 @@ object PlayBodyParsing extends PlayBodyParsing {
customParsers: Seq[(String, Parser[Option[T]])],
errorMsg: String, maxLength: Int = parse.DefaultMaxTextLength)
(implicit oTag: ClassTag[Option[T]], tag: ClassTag[T]): BodyParser[Option[T]] =
tolerantBodyParser[Option[T]](maxLength, errorMsg) { (requestHeader, bytes) =>
tolerantBodyParser[Option[T]](maxLength.toLong, errorMsg) { (requestHeader, bytes) =>
if (bytes.nonEmpty) {
parserCore(mimeType, customParsers, requestHeader, bytes)
} else
Expand All @@ -86,7 +90,7 @@ object PlayBodyParsing extends PlayBodyParsing {
customParsers: Seq[(String, Parser[T])],
errorMsg: String, maxLength: Int = parse.DefaultMaxTextLength)
(implicit tag: ClassTag[T]): BodyParser[T] =
tolerantBodyParser[T](maxLength, errorMsg) { (requestHeader, bytes) =>
tolerantBodyParser[T](maxLength.toLong, errorMsg) { (requestHeader, bytes) =>
parserCore(mimeType, customParsers, requestHeader, bytes)
}

Expand Down Expand Up @@ -163,11 +167,21 @@ trait PlayBodyParsing extends BodyParsers {
/**
* Helper method to parse parameters sent as Headers
*/
def fromHeaders[T](key: String, headers: Map[String, Seq[String]], default: Option[T] = None)(implicit binder: QueryStringBindable[T]): Either[String,T] =
def fromParameters[T](place: String)(key: String, headers: Map[String, Seq[String]], default: Option[T] = None)(implicit binder: QueryStringBindable[T]): Either[String,T] =
binder.bind(key, headers).getOrElse {
default.map(d => Right(d)).getOrElse(Left("Missing header parameter(s): " + key))
default.map(d => Right(d)).getOrElse(Left(s"Missing $place parameter(s) for '$key'"))
}

/**
* Helper methods to parse files
*/
def fromFileOptional[T <: Option[File]](name: String, file: Option[FilePart[TemporaryFile]]) = Right(file.map(_.ref.file))

def fromFileRequired[T <: File](name: String, file: Option[FilePart[TemporaryFile]]) = file match {
case Some(filePart) => Right(filePart.ref.file)
case None => Left(s"Missing file parameter for '$name'")
}

/**
* This is private in play codebase. Copy-pasted it.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package de.zalando.play.controllers

import java.util.Base64
import java.io.File

import com.fasterxml.jackson.databind.{MappingIterator, ObjectReader, ObjectWriter}
import com.fasterxml.jackson.dataformat.csv.{CsvMapper, CsvParser, CsvSchema}
import org.joda.time.{DateMidnight, DateTime}
import org.joda.time.{LocalDate, DateTime}
import play.api.mvc.{PathBindable, QueryStringBindable}

import scala.io.Source

/**
* @author slasch
* @since 03.01.2016.
Expand Down Expand Up @@ -40,36 +42,50 @@ object PlayPathBindables {
(key: String, e: Exception) => "Cannot parse parameter %s as DateTime: %s".format(key, e.getMessage)
)

implicit object pathBindableDateMidnight extends PathBindable.Parsing[DateMidnight](
implicit object pathBindableLocalDate extends PathBindable.Parsing[LocalDate](
Rfc3339Util.parseDate,
Rfc3339Util.writeDate,
(key: String, e: Exception) => "Cannot parse parameter %s as DateMidnight: %s".format(key, e.getMessage)
(key: String, e: Exception) => "Cannot parse parameter %s as LocalDate: %s".format(key, e.getMessage)
)
implicit object queryBindableDateTime extends QueryStringBindable.Parsing[DateTime](
Rfc3339Util.parseDateTime,
Rfc3339Util.writeDateTime,
(key: String, e: Exception) => "Cannot parse parameter %s as DateTime: %s".format(key, e.getMessage)
)

implicit object queryBindableDateMidnight extends QueryStringBindable.Parsing[DateMidnight](
implicit object queryBindableLocalDate extends QueryStringBindable.Parsing[LocalDate](
Rfc3339Util.parseDate,
Rfc3339Util.writeDate,
(key: String, e: Exception) => "Cannot parse parameter %s as DateMidnight: %s".format(key, e.getMessage)
(key: String, e: Exception) => "Cannot parse parameter %s as LocalDate: %s".format(key, e.getMessage)
)

implicit object pathBindableBase64String extends PathBindable.Parsing[Base64String](
s => Base64String.string2base64string(s),
s => Base64String.base64string2string(s),
(key: String, e: Exception) => "Cannot parse parameter %s as DateTime: %s".format(key, e.getMessage)
(key: String, e: Exception) => "Cannot parse parameter %s as Base64String: %s".format(key, e.getMessage)
)

implicit object queryBindableBase64String extends QueryStringBindable.Parsing[Base64String](
s => Base64String.string2base64string(s),
s => Base64String.base64string2string(s),
(key: String, e: Exception) => "Cannot parse parameter %s as DateMidnight: %s".format(key, e.getMessage)
(key: String, e: Exception) => "Cannot parse parameter %s as Base64String: %s".format(key, e.getMessage)
)

implicit object queryBindableFile extends QueryStringBindable.Parsing[java.io.File](
s => tempFileFromString(s),
s => Source.fromFile(s).getLines().mkString("\n"),
(key: String, e: Exception) => "Cannot parse parameter %s as java.io.File: %s".format(key, e.getMessage)
)

def tempFileFromString(s: String) = {
val prefix = "tmp_" + s.hashCode
val f = File.createTempFile(prefix.toString, "")
f.deleteOnExit()
import java.nio.file.{Paths, Files}
import java.nio.charset.StandardCharsets
Files.write(Paths.get(f.getAbsolutePath), s.getBytes(StandardCharsets.UTF_8))
f
}
/**
* Factory to create PathBindable for optional values of any type
*
Expand Down
16 changes: 7 additions & 9 deletions api/src/main/scala/de/zalando/play/controllers/Rfc3339Util.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package de.zalando.play.controllers

import org.joda.time.format.DateTimeFormat
import org.joda.time.{DateMidnight, DateTime}

import scala.util.Try
import org.joda.time.{DateTime, LocalDate}

/**
* An utility class for parsing date and date-time inputs as required by RFC3339
Expand All @@ -12,7 +10,7 @@ import scala.util.Try
* which is not completely interchangeable with RFC3339
*
* As we need different types for Dates and DateTimes for implicit conversions to work,
* deliberately using deprecated DateMidnight here. Not sure if it is a problem or not.
* deliberately using LocalDate here.
*
* @author slasch
* @since 04.01.2016.
Expand All @@ -29,18 +27,18 @@ object Rfc3339Util {
if(datestring.endsWith("Z") || datestring.endsWith("z")) parseFull(datestring)
else parseParts(datestring)

def parseDate(datestring: String): DateMidnight =
fullDate.parseDateTime(datestring).toDateMidnight
def parseDate(datestring: String): LocalDate =
fullDate.parseDateTime(datestring).toLocalDate

def writeDate(date: DateMidnight): String = fullDate.print(date)
def writeDate(date: LocalDate): String = fullDate.print(date)

def writeDateTime(date: DateTime): String = dateTime.print(date)

private def parseParts(datestring: String): DateTime = {
//step one, split off the timezone.
val sepChar = if (datestring.indexOf('+')>0) '+' else '-'
val firstpart = datestring.substring(0, datestring.lastIndexOf(sepChar))
val secondpart = datestring.substring(datestring.lastIndexOf(sepChar))
val firstpart = datestring.substring(0, datestring.lastIndexOf(sepChar.toInt))
val secondpart = datestring.substring(datestring.lastIndexOf(sepChar.toInt))
//step two, remove the colon from the timezone offset
val thirdpart = secondpart.substring(0, secondpart.indexOf(':')) + secondpart.substring(secondpart.indexOf(':') + 1)
val dstring = firstpart + thirdpart
Expand Down
Loading

0 comments on commit 4d9f1ad

Please sign in to comment.