Skip to content

Commit

Permalink
fix anorm for capitalized field names (apicollective#371)
Browse files Browse the repository at this point in the history
  • Loading branch information
benwaffle authored and mbryzek committed Mar 12, 2018
1 parent 6145e24 commit 413d706
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 2 deletions.
85 changes: 85 additions & 0 deletions lib/src/test/resources/generator/anorm/cap-name-conversions-26.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Generated by API Builder - https://www.apibuilder.io
* Service version: 1.0.0
*/
package test.apidoc.apidoctest.v0.anorm.conversions {

import anorm.{Column, MetaDataItem, TypeDoesNotMatch}
import play.api.libs.json.{JsArray, JsObject, JsValue}
import scala.util.{Failure, Success, Try}
import play.api.libs.json.JodaReads._

/**
* Conversions to collections of objects using JSON.
*/
object Util {

def parser[T](
f: play.api.libs.json.JsValue => T
) = anorm.Column.nonNull { (value, meta) =>
val MetaDataItem(columnName, nullable, clazz) = meta
value match {
case json: org.postgresql.util.PGobject => parseJson(f, columnName.qualified, json.getValue)
case json: java.lang.String => parseJson(f, columnName.qualified, json)
case _=> {
Left(
TypeDoesNotMatch(
s"Column[${columnName.qualified}] error converting $value to Json. Expected instance of type[org.postgresql.util.PGobject] and not[${value.asInstanceOf[AnyRef].getClass}]"
)
)
}


}
}

private[this] def parseJson[T](f: play.api.libs.json.JsValue => T, columnName: String, value: String) = {
Try {
f(
play.api.libs.json.Json.parse(value)
)
} match {
case Success(result) => Right(result)
case Failure(ex) => Left(
TypeDoesNotMatch(
s"Column[$columnName] error parsing json $value: $ex"
)
)
}
}

}

object Types {
import test.apidoc.apidoctest.v0.models.json._
implicit val columnToSeqApiDocTestName: Column[Seq[_root_.test.apidoc.apidoctest.v0.models.Name]] = Util.parser { _.as[Seq[_root_.test.apidoc.apidoctest.v0.models.Name]] }
implicit val columnToMapApiDocTestName: Column[Map[String, _root_.test.apidoc.apidoctest.v0.models.Name]] = Util.parser { _.as[Map[String, _root_.test.apidoc.apidoctest.v0.models.Name]] }
}

object Standard {
implicit val columnToJsObject: Column[play.api.libs.json.JsObject] = Util.parser { _.as[play.api.libs.json.JsObject] }
implicit val columnToSeqBoolean: Column[Seq[Boolean]] = Util.parser { _.as[Seq[Boolean]] }
implicit val columnToMapBoolean: Column[Map[String, Boolean]] = Util.parser { _.as[Map[String, Boolean]] }
implicit val columnToSeqDouble: Column[Seq[Double]] = Util.parser { _.as[Seq[Double]] }
implicit val columnToMapDouble: Column[Map[String, Double]] = Util.parser { _.as[Map[String, Double]] }
implicit val columnToSeqInt: Column[Seq[Int]] = Util.parser { _.as[Seq[Int]] }
implicit val columnToMapInt: Column[Map[String, Int]] = Util.parser { _.as[Map[String, Int]] }
implicit val columnToSeqLong: Column[Seq[Long]] = Util.parser { _.as[Seq[Long]] }
implicit val columnToMapLong: Column[Map[String, Long]] = Util.parser { _.as[Map[String, Long]] }
implicit val columnToSeqLocalDate: Column[Seq[_root_.org.joda.time.LocalDate]] = Util.parser { _.as[Seq[_root_.org.joda.time.LocalDate]] }
implicit val columnToMapLocalDate: Column[Map[String, _root_.org.joda.time.LocalDate]] = Util.parser { _.as[Map[String, _root_.org.joda.time.LocalDate]] }
implicit val columnToSeqDateTime: Column[Seq[_root_.org.joda.time.DateTime]] = Util.parser { _.as[Seq[_root_.org.joda.time.DateTime]] }
implicit val columnToMapDateTime: Column[Map[String, _root_.org.joda.time.DateTime]] = Util.parser { _.as[Map[String, _root_.org.joda.time.DateTime]] }
implicit val columnToSeqBigDecimal: Column[Seq[BigDecimal]] = Util.parser { _.as[Seq[BigDecimal]] }
implicit val columnToMapBigDecimal: Column[Map[String, BigDecimal]] = Util.parser { _.as[Map[String, BigDecimal]] }
implicit val columnToSeqJsObject: Column[Seq[_root_.play.api.libs.json.JsObject]] = Util.parser { _.as[Seq[_root_.play.api.libs.json.JsObject]] }
implicit val columnToMapJsObject: Column[Map[String, _root_.play.api.libs.json.JsObject]] = Util.parser { _.as[Map[String, _root_.play.api.libs.json.JsObject]] }
implicit val columnToSeqJsValue: Column[Seq[_root_.play.api.libs.json.JsValue]] = Util.parser { _.as[Seq[_root_.play.api.libs.json.JsValue]] }
implicit val columnToMapJsValue: Column[Map[String, _root_.play.api.libs.json.JsValue]] = Util.parser { _.as[Map[String, _root_.play.api.libs.json.JsValue]] }
implicit val columnToSeqString: Column[Seq[String]] = Util.parser { _.as[Seq[String]] }
implicit val columnToMapString: Column[Map[String, String]] = Util.parser { _.as[Map[String, String]] }
implicit val columnToSeqUUID: Column[Seq[_root_.java.util.UUID]] = Util.parser { _.as[Seq[_root_.java.util.UUID]] }
implicit val columnToMapUUID: Column[Map[String, _root_.java.util.UUID]] = Util.parser { _.as[Map[String, _root_.java.util.UUID]] }
}

}
35 changes: 35 additions & 0 deletions lib/src/test/resources/generator/anorm/cap-name.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Generated by API Builder - https://www.apibuilder.io
* Service version: 1.0.0
*/
import anorm._

package test.apidoc.apidoctest.v0.anorm.parsers {

import test.apidoc.apidoctest.v0.anorm.conversions.Standard._

import test.apidoc.apidoctest.v0.anorm.conversions.Types._

object Name {

def parserWithPrefix(prefix: String, sep: String = "_"): RowParser[test.apidoc.apidoctest.v0.models.Name] = parser(prefixOpt = Some(s"$prefix$sep"))

def parser(
First: String = "First",
Last: String = "Last",
prefixOpt: Option[String] = None
): RowParser[test.apidoc.apidoctest.v0.models.Name] = {
SqlParser.str(prefixOpt.getOrElse("") + First).? ~
SqlParser.str(prefixOpt.getOrElse("") + Last).? map {
case first ~ last => {
test.apidoc.apidoctest.v0.models.Name(
First = first,
Last = last
)
}
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,10 @@ trait ParserGenerator extends CodeGenerator {
* is a keyword, we append Instance (back ticks will not compile)
*/
private[this] def parserName(field: ScalaField): String = {
ScalaUtil.isKeyword(field.originalName) match {
Text.initLowerCase(ScalaUtil.isKeyword(field.originalName) match {
case true => Text.snakeToCamelCase(field.originalName) + "Instance"
case false => field.name
}
})
}

private[this] def generateUnion(union: ScalaUnion): String = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ class ParserGenerator26Spec extends FunSpec with Matchers {
}
"""

val capModel = """
{
"name": "name",
"plural": "names",
"attributes": [],
"fields": [
{ "name": "First", "type": "string", "required": false, "attributes": [] },
{ "name": "Last", "type": "string", "required": false, "attributes": [] }
]
}
"""

case class ServiceBuilder(
unions: Seq[String] = Nil,
models: Seq[String] = Nil,
Expand Down Expand Up @@ -120,6 +132,20 @@ class ParserGenerator26Spec extends FunSpec with Matchers {
}
}

it("model with capitalized fields") {
val form = ServiceBuilder(models = Seq(capModel)).form
ParserGenerator26.invoke(form) match {
case Left(errors) => {
fail(errors.mkString(", "))
}
case Right(files) => {
files.map(_.name) should be(fileNames)
models.TestHelper.assertEqualsFile("/generator/anorm/cap-name-conversions-26.txt", files.head.contents)
models.TestHelper.assertEqualsFile("/generator/anorm/cap-name.txt", files.last.contents)
}
}
}

it("composite model") {
val form = ServiceBuilder(models = Seq(nameModel)).addModel("""
{
Expand Down

0 comments on commit 413d706

Please sign in to comment.