{"id":15887829,"url":"https://github.com/neandertech/smithy4s-deriving","last_synced_at":"2026-03-02T17:36:43.588Z","repository":{"id":236105715,"uuid":"791923606","full_name":"neandertech/smithy4s-deriving","owner":"neandertech","description":"Experimental Scala 3 library that allows to automatically derive instances of the smithy4s abstractions from scala constructs.","archived":false,"fork":false,"pushed_at":"2024-07-16T17:33:06.000Z","size":110,"stargazers_count":25,"open_issues_count":3,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-03T12:11:11.029Z","etag":null,"topics":["derivation","scala","scala3","smithy","smithy4s"],"latest_commit_sha":null,"homepage":"https://neander.tech/2024-04-27-specs-first-and-code-first","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neandertech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-25T16:23:47.000Z","updated_at":"2025-01-13T17:47:42.000Z","dependencies_parsed_at":"2024-05-12T09:24:45.009Z","dependency_job_id":"f1172af8-dea0-41f0-9ba0-9f4ee5be4114","html_url":"https://github.com/neandertech/smithy4s-deriving","commit_stats":null,"previous_names":["neandertech/smithy4s-deriving"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/neandertech/smithy4s-deriving","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neandertech%2Fsmithy4s-deriving","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neandertech%2Fsmithy4s-deriving/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neandertech%2Fsmithy4s-deriving/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neandertech%2Fsmithy4s-deriving/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neandertech","download_url":"https://codeload.github.com/neandertech/smithy4s-deriving/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neandertech%2Fsmithy4s-deriving/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30012001,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T17:00:27.440Z","status":"ssl_error","status_checked_at":"2026-03-02T17:00:03.402Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["derivation","scala","scala3","smithy","smithy4s"],"created_at":"2024-10-06T06:05:11.133Z","updated_at":"2026-03-02T17:36:43.564Z","avatar_url":"https://github.com/neandertech.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# smithy4s-deriving\n\n`smithy4s-deriving` is a **Scala-3 only** experimental library that allows to automatically derive instances of the [smithy4s](https://disneystreaming.github.io/smithy4s/) abstractions from scala constructs.\n\nIf `smithy4s` is a tool that promotes a spec-first approach to API design, providing a code-generator that feeds of smithy specifications, the runtime interpreters it provides are written against a set of abstractions that are not inherently tied to code-generators.\n\n`smithy4s-deriving` provides a code-first alternative way to code-generation to interact with these interpreters, by using handcrafted data-types and interfaces written directly in Scala as the source of truth, thus giving access to a large number of features provided by Smithy4s, with a lower barrier of entry. In particular, this enables usage in scala-cli projects.\n\nThis project takes some inspiration from :\n\n* Jamie Thompson's https://github.com/bishabosha/ops-mirror\n* Jakub Kozlowski's https://github.com/polyvariant/respectfully/\n\n## Installation\n\nScala 3.4.1 or newer is required.\n\nSBT :\n\n```\nlibraryDependencies += \"tech.neander\" %% \"smithy4s-deriving\" % \u003cversion\u003e\naddCompilerPlugin(\"tech.neander\" %% \"smithy4s-deriving-compiler\" % \u003cversion\u003e)\n```\n\nscala-cli :\n\n```\n//\u003e using dep \"tech.neander::smithy4s-deriving:\u003cversion\u003e\"\n//\u003e using plugin \"tech.neander::smithy4s-deriving-compiler:\u003cversion\u003e\"\n```\n\nYou'll typically need the following imports to use the derivation :\n\n```scala\nimport smithy4s.*\nimport smithy4s.deriving.{given, *}\nimport smithy4s.deriving.aliases.* // for syntactically pleasant annotations\nimport scala.annotation.experimental // the derivation of API uses experimental metaprogramming features, at this time.\n\nimport smithy.api.* // if you want to use hints from the official smithy standard library\nimport alloy.* // if you want to use hints from the alloy library\n```\n\n\nThe `smithy4s-deriving` library is provided for Scala JVM, Scala JS (1.16+) and Scala Native (0.4.x), and is currently compatible with smithy4s 0.18.16+.\n\n## Examples\n\nhead other to [the examples](./modules/examples/shared/src/main/scala/) directory to get a feel for how to use it.\n\n## Derivation of case-classes schemas\n\n`smithy4s-deriving` allows for deriving schemas from case classes.\n\n```scala\nimport smithy4s.*\nimport smithy4s.deriving.{given, *}\n\ncase class Person(firstName: String, lastName: String) derives Schema\n```\n\nThis allows to access a bunch of serialisation [utilities](https://disneystreaming.github.io/smithy4s/docs/02.1-serialisation/serialisation) and other schema-driven features from smithy4s.\n\nIt is possible to customise the behaviour of serialisers in a similar way that you'd have in spec-first smithy4s, by annotating data-types / fields with the `@hints` annotation, and passing it instances of datatypes that were generated from smithy-traits by smithy4s. For instance :\n\n```scala\npackage example\n\nimport smithy.api.*\n\ncase class Person(@hints(JsonName(\"first-name\")) firstName: String = \"John\", @hints(JsonName(\"last-name\")) lastName = \"Doe\") derives Schema\n```\n\nis semantically equivalent to :\n\n```smithy\nnamespace example\n\nstructure Person {\n  @jsonName(\"first-name\")\n  @required\n  firstName: String = \"John\"\n\n  @jsonName(\"last-name\")\n  @required\n  lastName: String = \"Doe\"\n}\n```\n\n\n### NB :\n\n* All case class fields must have implicit (given) schemas available\n* Defaults are supported\n* Scaladoc is converted to `@documentation` hints\n\n## Derivation of ADTs schemas\n\n`smithy4s-deriving` allows for deriving schemas from ADTs.\n\n```scala\nimport smithy4s.*\nimport smithy4s.deriving.{given, *}\n\nenum Foo derives Schema {\n  case Bar(x: Int, y: Boolean)\n  case Baz(z: String)\n}\n```\n\nIt is possible to :\n* use the `@hints` annotation on ADTs and their members.\n* use the `@hints.member` annotation on ADT members to distinguish whether hints should go to the member or the target shape at the smithy level.\n* use the `@wrapper` on single-field case classes in order to prevent a layer of \"structure\" schema from being created.\n\nFor instance :\n\n```scala\npackage example\n\nimport smithy4s.*\nimport smithy4s.deriving.{given, *}\n\nimport smithy.api.*\n\nenum Foo derives Schema {\n  @hints(Documentation(\"Some docs\"))\n  @hints.member(JsonName(\"bar\")) // note the different annotation to target the smithy member\n  case Bar(x: Int, y: Boolean)\n\n  @hints.member(JsonName(\"baz\"))\n  @wrapper // note the wrapper annotation here\n  case Baz(z: String)\n}\n```\n\nIs equivalent to the combination of these smithy specs :\n\n```smithy\nnamespace example\n\nuse example.foo#Bar\n\nunion Foo {\n  @jsonName(\"baz\")\n  Bar: Bar\n  @jsonName(\"bar\")\n  Baz: String\n}\n```\n\nand\n\n```smithy\nnamespace example.foo\n\n///Some docs\nstructure Bar {\n  @required\n  x: Integer\n  @required\n  y: String\n}\n```\n\n\n\n## Derivation of Services from interfaces/classes\n\n```scala\nimport smithy4s.*\nimport smithy4s.deriving.{given, *}\nimport scala.annotation.experimental\n\n@experimental\ntrait HelloWorldService derives API {\n\n  def hello(name: String, location: Option[String]) : IO[String]\n\n}\n```\n\nThis allows to access whatever interpreters are provided by smithy4s or its downstream libraries. These interpreters are how smithy4s integrates with various libraries and protocols.\n\n* It is possible to use hints on interfaces to customise interpreter behaviour.\n* It is also possible to use the `@errors` annotation to tie error handling to either services\n\nFor instance:\n\n```scala\npackage example\n\nimport smithy4s.*\nimport smithy4s.deriving.{given, *}\nimport smithy.api.*\nimport alloy.*\nimport scala.annotation.experimental\n\n@hints(HttpError(403))\ncase class Bounce(message: String) extends Throwable derives Schema\n@hints(HttpError(500))\ncase class Crash(cause: String) extends Throwable derives Schema\n\n@experimental\n@hints(SimpleRestJson())\ntrait HelloWorldService derives API {\n\n  @errors[(BadLocation, Crash)]\n  @hints(Http(\"GET\", \"/hello/{name}\", 200))\n  def hello(\n    @hints(HttpLabel()) name: String,\n    @hints(HttpQuery(\"from\")) location: Option[String]\n  ) : IO[String]\n\n}\n```\n\nIs semantically equivalent to the combination of these smithy specs :\n\n```smithy\n$version: \"2\"\n\nnamespace example\n\nuse alloy#simpleRestJson\nuse example.helloWorldService#hello\n\n@simpleRestJson\nservice Foo {\n  operations: [hello]\n}\n\n@error(\"client\")\n@httpError(403)\nstructure Bounce {\n  @required\n  message: String\n}\n\n@error(\"server\")\n@httpError(500)\nstructure Crash {\n  @required\n  cause: String\n}\n```\n\nand\n\n```smithy\n$version: \"2\"\n\nnamespace example.helloWorldService\n\nuse example#Bounce\nuse example#Crash\n\n@http(method: GET, uri: \"/hello/{name}\", code: 200)\noperation hello {\n  input := {\n    @required\n    @httpLabel\n    name: String\n\n    @httpQuery(\"from\")\n    location: String\n  }\n  output := {\n    @httpPayload\n    value: String\n  }\n\n  errors: [Bounce, Crash]\n}\n```\n\n\n### NB :\n\n* All parameters of methods and all output types (within the effect) must have implicit (given) schemas available.\n* Defaults are supported\n* Scaladoc is converted to `@documentation` hints\n\n## More concise annotations\n\nTo reduce the verbosity induced by the `hints` annotation, it is possible to define custom annotations that create the responsibility of\ncreating hints, as such :\n\n```scala\nimport smithy4s.Hints\nimport smithy4s.deriving.HintsProvider\n\ncase class httpGet(uri: String, status: Int) extends HintsProvider {\n  def hints = Hints(Http(NonEmptyString(\"GET\"), NonEmptyString(uri), status))\n}\n```\n\nA few of these more-concise annotations are provided out-of-the-box in the `smithy4s.deriving.aliases` package.\n\n## Difference between smithy4s.deriving.API and and smithy4s.Service\n\nAs you may have noted, instead of deriving `smithy4s.Service`, we're deriving `smithy4s.deriving.API` instead. That is because the `Service` abstraction expects a polymorphic type, whereas our interface is monomorphic. Therefore, the `API` construct allows to turn instances of our interface into a \"virtual\" interface that does abide by the kind that `smithy4s.Service` expects. Because of this slight mismatch, the user is expected to perform a call to the `.liftService` extension on the instance of the interface when wiring it into interpreters that are coming from smithy4s, such as :\n\n```scala\nSimpleRestJsonBuilder\n  .routes(new HelloWorldService().liftService[IO])\n  .resource\n  .map(_.orNotFound)\n```\n\nThe derivation works for monomorphic interfaces (and concrete classes) that carry methods that are homogenous in effect type. This means that the derivation will fail for an interface like this.\n\n```scala\ntrait Foo {\n  def bar() : IO[Boolean]\n  def baz() : Int\n}\n```\n\nIt will however work for direct-style interfaces, such as :\n\n```scala\ntrait Foo {\n  def bar() : Boolean\n  def baz() : Int\n}\n```\n\nor for any other mono-functor effect (`IO`, `Future`, type aliases to `type Result[A] = Either[String, A]`, etc).\n\n### Transformations\n\nIt is possible to apply generic transformations to an implementation of a service :\n\n```scala\nclass Foo() derives API {\n  def bar(x: Int) : IO[Int] = IO(x)\n}\n\nval addDelay = new PolyFunction[IO, IO]{\n  def apply[A](io: IO[A]) : IO[A] = IO.sleep(1.second) *\u003e io\n}\n\nnew Foo().transform(addDelay)\n```\n\nA lot more is possible to achieve, but I don't have time to write much docs.\n\n### Stubs\n\nsmithy4s-deriving, combined with the `export` keyword introduced by Scala 3, makes it reasonably easy to implement mocks/stubs for interfaces that have many methods :\n\n```scala\ntrait Foo() derives API {\n  def foo(x: Int): Try[Int]\n\n  def bar(x: Int): Try[Int]\n}\n\n// creates a stub that will implement all methods by returning `Failure(Boom)`\nval stub = API[Foo].default[Try](Failure(Boom))\nval instance = new Foo {\n  export stub.{foo =\u003e _, *}\n  override def foo(x: Int): Try[Int] = Success(x + 2)\n}\n```\n\n### Compile time validation\n\nThe `smithy4s-deriving-compiler` permits the validation of API/Schema usage within the compilation cycle. At the time of writing this, the plugin works by looking up derived `API` instances and crawling through the schemas from there, which implies that standalone `Schema` instances that are not (transitively) tied to `API` instances are not validated at compile time.\n\n\n\n### Re-creating a smithy-model from the derived constructs (JVM only)\n\nIt is possible to automatically recreate a smithy model from the derived abstractions, and to run the smithy validators. One could use this in a unit test, for instance, to verify the correctness of their services according to the rules of smithy.\n\nSee an example of how to do that [here](./modules/examples/jvm/src/main/scala/printSpecs.scala).\n\n### Known limitations\n\n* Default parameters are captured in schemas of case classes, but not for methods, unless the `-Yretain-trees` compiler option is set.\n* `API` derivation is using experimental features from the Scala meta-programming tooling, which implies an invasive (but justified) requirement to annotate stuff with `@experimental`\n* The [smithy to openapi](https://disneystreaming.github.io/smithy4s/docs/protocols/simple-rest-json/openapi) conversion feature provided by smithy4s happens at build-time, via the build plugins. This unfortunately implies that users wanting to use smithy4s-deriving will not benefit from the simpler one-liner allowing to serve swagger-ui.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneandertech%2Fsmithy4s-deriving","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneandertech%2Fsmithy4s-deriving","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneandertech%2Fsmithy4s-deriving/lists"}