Skip to content

Commit 8460001

Browse files
authored
update response mapping v2, add script to support testing (#10)
* update response mapping v2, add script to support testing * update jvm in ci
1 parent c1191b2 commit 8460001

File tree

8 files changed

+108
-49
lines changed

8 files changed

+108
-49
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
- uses: coursier/cache-action@v6
1616
- uses: VirtusLab/scala-cli-setup@main
1717
with:
18+
jvm: temurin:21
1819
apps: sbt
1920
power: true
2021
- name: Start up emulators
@@ -30,6 +31,7 @@ jobs:
3031
- uses: coursier/cache-action@v6
3132
- uses: VirtusLab/scala-cli-setup@main
3233
with:
34+
jvm: temurin:21
3335
apps: sbt
3436
- name: Import signing key
3537
if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == ''

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ target/
88
metals.sbt
99
.scala-build
1010
example/out
11-
.env
11+
.env
12+
test-local

build.sbt

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ lazy val noPublish = Seq(
4646

4747
lazy val sttpClient4Version = "4.0.0-RC1"
4848

49-
lazy val sttpClient3Version = "3.10.2"
49+
lazy val sttpClient3Version = "3.10.3"
5050

51-
lazy val zioVersion = "2.1.15"
51+
lazy val zioVersion = "2.1.16"
5252

53-
lazy val zioJsonVersion = "0.7.28"
53+
lazy val zioJsonVersion = "0.7.39"
5454

5555
lazy val jsoniterVersion = "2.33.2"
5656

@@ -67,6 +67,16 @@ lazy val root = (project in file("."))
6767
.aggregate(testProjects.componentProjects.map(p => LocalProject(p.id)) *)
6868
.settings(noPublish)
6969

70+
// for supporting code inspection / testing of generated code via test_gen.sh script
71+
lazy val testLocal = (project in file("test-local"))
72+
.settings(
73+
libraryDependencies ++= (
74+
dependencyByConfig("Sttp4", "Jsoniter", "ZioChunk")
75+
++ dependencyByConfig("Sttp3", "ZioJson", "List")
76+
)
77+
)
78+
.settings(noPublish)
79+
7080
lazy val core = crossProject(JVMPlatform, NativePlatform)
7181
.in(file("modules/core"))
7282
.settings(publishSettings)
@@ -92,21 +102,23 @@ lazy val cli = project
92102
nativeConfig := nativeConfig.value.withMultithreading(false)
93103
)
94104

95-
def dependencyByConfig(httpSource: String, jsonCodec: String, arrayType: String): List[ModuleID] = {
105+
def dependencyByConfig(httpSource: String, jsonCodec: String, arrayType: String): Seq[ModuleID] = {
96106
(httpSource match {
97-
case "Sttp3" => List("com.softwaremill.sttp.client3" %% "core" % sttpClient3Version)
98-
case "Sttp4" => List("com.softwaremill.sttp.client4" %% "core" % sttpClient4Version)
99-
case _ => Nil
100-
}) ::: (jsonCodec match {
107+
case "Sttp3" => Seq("com.softwaremill.sttp.client3" %% "core" % sttpClient3Version)
108+
case "Sttp4" => Seq("com.softwaremill.sttp.client4" %% "core" % sttpClient4Version)
109+
case other => throw new InterruptedException(s"Invalid http source: $other")
110+
}) ++ (jsonCodec match {
101111
case "Jsoniter" =>
102-
List(
112+
Seq(
103113
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % jsoniterVersion,
104114
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % jsoniterVersion % "compile-internal"
105115
)
106-
case "ZioJson" => List("dev.zio" %% "zio-json" % zioJsonVersion)
107-
}) ::: (arrayType match {
108-
case "ZioChunk" => List("dev.zio" %% "zio" % zioVersion)
109-
case _ => Nil
116+
case "ZioJson" => Seq("dev.zio" %% "zio-json" % zioJsonVersion)
117+
case other => throw new InterruptedException(s"Invalid json codec: $other")
118+
}) ++ (arrayType match {
119+
case "ZioChunk" => Seq("dev.zio" %% "zio" % zioVersion)
120+
case "List" | "Vector" | "Array" => Nil
121+
case other => throw new InterruptedException(s"Invalid array type: $other")
110122
})
111123
}
112124

modules/cli/src/main/scala/cli.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// for test runs using scala-cli
22
//> using jvm system
3-
//> using scala 3.6.3
3+
//> using scala 3.6.4
44
//> using file ../../../../core/shared/src/main/scala/codegen.scala
55
//> using dep com.lihaoyi::upickle:4.1.0
66

modules/core/shared/src/main/scala/codegen.scala

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,60 @@ def generateBySpec(
154154
case Dialect.Scala3 => s"package $resourcesPkg",
155155
"",
156156
config.httpSource match {
157-
case HttpSource.Sttp4 => "import sttp.model.*\nimport sttp.client4.*"
157+
case HttpSource.Sttp4 =>
158+
"import sttp.model.*\nimport sttp.client4.*, sttp.client4.ResponseException.{DeserializationException, UnexpectedStatusCode}"
158159
case HttpSource.Sttp3 => "import sttp.model.*\nimport sttp.client3.*"
159160
},
161+
config.jsonCodec match {
162+
case JsonCodec.ZioJson => "import zio.json.*"
163+
case JsonCodec.Jsoniter => "import com.github.plokhotnyuk.jsoniter_scala.core.*"
164+
},
160165
config.dialect match
161166
case Dialect.Scala3 => "",
162167
s"val resourceRequest: ${
163168
if config.httpSource == HttpSource.Sttp3 then "RequestT[Empty, Either[String, String], Any]"
164169
else "PartialRequest[Either[String, String]]"
165170
} = basicRequest.headers(Header.contentType(MediaType.ApplicationJson))",
171+
"",
166172
s"export ${config.outPkg}.QueryParameters",
167-
config.dialect match
168-
case Dialect.Scala3 => ""
173+
"",
174+
(config.httpSource, config.jsonCodec) match
175+
case (HttpSource.Sttp4, JsonCodec.Jsoniter) =>
176+
"""|def asJson[T : JsonValueCodec]: ResponseAs[Either[ResponseException[String], T]] =
177+
| asByteArrayAlways.mapWithMetadata((bytes, metadata) =>
178+
| if metadata.isSuccess then
179+
| try {
180+
| Right(readFromArray[T](bytes))
181+
| } catch {
182+
| case e: Exception =>
183+
| Left(DeserializationException(String(bytes, java.nio.charset.StandardCharsets.UTF_8), e, metadata))
184+
| }
185+
| else Left(UnexpectedStatusCode(String(bytes, java.nio.charset.StandardCharsets.UTF_8), metadata))
186+
| )""".stripMargin
187+
case (HttpSource.Sttp3, JsonCodec.Jsoniter) =>
188+
"""|def asJson[T : JsonValueCodec]: ResponseAs[Either[ResponseException[String, Exception], T], Any] =
189+
| asByteArrayAlways.mapWithMetadata((bytes, metadata) =>
190+
| if metadata.isSuccess then
191+
| try {
192+
| Right(readFromArray[T](bytes))
193+
| } catch {
194+
| case e: Exception =>
195+
| Left(DeserializationException(String(bytes, java.nio.charset.StandardCharsets.UTF_8), e))
196+
| }
197+
| else Left(HttpError(String(bytes, java.nio.charset.StandardCharsets.UTF_8), metadata.code))
198+
| )""".stripMargin
199+
case (HttpSource.Sttp4, JsonCodec.ZioJson) =>
200+
"""|def asJson[T : JsonDecoder]: ResponseAs[Either[ResponseException[String], T]] =
201+
| asStringAlways.mapWithMetadata((body, metadata) =>
202+
| if metadata.isSuccess then body.fromJson[T].left.map(e => DeserializationException(body, Exception(e), metadata))
203+
| else Left(UnexpectedStatusCode(body, metadata))
204+
| )""".stripMargin
205+
case (HttpSource.Sttp3, JsonCodec.ZioJson) =>
206+
"""|def asJson[T : JsonDecoder]: ResponseAs[Either[ResponseException[String, Exception], T], Any] =
207+
| asStringAlways.mapWithMetadata((body, metadata) =>
208+
| if metadata.isSuccess then body.fromJson[T].left.map(e => DeserializationException(body, Exception(e)))
209+
| else Left(HttpError(body, metadata.code))
210+
| )""".stripMargin
169211
).mkString("\n")
170212
)
171213
List(path.toFile())
@@ -254,20 +296,17 @@ def resourceCode(
254296
hasProps: SchemaPath => Boolean,
255297
commonQueryParams: Map[String, Parameter]
256298
) =
299+
val sttpClientPkg = httpSource match
300+
case HttpSource.Sttp4 => "sttp.client4"
301+
case HttpSource.Sttp3 => "sttp.client3"
302+
257303
List(
258304
s"package $pkg",
259305
"",
260306
s"import $schemasPkg.*",
261307
s"import $resourcesPkg.*",
262308
"",
263-
httpSource match {
264-
case HttpSource.Sttp4 => "import sttp.model.Uri, sttp.model.Uri.PathSegment, sttp.client4.*"
265-
case HttpSource.Sttp3 => "import sttp.model.Uri, sttp.model.Uri.PathSegment, sttp.client3.*"
266-
},
267-
jsonCodec match {
268-
case JsonCodec.ZioJson => "import zio.json.*"
269-
case JsonCodec.Jsoniter => "import com.github.plokhotnyuk.jsoniter_scala.core.*"
270-
},
309+
s"import sttp.model.Uri, sttp.model.Uri.PathSegment, $sttpClientPkg.*",
271310
"",
272311
s"object ${resourceName} {" +
273312
resource.methods
@@ -351,35 +390,18 @@ def resourceCode(
351390

352391
def responseType(t: String) =
353392
httpSource match
354-
case HttpSource.Sttp4 => s"Request[Either[String, $t]]"
393+
case HttpSource.Sttp4 =>
394+
s"Request[Either[${if t == "String" then "String" else "ResponseException[String]"}, $t]]"
355395
case HttpSource.Sttp3 =>
356-
s"RequestT[Identity, Either[String, $t], Any]"
396+
s"RequestT[Identity, Either[${if t == "String" then "String" else "ResponseException[String, Exception]"}, $t], Any]"
357397

358398
val (resType, mapResponse) = v.response match
359399
case Some(r) if r.schemaPath.forall(hasProps) =>
360400
val bodyType = r.scalaType(arrType)
361401

362402
(
363403
responseType(bodyType),
364-
jsonCodec match
365-
case JsonCodec.ZioJson =>
366-
s"""|.response(
367-
| asStringAlways.mapWithMetadata((body, metadata) =>
368-
| if (metadata.isSuccess) then body.fromJson[$bodyType] else Left(body)
369-
| )
370-
|)""".stripMargin
371-
case JsonCodec.Jsoniter =>
372-
s"""|.response(
373-
| asByteArrayAlways.mapWithMetadata((bytes, metadata) =>
374-
| if (metadata.isSuccess) {
375-
| try {
376-
| Right(readFromArray[$bodyType](bytes))
377-
| } catch {
378-
| case e: Throwable => Left(e.getMessage())
379-
| }
380-
| } else Left(String(bytes, java.nio.charset.StandardCharsets.UTF_8))
381-
| )
382-
|)""".stripMargin
404+
s".response(asJson[$bodyType])"
383405
)
384406
case _ => (responseType("String"), "")
385407

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=1.10.7
1+
sbt.version=1.10.10

project/plugins.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.6")
1+
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.7")
22

33
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
44

test_gen.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
3+
# script to support with quick execution and code inspection on generator changes
4+
# runs code generator via scala-cli for given config
5+
# will ouput code under test-local/src/main/scala
6+
7+
spec=pubsub
8+
json_codec=jsoniter
9+
http_source=sttp4
10+
array_type=list
11+
out_dir=test-local/src/main/scala/test-$spec-v1/$http_source/$spec/$json_codec
12+
rm -rf $out_dir && mkdir -p $out_dir
13+
14+
echo "running generator for $spec to $out_dir"
15+
scala-cli modules/cli/src/main/scala/cli.scala -- \
16+
-specs=modules/test-resources/src/main/resources/${spec}_v1.json \
17+
-out-dir=$out_dir \
18+
-out-pkg=gcp.${spec}.v1.${http_source}.${json_codec}.$array_type \
19+
-http-source=$http_source \
20+
-json-codec=$json_codec \
21+
-array-type=$array_type && \
22+
scala-cli fmt $out_dir

0 commit comments

Comments
 (0)