Skip to content

Commit fe37f09

Browse files
authored
Merge pull request #24 from rolang/require-custom-jsoniter-json-type
require custom jsoniter json type to be provided
2 parents cfcea52 + fdbf27a commit fe37f09

7 files changed

Lines changed: 132 additions & 166 deletions

File tree

README.md

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ The generator can be used with any tool that can perform system calls to a comma
2424
See example under [example/generate.scala](./example/generate.scala).
2525

2626
```scala
27-
//> using scala 3.7.3
28-
//> using dep dev.rolang::gcp-codegen::0.0.8
27+
//> using scala 3.7.4
28+
//> using dep dev.rolang::gcp-codegen::0.0.12
2929

3030
import gcp.codegen.*, java.nio.file.*, GeneratorConfig.*
3131

@@ -55,13 +55,41 @@ See output in `example/out`.
5555

5656
| Configuration | Description | Options | Default |
5757
| ------------------- | ---------------- | ------- | --- |
58-
| -specs | Can be `stdin` or a path to the JSON file. | | |
59-
| -out-dir | Ouput directory | | |
60-
| -out-pkg | Output package | | |
61-
| -http-source | Generated http source. | [Sttp4](https://sttp.softwaremill.com/en/stable) | |
62-
| -json-codec | Generated JSON codec | [Jsoniter](https://github.com/plokhotnyuk/jsoniter-scala), [ZioJson](https://zio.dev/zio-json) | |
63-
| -array-type | Collection type for JSON arrays | `List`, `Vector`, `Array`, `ZioChunk` | `List` |
64-
| -include-resources | Optional resource filter. | | |
58+
| -specs | Can be `stdin` or a path to the JSON file. | | |
59+
| -out-dir | Output directory | | |
60+
| -out-pkg | Output package | | |
61+
| -http-source | Generated http source. | [Sttp4](https://sttp.softwaremill.com/en/stable) | |
62+
| -json-codec | Generated JSON codec | [Jsoniter](https://github.com/plokhotnyuk/jsoniter-scala), [ZioJson](https://zio.dev/zio-json) | |
63+
| -jsoniter-json-type | In case of Jsoniter a fully qualified name of the custom type that can represent a raw Json value | |
64+
| -array-type | Collection type for JSON arrays | `List`, `Vector`, `Array`, `ZioChunk` | `List` |
65+
| -include-resources | Optional resource filter. | | |
66+
67+
##### Jsoniter Json type and codec example
68+
Jsoniter doesn't ship with a type that can represent raw Json values to be used for mapping of `any` / `object` types,
69+
but it provides methods to read / write raw values as bytes (related [issue](https://github.com/plokhotnyuk/jsoniter-scala/issues/1257)).
70+
Given that we can create a custom type with a codec which can look for example like [that](modules/example-jsoniter-json/shared/src/main/scala/json.scala):
71+
```scala
72+
package example.jsoniter
73+
import com.github.plokhotnyuk.jsoniter_scala.core.*
74+
75+
opaque type Json = Array[Byte]
76+
object Json:
77+
def writeToJson[T: JsonValueCodec](v: T): Json = writeToArray[T](v)
78+
79+
given codec: JsonValueCodec[Json] = new JsonValueCodec[Json]:
80+
override def decodeValue(in: JsonReader, default: Json): Json = in.readRawValAsBytes()
81+
override def encodeValue(x: Json, out: JsonWriter): Unit = out.writeRawVal(x)
82+
override val nullValue: Json = Array[Byte](0)
83+
84+
extension (v: Json)
85+
def readAsUnsafe[T: JsonValueCodec]: T = readFromArray(v)
86+
def readAs[T: JsonValueCodec]: Either[Throwable, T] =
87+
try Right(readFromArray(v))
88+
catch case t: Throwable => Left(t)
89+
```
90+
Then pass it as argument to the code generator like `-jsoniter-json-type=_root_.example.jsoniter.Json`.
91+
Since this type and codec can be shared across generated clients it has to be provided (at least for now)
92+
instead of being generated for each client to avoid duplicated / redundant code.
6593

6694
##### Examples:
6795

@@ -79,7 +107,7 @@ curl 'https://pubsub.googleapis.com/$discovery/rest?version=v1' > pubsub_v1.json
79107
-specs=./pubsub_v1.json \
80108
-out-pkg=gcp.pubsub.v1 \
81109
-http-source=sttp4 \
82-
-json-codec=jsoniter \
110+
-json-codec=ziojson \
83111
-include-resources='projects.*,!projects.snapshots' # optional filters
84112
```
85113

build.sbt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ val zioVersion = "2.1.23"
4646

4747
val zioJsonVersion = "0.8.0"
4848

49-
val jsoniterVersion = "2.38.6"
49+
val jsoniterVersion = "2.38.8"
5050

5151
val munitVersion = "1.2.1"
5252

@@ -58,13 +58,16 @@ lazy val root = (project in file("."))
5858
.aggregate(
5959
core.native,
6060
core.jvm,
61+
exampleJsoniterJson.native,
62+
exampleJsoniterJson.jvm,
6163
cli
6264
)
6365
.aggregate(testProjects.componentProjects.map(p => LocalProject(p.id)) *)
6466
.settings(noPublish)
6567

