Skip to content

Commit 078fef7

Browse files
Baccataghostbuster91kubukoz
authored
Smithy4s integration (#58)
Co-authored-by: ghostbuster91 <[email protected]> Co-authored-by: Jakub Kozłowski <[email protected]> Co-authored-by: Olivier Mélois <[email protected]>
1 parent a482091 commit 078fef7

File tree

62 files changed

+2618
-260
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2618
-260
lines changed

.envrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
use flake

.scalafmt.conf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
version = "3.8.0"
22
runner.dialect = scala213
33
maxColumn = 120
4+
5+
rewrite {
6+
rules = [
7+
ExpandImportSelectors,
8+
Imports
9+
]
10+
11+
imports {
12+
groups = [
13+
["[a-z].*"],
14+
["java\\..*", "scala\\..*"]
15+
]
16+
sort = original
17+
}
18+
}
19+
420
fileOverride {
521
"glob:**/fs2/src/**" {
622
runner.dialect = scala213source3

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,27 @@ override def ivyDeps = super.ivyDeps() ++ Agg(ivy"tech.neander::jsonrpclib-fs2::
3939
**/!\ Please be aware that this library is in its early days and offers strictly no guarantee with regards to backward compatibility**
4040

4141
See the modules/examples folder.
42+
43+
## Smithy Integration
44+
45+
You can now use `jsonrpclib` directly with [Smithy](https://smithy.io/) and [smithy4s](https://disneystreaming.github.io/smithy4s/), enabling type-safe,
46+
schema-first JSON-RPC APIs with minimal boilerplate.
47+
48+
This integration is supported by the following modules:
49+
50+
```scala
51+
// Defines the Smithy protocol for JSON-RPC
52+
libraryDependencies += "tech.neander" % "jsonrpclib-smithy" % <version>
53+
54+
// Provides smithy4s client/server bindings for JSON-RPC
55+
libraryDependencies += "tech.neander" %%% "jsonrpclib-smithy4s" % <version>
56+
```
57+
58+
With these modules, you can:
59+
60+
- Annotate your Smithy operations with `@jsonRpcRequest` or `@jsonRpcNotification`
61+
- Generate client and server interfaces using smithy4s
62+
- Use ClientStub to invoke remote services over JSON-RPC
63+
- Use ServerEndpoints to expose service implementations via a Channel
64+
65+
This allows you to define your API once in Smithy and interact with it as a fully typed JSON-RPC service—without writing manual encoders, decoders, or dispatch logic.

build.sbt

Lines changed: 171 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,46 @@ inThisBuild(
1616
)
1717

1818
val scala213 = "2.13.16"
19-
val scala3 = "3.3.5"
19+
val scala3 = "3.3.6"
20+
val jdkVersion = 11
2021
val allScalaVersions = List(scala213, scala3)
2122
val jvmScalaVersions = allScalaVersions
2223
val jsScalaVersions = allScalaVersions
2324
val nativeScalaVersions = allScalaVersions
2425

2526
val fs2Version = "3.12.0"
2627

28+
ThisBuild / versionScheme := Some("early-semver")
2729
ThisBuild / tpolecatOptionsMode := DevMode
2830

2931
val commonSettings = Seq(
3032
libraryDependencies ++= Seq(
3133
"com.disneystreaming" %%% "weaver-cats" % "0.8.4" % Test
3234
),
3335
mimaPreviousArtifacts := Set(
34-
organization.value %%% name.value % "0.0.7"
36+
// organization.value %%% name.value % "0.0.7"
3537
),
36-
scalacOptions += "-java-output-version:8"
38+
scalacOptions ++= {
39+
CrossVersion.partialVersion(scalaVersion.value) match {
40+
case Some((2, _)) => Seq(s"-release:$jdkVersion")
41+
case _ => Seq(s"-java-output-version:$jdkVersion")
42+
}
43+
},
44+
)
45+
46+
val commonJvmSettings = Seq(
47+
javacOptions ++= Seq("--release", jdkVersion.toString)
3748
)
3849

3950
val core = projectMatrix
4051
.in(file("modules") / "core")
4152
.jvmPlatform(
4253
jvmScalaVersions,
43-
Test / unmanagedSourceDirectories ++= Seq(
44-
(projectMatrixBaseDirectory.value / "src" / "test" / "scalajvm-native").getAbsoluteFile
45-
)
54+
Seq(
55+
Test / unmanagedSourceDirectories ++= Seq(
56+
(projectMatrixBaseDirectory.value / "src" / "test" / "scalajvm-native").getAbsoluteFile
57+
)
58+
) ++ commonJvmSettings
4659
)
4760
.jsPlatform(jsScalaVersions)
4861
.nativePlatform(
@@ -56,13 +69,13 @@ val core = projectMatrix
5669
name := "jsonrpclib-core",
5770
commonSettings,
5871
libraryDependencies ++= Seq(
59-
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.30.2"
72+
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-circe" % "2.30.2"
6073
)
6174
)
6275

6376
val fs2 = projectMatrix
6477
.in(file("modules") / "fs2")
65-
.jvmPlatform(jvmScalaVersions)
78+
.jvmPlatform(jvmScalaVersions, commonJvmSettings)
6679
.jsPlatform(jsScalaVersions)
6780
.nativePlatform(nativeScalaVersions)
6881
.disablePlugins(AssemblyPlugin)
@@ -71,19 +84,97 @@ val fs2 = projectMatrix
7184
name := "jsonrpclib-fs2",
7285
commonSettings,
7386
libraryDependencies ++= Seq(
74-
"co.fs2" %%% "fs2-core" % fs2Version
87+
"co.fs2" %%% "fs2-core" % fs2Version,
88+
"io.circe" %%% "circe-generic" % "0.14.7" % Test
7589
)
7690
)
7791

92+
val smithy = projectMatrix
93+
.in(file("modules") / "smithy")
94+
.jvmPlatform(false)
95+
.disablePlugins(AssemblyPlugin, MimaPlugin)
96+
.enablePlugins(SmithyTraitCodegenPlugin)
97+
.settings(
98+
name := "jsonrpclib-smithy",
99+
commonJvmSettings,
100+
smithyTraitCodegenDependencies := List(Dependencies.alloy.core),
101+
smithyTraitCodegenJavaPackage := "jsonrpclib",
102+
smithyTraitCodegenNamespace := "jsonrpclib"
103+
)
104+
105+
val smithyTests = projectMatrix
106+
.in(file("modules/smithy-tests"))
107+
.jvmPlatform(Seq(scala213))
108+
.dependsOn(smithy)
109+
.settings(
110+
publish / skip := true,
111+
libraryDependencies ++= Seq(
112+
"com.disneystreaming" %%% "weaver-cats" % "0.8.4" % Test
113+
)
114+
)
115+
.disablePlugins(MimaPlugin)
116+
117+
lazy val buildTimeProtocolDependency =
118+
/** By default, smithy4sInternalDependenciesAsJars doesn't contain the jars in the "smithy4s" configuration. We have
119+
* to add them manually - this is the equivalent of a "% Smithy4s"-scoped dependency.
120+
*
121+
* Ideally, this would be
122+
* {{{
123+
* (Compile / smithy4sInternalDependenciesAsJars) ++=
124+
* Smithy4s / smithy4sInternalDependenciesAsJars).value.map(_.data)
125+
* }}}
126+
*
127+
* but that doesn't work because the Smithy4s configuration doesn't extend from Compile so it doesn't have the
128+
* `internalDependencyAsJars` setting.
129+
*/
130+
Compile / smithy4sInternalDependenciesAsJars ++=
131+
(smithy.jvm(autoScalaLibrary = false) / Compile / fullClasspathAsJars).value.map(_.data)
132+
133+
val smithy4s = projectMatrix
134+
.in(file("modules") / "smithy4s")
135+
.jvmPlatform(jvmScalaVersions, commonJvmSettings)
136+
.jsPlatform(jsScalaVersions)
137+
.nativePlatform(Seq(scala3))
138+
.disablePlugins(AssemblyPlugin)
139+
.enablePlugins(Smithy4sCodegenPlugin)
140+
.dependsOn(core)
141+
.settings(
142+
name := "jsonrpclib-smithy4s",
143+
commonSettings,
144+
mimaPreviousArtifacts := Set.empty,
145+
libraryDependencies ++= Seq(
146+
"com.disneystreaming.smithy4s" %%% "smithy4s-json" % smithy4sVersion.value
147+
),
148+
buildTimeProtocolDependency
149+
)
150+
151+
val smithy4sTests = projectMatrix
152+
.in(file("modules") / "smithy4s-tests")
153+
.jvmPlatform(jvmScalaVersions, commonJvmSettings)
154+
.jsPlatform(jsScalaVersions)
155+
.nativePlatform(Seq(scala3))
156+
.disablePlugins(AssemblyPlugin)
157+
.enablePlugins(Smithy4sCodegenPlugin)
158+
.dependsOn(smithy4s, fs2 % Test)
159+
.settings(
160+
commonSettings,
161+
publish / skip := true,
162+
libraryDependencies ++= Seq(
163+
"io.circe" %%% "circe-generic" % "0.14.7"
164+
),
165+
buildTimeProtocolDependency
166+
)
167+
78168
val exampleServer = projectMatrix
79169
.in(file("modules") / "examples/server")
80-
.jvmPlatform(List(scala213))
170+
.jvmPlatform(List(scala213), commonJvmSettings)
81171
.dependsOn(fs2)
82172
.settings(
83173
commonSettings,
84174
publish / skip := true,
85175
libraryDependencies ++= Seq(
86-
"co.fs2" %%% "fs2-io" % fs2Version
176+
"co.fs2" %%% "fs2-io" % fs2Version,
177+
"io.circe" %%% "circe-generic" % "0.14.7"
87178
)
88179
)
89180
.disablePlugins(MimaPlugin)
@@ -95,38 +186,93 @@ val exampleClient = projectMatrix
95186
Seq(
96187
fork := true,
97188
envVars += "SERVER_JAR" -> (exampleServer.jvm(scala213) / assembly).value.toString
98-
)
189+
) ++ commonJvmSettings
99190
)
100191
.disablePlugins(AssemblyPlugin)
101192
.dependsOn(fs2)
102193
.settings(
103194
commonSettings,
104195
publish / skip := true,
105196
libraryDependencies ++= Seq(
106-
"co.fs2" %%% "fs2-io" % fs2Version
197+
"co.fs2" %%% "fs2-io" % fs2Version,
198+
"io.circe" %%% "circe-generic" % "0.14.7"
107199
)
108200
)
109201
.disablePlugins(MimaPlugin)
110202

203+
val exampleSmithyShared = projectMatrix
204+
.in(file("modules") / "examples/smithyShared")
205+
.jvmPlatform(List(scala213), commonJvmSettings)
206+
.dependsOn(smithy4s, fs2)
207+
.enablePlugins(Smithy4sCodegenPlugin)
208+
.settings(
209+
commonSettings,
210+
publish / skip := true,
211+
buildTimeProtocolDependency
212+
)
213+
.disablePlugins(MimaPlugin)
214+
215+
val exampleSmithyServer = projectMatrix
216+
.in(file("modules") / "examples/smithyServer")
217+
.jvmPlatform(List(scala213), commonJvmSettings)
218+
.dependsOn(exampleSmithyShared)
219+
.settings(
220+
commonSettings,
221+
publish / skip := true,
222+
libraryDependencies ++= Seq(
223+
"co.fs2" %%% "fs2-io" % fs2Version
224+
),
225+
assembly / assemblyMergeStrategy := {
226+
case PathList("META-INF", "smithy", _*) => MergeStrategy.concat
227+
case PathList("jsonrpclib", "package.class") => MergeStrategy.first
228+
case PathList("META-INF", xs @ _*) if xs.nonEmpty => MergeStrategy.discard
229+
case x => MergeStrategy.first
230+
}
231+
)
232+
.disablePlugins(MimaPlugin)
233+
234+
val exampleSmithyClient = projectMatrix
235+
.in(file("modules") / "examples/smithyClient")
236+
.jvmPlatform(
237+
List(scala213),
238+
Seq(
239+
fork := true,
240+
envVars += "SERVER_JAR" -> (exampleSmithyServer.jvm(scala213) / assembly).value.toString
241+
) ++ commonJvmSettings
242+
)
243+
.dependsOn(exampleSmithyShared)
244+
.settings(
245+
commonSettings,
246+
publish / skip := true,
247+
libraryDependencies ++= Seq(
248+
"co.fs2" %%% "fs2-io" % fs2Version
249+
)
250+
)
251+
.disablePlugins(MimaPlugin, AssemblyPlugin)
252+
111253
val root = project
112254
.in(file("."))
113255
.settings(
114256
publish / skip := true
115257
)
116258
.disablePlugins(MimaPlugin, AssemblyPlugin)
117-
.aggregate(List(core, fs2, exampleServer, exampleClient).flatMap(_.projectRefs): _*)
118-
119-
// The core compiles are a workaround for https://github.com/plokhotnyuk/jsoniter-scala/issues/564
120-
// when we switch to SN 0.5, we can use `makeWithSkipNestedOptionValues` instead: https://github.com/plokhotnyuk/jsoniter-scala/issues/564#issuecomment-2787096068
121-
val compileCoreModules = {
122-
for {
123-
scalaVersionSuffix <- List("", "3")
124-
platformSuffix <- List("", "JS", "Native")
125-
task <- List("compile", "package")
126-
} yield s"core$platformSuffix$scalaVersionSuffix/$task"
127-
}.mkString(";")
259+
.aggregate(
260+
List(
261+
core,
262+
fs2,
263+
exampleServer,
264+
exampleClient,
265+
smithy,
266+
smithyTests,
267+
smithy4s,
268+
smithy4sTests,
269+
exampleSmithyShared,
270+
exampleSmithyServer,
271+
exampleSmithyClient
272+
).flatMap(_.projectRefs): _*
273+
)
128274

129275
addCommandAlias(
130276
"ci",
131-
s"$compileCoreModules;test;scalafmtCheckAll;mimaReportBinaryIssues"
277+
s"compile;test;scalafmtCheckAll;mimaReportBinaryIssues"
132278
)

0 commit comments

Comments
 (0)