Skip to content

Commit f994b5c

Browse files
camarenaghostdogpr
andauthored
Fixing rendering of descriptions for multi-line descriptions ending in quote (#1544)
* Fixing rendering of descriptions for multi-line descriptions ending in quote * Do not add unnecessary space after opening triple quote * adding test cases and making them pass * remove debug code * Update core/src/main/scala/caliban/Rendering.scala Co-authored-by: Pierre Ricadat <ghostdogpr@users.noreply.github.com> Co-authored-by: Pierre Ricadat <ghostdogpr@users.noreply.github.com>
1 parent 95eee8f commit f994b5c

4 files changed

Lines changed: 209 additions & 11 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ Want to see your company here? [Submit a PR](https://github.com/ghostdogpr/calib
3939
* [LeadIQ](https://leadiq.com)
4040
* [Sanjagh.pro](https://sanjagh.pro)
4141
* [Soundtrack Your Brand](https://www.soundtrackyourbrand.com)
42+
* [StepZen](https://www.stepzen.com)
4243
* [Undo](https://www.undo.app)

core/src/main/scala/caliban/Rendering.scala

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package caliban
22

3-
import caliban.Value.{ BooleanValue, EnumValue, FloatValue, IntValue, NullValue, StringValue }
3+
import caliban.Value._
44
import caliban.introspection.adt._
5+
import caliban.introspection.adt.__TypeKind._
56
import caliban.parsing.adt.Directive
6-
import __TypeKind._
77

88
object Rendering {
99

@@ -149,11 +149,34 @@ object Rendering {
149149
case _ => ""
150150
}
151151

152-
private def renderDescription(description: Option[String], newline: Boolean = true): String = description match {
153-
case None => ""
154-
case Some(value) if newline =>
155-
if (value.contains("\n")) renderTripleQuotedString("\n" + value) + "\n" else renderString(value) + "\n"
156-
case Some(value) => if (value.contains("\n")) renderTripleQuotedString(value) else renderString(value) + " "
152+
private def renderDescription(description: Option[String], newline: Boolean = true): String = {
153+
// Most of the graphql tools are greedy on triple quotes. To be compatible we
154+
// need to break 4 or more '"' at the end of the description either with a newline or a space
155+
val tripleQuote = "\"\"\""
156+
157+
def nlOrSp = if (newline) "\n" else " "
158+
159+
def nlOrNot = if (newline) "\n" else ""
160+
161+
def renderTripleQuotedString(value: String) = {
162+
val valueEscaped = value.replace(tripleQuote, s"\\$tripleQuote")
163+
// check if it ends in quote but it is already escaped
164+
if (value.endsWith("\\\""))
165+
s"$tripleQuote$nlOrNot$valueEscaped$nlOrNot$tripleQuote"
166+
// check if it ends in quote. We need to break the sequence of 4 or more '"'
167+
else if (value.last == '"') {
168+
s"$tripleQuote$nlOrNot$valueEscaped$nlOrSp$tripleQuote"
169+
} else {
170+
// ok. No quotes at the end of value
171+
s"$tripleQuote$nlOrNot$valueEscaped$nlOrNot$tripleQuote"
172+
}
173+
}
174+
description match {
175+
case None => ""
176+
case Some(value) if value.exists(_ == '\n') =>
177+
s"${renderTripleQuotedString(s"$value")}$nlOrSp"
178+
case Some(value) => s"${renderString(value)}$nlOrSp"
179+
}
157180
}
158181

159182
private def renderSpecifiedBy(specifiedBy: Option[String]): String =
@@ -222,9 +245,6 @@ object Rendering {
222245
}
223246
}
224247

225-
private def renderTripleQuotedString(value: String) =
226-
"\"\"\"" + value.replace("\"\"\"", "\\\"\"\"") + "\"\"\""
227-
228248
private def renderString(value: String) =
229249
"\"" + value
230250
.replace("\\", "\\\\")

core/src/test/scala/caliban/RenderingSpec.scala

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import zio.test._
99

1010
object RenderingSpec extends ZIOSpecDefault {
1111

12+
val tripleQuote = "\"\"\""
13+
1214
override def spec =
1315
suite("rendering")(
1416
test("it should render directives") {
@@ -177,8 +179,108 @@ object RenderingSpec extends ZIOSpecDefault {
177179
)
178180
val renderedType = Rendering.renderTypes(List(testType))
179181
assert(renderedType)(
180-
equalTo("\"\"\"\nA multiline \"TestType\" description\ngiven inside \\\"\"\"-quotes\n\"\"\"\ntype TestType")
182+
equalTo("\"\"\"\nA multiline \"TestType\" description\ngiven inside \\\"\"\"-quotes\n\n\"\"\"\ntype TestType")
181183
)
184+
},
185+
test("it should render single line descriptions") {
186+
import RenderingSpecSchemaSingleLineDescription.resolver
187+
val expected =
188+
"""schema {
189+
| query: Query
190+
|}
191+
|
192+
|"type description in a single line"
193+
|type OutputValue {
194+
| "field description in a single line"
195+
| r: Int!
196+
|}
197+
|
198+
|type Query {
199+
| "query description in a single line"
200+
| q("argument description in a single line" in: Int!): OutputValue!
201+
|}
202+
|""".stripMargin
203+
assert(graphQL(resolver).render.trim)(equalTo(expected.trim))
204+
},
205+
test("it should render multiple line descriptions") {
206+
import RenderingSpecSchemaMultiLineDescription.resolver
207+
val expected =
208+
s"""schema {
209+
| query: Query
210+
|}
211+
|
212+
|$tripleQuote
213+
|type description in
214+
|Multiple lines
215+
|$tripleQuote
216+
|type OutputValue {
217+
| $tripleQuote
218+
|field description in
219+
|Multiple lines
220+
|$tripleQuote
221+
| r: Int!
222+
|}
223+
|
224+
|type Query {
225+
| $tripleQuote
226+
|query description in
227+
|Multiple lines
228+
|$tripleQuote
229+
| q(${tripleQuote}argument description in
230+
|Multiple lines${tripleQuote} in: Int!): OutputValue!
231+
|}
232+
|""".stripMargin
233+
assert(graphQL(resolver).render.trim)(equalTo(expected.trim))
234+
},
235+
test("it should render single line descriptions ending in quote") {
236+
import RenderingSpecSchemaSingleLineEndingInQuoteDescription.resolver
237+
val expected =
238+
"""schema {
239+
| query: Query
240+
|}
241+
|
242+
|"type description in a single line \"ending in quote\""
243+
|type OutputValue {
244+
| "field description in a single line \"ending in quote\""
245+
| r: Int!
246+
|}
247+
|
248+
|type Query {
249+
| "query description in a single line \"ending in quote\""
250+
| q("argument description in a single line \"ending in quote\"" in: Int!): OutputValue!
251+
|}
252+
|""".stripMargin
253+
assert(graphQL(resolver).render.trim)(equalTo(expected.trim))
254+
},
255+
test("it should render multi line descriptions ending in quote") {
256+
import RenderingSpecSchemaMultiLineEndingInQuoteDescription.resolver
257+
val expected =
258+
s"""schema {
259+
| query: Query
260+
|}
261+
|
262+
|$tripleQuote
263+
|type description in multiple lines
264+
|\"ending in quote\"
265+
|$tripleQuote
266+
|type OutputValue {
267+
| $tripleQuote
268+
|field description in multiple lines
269+
|\"ending in quote\"
270+
|$tripleQuote
271+
| r: Int!
272+
|}
273+
|
274+
|type Query {
275+
| $tripleQuote
276+
|query description in multiple lines
277+
|\"ending in quote\"
278+
|$tripleQuote
279+
| q(${tripleQuote}argument description in multiple lines
280+
|\"ending in quote\" ${tripleQuote} in: Int!): OutputValue!
281+
|}
282+
|""".stripMargin
283+
assert(graphQL(resolver).render.trim)(equalTo(expected.trim))
182284
}
183285
)
184286
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package caliban
2+
3+
import caliban.schema.Annotations.GQLDescription
4+
5+
object RenderingSpecSchemaSingleLineDescription {
6+
7+
@GQLDescription("type description in a single line")
8+
case class OutputValue(@GQLDescription("field description in a single line") r: Int)
9+
10+
case class InputValue(@GQLDescription("argument description in a single line") in: Int)
11+
12+
def getResult: OutputValue = ???
13+
14+
case class Query(
15+
@GQLDescription("query description in a single line") q: InputValue => OutputValue
16+
)
17+
18+
val resolver = RootResolver(
19+
Query(_ => getResult)
20+
)
21+
}
22+
23+
object RenderingSpecSchemaMultiLineDescription {
24+
25+
@GQLDescription("type description in\nMultiple lines")
26+
case class OutputValue(@GQLDescription("field description in\nMultiple lines") r: Int)
27+
28+
case class InputValue(@GQLDescription("argument description in\nMultiple lines") in: Int)
29+
30+
def getResult: OutputValue = ???
31+
32+
case class Query(
33+
@GQLDescription("query description in\nMultiple lines") q: InputValue => OutputValue
34+
)
35+
36+
val resolver = RootResolver(
37+
Query(_ => getResult)
38+
)
39+
}
40+
41+
object RenderingSpecSchemaSingleLineEndingInQuoteDescription {
42+
43+
@GQLDescription("type description in a single line \"ending in quote\"")
44+
case class OutputValue(@GQLDescription("field description in a single line \"ending in quote\"") r: Int)
45+
46+
case class InputValue(@GQLDescription("argument description in a single line \"ending in quote\"") in: Int)
47+
48+
def getResult: OutputValue = ???
49+
50+
case class Query(
51+
@GQLDescription("query description in a single line \"ending in quote\"") q: InputValue => OutputValue
52+
)
53+
54+
val resolver = RootResolver(
55+
Query(_ => getResult)
56+
)
57+
}
58+
59+
object RenderingSpecSchemaMultiLineEndingInQuoteDescription {
60+
61+
@GQLDescription("type description in multiple lines\n\"ending in quote\"")
62+
case class OutputValue(@GQLDescription("field description in multiple lines\n\"ending in quote\"") r: Int)
63+
64+
case class InputValue(@GQLDescription("argument description in multiple lines\n\"ending in quote\"") in: Int)
65+
66+
def getResult: OutputValue = ???
67+
68+
case class Query(
69+
@GQLDescription("query description in multiple lines\n\"ending in quote\"") q: InputValue => OutputValue
70+
)
71+
72+
val resolver = RootResolver(
73+
Query(_ => getResult)
74+
)
75+
}

0 commit comments

Comments
 (0)