6668
// for supporting code inspection / testing of generated code via test_gen.sh script
6769
lazy val testLocal = (project in file("test-local"))
70+
.dependsOn(exampleJsoniterJson.jvm)
6871
.settings(
6972
libraryDependencies ++= Seq(
7073
"com.softwaremill.sttp.client4" %% "core" % sttpClient4Version,
@@ -101,6 +104,20 @@ lazy val cli = project
101104
nativeConfig := nativeConfig.value.withMultithreading(false)
102105
)
103106

107+
lazy val exampleJsoniterJson = crossProject(JVMPlatform, NativePlatform)
108+
.in(file("modules/example-jsoniter-json"))
109+
.settings(noPublish)
110+
.settings(
111+
name := "example-jsoniter-json",
112+
moduleName := "example-jsoniter-json"
113+
)
114+
.settings(
115+
libraryDependencies ++= Seq(
116+
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % jsoniterVersion,
117+
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % jsoniterVersion % "compile-internal"
118+
)
119+
)
120+
104121
def dependencyByConfig(httpSource: String, jsonCodec: String, arrayType: String): Seq[ModuleID] = {
105122
(httpSource match {
106123
case "Sttp4" => Seq("com.softwaremill.sttp.client4" %% "core" % sttpClient4Version)
@@ -136,7 +153,7 @@ lazy val testProjects: CompositeProject = new CompositeProject {
136153
arrayType <- Seq("ZioChunk", "List")
137154
id = s"test-$apiName-$apiVersion-${httpSource}-${jsonCodec}-${arrayType}".toLowerCase()
138155
} yield {
139-
Project
156+
val p = Project
140157
.apply(
141158
id = id,
142159
base = file("modules") / id
@@ -155,6 +172,12 @@ lazy val testProjects: CompositeProject = new CompositeProject {
155172
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % jsoniterVersion % Test
156173
) ++ dependencyByConfig(httpSource = httpSource, jsonCodec = jsonCodec, arrayType = arrayType)
157174
)
175+
176+
if (jsonCodec == "Jsoniter") {
177+
p.dependsOn(exampleJsoniterJson.componentProjects.map(p => ClasspathDependency(p, p.configuration)) *)
178+
} else {
179+
p
180+
}
158181
}
159182
}
160183

@@ -235,7 +258,8 @@ def codegenTask(
235258
s"-out-pkg=$basePkgName",
236259
s"-http-source=$httpSource",
237260
s"-json-codec=$jsonCodec",
238-
s"-array-type=$arrayType"
261+
s"-array-type=$arrayType",
262+
s"-jsoniter-json-type=_root_.example.jsoniter.Json"
239263
).mkString(" ") ! ProcessLogger(l => logger.info(l), e => errs += e)) match {
240264
case 0 => ()
241265
case c => throw new InterruptedException(s"Failure on code generation: ${errs.mkString("\n")}")

example/generate.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
//> using scala 3.7.3
2-
//> using dep dev.rolang::gcp-codegen::0.0.8
1+
//> using scala 3.7.4
2+
//> using dep dev.rolang::gcp-codegen::0.0.12
33

44
import gcp.codegen.*, java.nio.file.*, GeneratorConfig.*
55

@@ -10,7 +10,7 @@ import gcp.codegen.*, java.nio.file.*, GeneratorConfig.*
1010
outDir = Path.of("out"),
1111
outPkg = "example.pubsub.v1",
1212
httpSource = HttpSource.Sttp4,
13-
jsonCodec = JsonCodec.Jsoniter,
13+
jsonCodec = JsonCodec.ZioJson,
1414
arrayType = ArrayType.List,
1515
preprocess = specs => specs
1616
)

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ import scala.concurrent.duration.*
3131

3232
private def argsToTask(args: Seq[String]): Either[String, Task] =
3333
val argsMap = args.toList
34-
.flatMap(_.split('=').map(_.trim().toLowerCase()))
34+
.flatMap(_.split('=').map(_.trim()))
3535
.sliding(2, 2)
3636
.collect { case a :: b :: _ =>
37-
a -> b
37+
a.toLowerCase() -> b
3838
}
3939
.toMap
4040

@@ -57,10 +57,17 @@ private def argsToTask(args: Seq[String]): Either[String, Task] =
5757
.get("-http-source")
5858
.flatMap(v => HttpSource.values.find(_.toString().equalsIgnoreCase(v)))
5959
.toRight("Missing or invalid -http-source")
60-
jsonCodec <- argsMap
61-
.get("-json-codec")
62-
.flatMap(v => JsonCodec.values.find(_.toString().equalsIgnoreCase(v)))
63-
.toRight("Missing or invalid -json-codec")
60+
jsonCodec <- (
61+
argsMap
62+
.get("-json-codec")
63+
.map(_.toLowerCase()),
64+
argsMap.get("-jsoniter-json-type")
65+
) match {
66+
case (Some("ziojson"), _) => Right(JsonCodec.ZioJson)
67+
case (Some("jsoniter"), Some(jsonType)) => Right(JsonCodec.Jsoniter(jsonType))
68+
case (Some("jsoniter"), None) => Left("Missing -jsoniter-json-type")
69+
case _ => Left("Missing or invalid -json-codec")
70+
}
6471
arrayType <- argsMap.get("-array-type") match
6572
case None => Right(ArrayType.List)
6673
case Some(v) => ArrayType.values.find(_.toString().equalsIgnoreCase(v)).toRight(s"Invalid array-type $v")

0 commit comments

Comments
 (0)