Skip to content

Commit 89ef343

Browse files
Merge pull request #147 from blast-hardcheese/fix-issue-145
Fix emptyToNull not working correctly with snake-cased parameters
2 parents 20fb0bc + 175ee17 commit 89ef343

File tree

5 files changed

+85
-10
lines changed

5 files changed

+85
-10
lines changed

modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ case class ProtocolDefinitions[L <: LA](elems: List[StrictProtocolElems[L]],
2121
protocolImports: List[L#Import],
2222
packageObjectImports: List[L#Import],
2323
packageObjectContents: List[L#ValueDefinition])
24+
sealed trait EmptyToNullBehaviour
25+
case object EmptyIsNull extends EmptyToNullBehaviour
26+
case object EmptyIsEmpty extends EmptyToNullBehaviour
2427

2528
case class ProtocolParameter[L <: LA](term: L#MethodParameter,
2629
name: String,
2730
dep: Option[L#TermName],
2831
readOnlyKey: Option[String],
29-
emptyToNullKey: Option[String])
32+
emptyToNull: EmptyToNullBehaviour)
3033

3134
case class SuperClass[L <: LA](
3235
clsName: String,

modules/codegen/src/main/scala/com/twilio/guardrail/extract/Extractable.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package com.twilio.guardrail.extract
2+
import com.twilio.guardrail.{ EmptyIsEmpty, EmptyIsNull, EmptyToNullBehaviour }
23

34
import scala.util.Try
45

@@ -28,4 +29,9 @@ object Extractable {
2829
implicit val defaultExtractableString: Extractable[String] = build[String]({
2930
case x: String => x
3031
})
32+
implicit val defaultExtractableEmptyToNullBehaviour: Extractable[EmptyToNullBehaviour] =
33+
build[EmptyToNullBehaviour]({
34+
case x: Boolean if x => EmptyIsNull
35+
case x: Boolean if ! x => EmptyIsEmpty
36+
})
3137
}

modules/codegen/src/main/scala/com/twilio/guardrail/extract/package.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ package object extract {
99
VendorExtension(v).extract[String]("x-scala-type")
1010
def ServerRawResponse[F: VendorExtension.VendorExtensible](v: F): Option[Boolean] =
1111
VendorExtension(v).extract[Boolean]("x-server-raw-response")
12-
def ScalaEmptyIsNull[F: VendorExtension.VendorExtensible](v: F): Option[Boolean] =
13-
VendorExtension(v).extract[Boolean]("x-scala-empty-is-null")
12+
def ScalaEmptyIsNull[F: VendorExtension.VendorExtensible](v: F): Option[EmptyToNullBehaviour] =
13+
VendorExtension(v).extract[EmptyToNullBehaviour]("x-scala-empty-is-null")
1414
def ScalaFileHashAlgorithm[F: VendorExtension.VendorExtensible](v: F): Option[String] =
1515
VendorExtension(v).extract[String]("x-scala-file-hash")
1616
}

modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,12 @@ object CirceProtocolGenerator {
132132
}
133133

134134
readOnlyKey = Option(name).filter(_ => Option(property.getReadOnly).contains(true))
135-
needsEmptyToNull = property match {
135+
emptyToNull = (property match {
136136
case d: DateProperty => ScalaEmptyIsNull(d)
137137
case dt: DateTimeProperty => ScalaEmptyIsNull(dt)
138138
case s: StringProperty => ScalaEmptyIsNull(s)
139139
case _ => None
140-
}
141-
emptyToNullKey = needsEmptyToNull.filter(_ == true).map(_ => argName)
140+
}).getOrElse(EmptyIsEmpty)
142141

143142
(tpe, classDep) = meta match {
144143
case SwaggerUtil.Resolved(declType, classDep, _) =>
@@ -162,7 +161,7 @@ object CirceProtocolGenerator {
162161
)(Function.const((tpe, defaultValue)) _)
163162
term = param"${Term.Name(argName)}: ${finalDeclType}".copy(default = finalDefaultValue)
164163
dep = classDep.filterNot(_.value == clsName) // Filter out our own class name
165-
} yield ProtocolParameter[ScalaLanguage](term, name, dep, readOnlyKey, emptyToNullKey)
164+
} yield ProtocolParameter[ScalaLanguage](term, name, dep, readOnlyKey, emptyToNull)
166165

167166
case RenderDTOClass(clsName, selfTerms, parents) =>
168167
val discriminators = parents.flatMap(_.discriminators)
@@ -238,9 +237,9 @@ object CirceProtocolGenerator {
238237
val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot(
239238
param => discriminators.contains(param.name)
240239
)
241-
val emptyToNullKeys: List[String] = params.flatMap(_.emptyToNullKey).toList
240+
val needsEmptyToNull: Boolean = params.exists(_.emptyToNull == EmptyIsNull)
242241
val paramCount = params.length
243-
val decVal = if (paramCount <= 22 && emptyToNullKeys.isEmpty) {
242+
val decVal = if (paramCount <= 22 && !needsEmptyToNull) {
244243
val names: List[Lit] = params.map(_.name).map(Lit.String(_)).to[List]
245244
q"""
246245
Decoder.${Term.Name(s"forProduct${paramCount}")}(..${names})(${Term
@@ -258,7 +257,7 @@ object CirceProtocolGenerator {
258257
})
259258
.get
260259
val term = Term.Name(param.term.name.value)
261-
val enum = if (emptyToNullKeys contains param.name) {
260+
val enum = if (param.emptyToNull == EmptyIsNull) {
262261
enumerator"""
263262
${Pat.Var(term)} <- c.downField(${Lit
264263
.String(param.name)}).withFocus(j => j.asString.fold(j)(s => if(s.isEmpty) Json.Null else j)).as[${tpe}]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package core.issues
2+
3+
import com.twilio.guardrail._
4+
import com.twilio.guardrail.generators.AkkaHttp
5+
import org.scalatest.{ FunSpec, Matchers }
6+
import support.SwaggerSpecRunner
7+
8+
import scala.meta._
9+
10+
class Issue145 extends FunSpec with Matchers with SwaggerSpecRunner {
11+
12+
describe("Generate hierarchical classes") {
13+
14+
val swagger: String = """
15+
| swagger: '2.0'
16+
| info:
17+
| title: Parsing Error Sample
18+
| version: 1.0.0
19+
| definitions:
20+
| Pet:
21+
| type: object
22+
| properties:
23+
| name:
24+
| type: string
25+
| x-scala-empty-is-null: true
26+
| x-scala-type: CustomThing
27+
| underscore_name:
28+
| type: string
29+
| x-scala-empty-is-null: true
30+
| x-scala-type: CustomThing
31+
| dash-name:
32+
| type: string
33+
| x-scala-empty-is-null: true
34+
| x-scala-type: CustomThing
35+
|""".stripMargin
36+
37+
val (
38+
ProtocolDefinitions(
39+
ClassDefinition(namePet, tpePet, clsPet, companionPet, catParents) :: Nil,
40+
_,
41+
_,
42+
_
43+
),
44+
_,
45+
_
46+
) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp)
47+
48+
it("should generate right companion object") {
49+
companionPet.toString() shouldBe q"""
50+
object Pet {
51+
implicit val encodePet = {
52+
val readOnlyKeys = Set[String]()
53+
Encoder.forProduct3("name", "underscore_name", "dash-name")((o: Pet) => (o.name, o.underscoreName, o.`dash-name`)).mapJsonObject(_.filterKeys(key => !(readOnlyKeys contains key)))
54+
}
55+
implicit val decodePet = new Decoder[Pet] {
56+
final def apply(c: HCursor): Decoder.Result[Pet] =
57+
for (
58+
name <- c.downField("name").withFocus(j => j.asString.fold(j)(s => if (s.isEmpty) Json.Null else j)).as[Option[CustomThing]];
59+
underscoreName <- c.downField("underscore_name").withFocus(j => j.asString.fold(j)(s => if (s.isEmpty) Json.Null else j)).as[Option[CustomThing]];
60+
`dash-name` <- c.downField("dash-name").withFocus(j => j.asString.fold(j)(s => if (s.isEmpty) Json.Null else j)).as[Option[CustomThing]]
61+
) yield Pet(name, underscoreName, `dash-name`)
62+
}
63+
}""".toString()
64+
}
65+
66+
}
67+
}

0 commit comments

Comments
 (0)