├── project ├── build.properties ├── plugins.sbt ├── BuildPlugin.scala └── Dependencies.scala ├── .gitattributes ├── .sbtopts ├── modules ├── transformers │ └── src │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── software.amazon.smithy.build.ProjectionTransformer │ │ └── main │ │ └── scala │ │ └── smithy4s │ │ └── zio │ │ └── transformers │ │ └── SimpleRestJsonProtocolTransformer.scala ├── compliance-tests │ └── src │ │ └── main │ │ └── scala │ │ └── smithy4s │ │ └── zio │ │ └── compliancetests │ │ ├── internals │ │ ├── NoInputOp.scala │ │ ├── IntendedShortCircuit.scala │ │ ├── eq │ │ │ └── Smithy4sEqInstances.scala │ │ ├── HttpAppDriver.scala │ │ ├── ErrorResponseTest.scala │ │ ├── CanonicalSmithyDecoder.scala │ │ ├── DefaultSchemaVisitor.scala │ │ └── package.scala │ │ ├── ShouldRun.scala │ │ ├── ComplianceTest.scala │ │ ├── Router.scala │ │ ├── ReverseRouter.scala │ │ ├── TestConfig.scala │ │ ├── HttpProtocolCompliance.scala │ │ ├── AllowRules.scala │ │ ├── package.scala │ │ └── ProtocolComplianceSuite.scala ├── examples │ └── src │ │ └── main │ │ ├── scala │ │ └── smithy4s │ │ │ └── zio │ │ │ └── examples │ │ │ ├── package.scala │ │ │ └── todo │ │ │ ├── package.scala │ │ │ ├── impl │ │ │ ├── PrimaryKeyGen.scala │ │ │ ├── Database.scala │ │ │ ├── InMemoryDatabase.scala │ │ │ └── ToDoImpl.scala │ │ │ ├── ServerExample.scala │ │ │ └── ClientExample.scala │ │ └── smithy │ │ ├── Hello.smithy │ │ └── Todo.smithy ├── prelude │ └── src │ │ ├── main │ │ └── scala │ │ │ └── smithy4s │ │ │ └── zio │ │ │ └── prelude │ │ │ ├── instances │ │ │ ├── package.scala │ │ │ ├── EqualsInstances.scala │ │ │ ├── HashInstances.scala │ │ │ ├── ZSchemaInstances.scala │ │ │ └── DebugInstances.scala │ │ │ ├── package.scala │ │ │ ├── SchemaVisitorZSchemaGen.scala │ │ │ ├── SchemaVisitorDebug.scala │ │ │ ├── SchemaVisitorEqual.scala │ │ │ └── SchemaVisitorHash.scala │ │ └── test │ │ └── scala │ │ └── smithy4s │ │ └── zio │ │ └── prelude │ │ └── testcases │ │ ├── RecursiveFoo.scala │ │ ├── IntOrInt.scala │ │ ├── IntOrString.scala │ │ ├── FooBar.scala │ │ └── IntFooBar.scala ├── test-scenarios │ └── src │ │ ├── generated │ │ └── smithy4s │ │ │ └── example │ │ │ ├── guides │ │ │ └── auth │ │ │ │ ├── package.scala │ │ │ │ ├── World.scala │ │ │ │ ├── HealthCheckOutput.scala │ │ │ │ └── NotAuthorizedError.scala │ │ │ ├── CityId.scala │ │ │ ├── Ingredients.scala │ │ │ ├── CitySummaries.scala │ │ │ ├── UVIndex.scala │ │ │ ├── Menu.scala │ │ │ ├── ChanceOfRain.scala │ │ │ ├── GetEnumInput.scala │ │ │ ├── FreeForm.scala │ │ │ ├── GetForecastInput.scala │ │ │ ├── GetEnumOutput.scala │ │ │ ├── CustomCodeInput.scala │ │ │ ├── EchoBody.scala │ │ │ ├── GetMenuResult.scala │ │ │ ├── MenuItem.scala │ │ │ ├── VersionOutput.scala │ │ │ ├── GetIntEnumOutput.scala │ │ │ ├── GetIntEnumInput.scala │ │ │ ├── package.scala │ │ │ ├── GetMenuRequest.scala │ │ │ ├── CustomCodeOutput.scala │ │ │ ├── HeadRequestOutput.scala │ │ │ ├── Salad.scala │ │ │ ├── ReservationOutput.scala │ │ │ ├── GetCurrentTimeOutput.scala │ │ │ ├── FallbackError.scala │ │ │ ├── FallbackError2.scala │ │ │ ├── HealthRequest.scala │ │ │ ├── RecursiveInput.scala │ │ │ ├── CityCoordinates.scala │ │ │ ├── GetCityInput.scala │ │ │ ├── NotFoundError.scala │ │ │ ├── GetCityOutput.scala │ │ │ ├── NoSuchResource.scala │ │ │ ├── OptionalOutputOutput.scala │ │ │ ├── HealthResponse.scala │ │ │ ├── Pizza.scala │ │ │ ├── ListCitiesOutput.scala │ │ │ ├── ListCitiesInput.scala │ │ │ ├── ReservationInput.scala │ │ │ ├── CitySummary.scala │ │ │ ├── AddMenuItemRequest.scala │ │ │ ├── GenericClientError.scala │ │ │ ├── GenericServerError.scala │ │ │ ├── GetForecastOutput.scala │ │ │ ├── PriceError.scala │ │ │ ├── AddMenuItemResult.scala │ │ │ ├── EchoInput.scala │ │ │ ├── RoundTripData.scala │ │ │ ├── TheEnum.scala │ │ │ ├── PizzaBase.scala │ │ │ ├── EnumResult.scala │ │ │ ├── UnknownServerError.scala │ │ │ ├── UnknownServerErrorCode.scala │ │ │ ├── Ingredient.scala │ │ │ ├── HeaderEndpointData.scala │ │ │ ├── Food.scala │ │ │ ├── ForecastResult.scala │ │ │ └── RecursiveInputService.scala │ │ └── smithy │ │ ├── recursiveInput.smithy │ │ ├── auth-guide.smithy │ │ └── weather.smithy ├── http │ └── src │ │ └── main │ │ └── scala │ │ └── smithy4s │ │ └── zio │ │ └── http │ │ ├── protocol │ │ ├── SimpleProtocolCodecs.scala │ │ └── SimpleProtocolBuilder.scala │ │ ├── internal │ │ ├── ZHttpToSmithy4sClient.scala │ │ ├── builders │ │ │ ├── client │ │ │ │ └── ClientBuilder.scala │ │ │ └── server │ │ │ │ └── RouterBuilder.scala │ │ └── SimpleRestJsonCodecs.scala │ │ ├── middleware │ │ ├── ClientEndpointMiddleware.scala │ │ └── ServerEndpointMiddleware.scala │ │ ├── SimpleRestJsonBuilder.scala │ │ └── package.scala ├── tests │ └── src │ │ └── test │ │ └── scala │ │ └── smithy4s │ │ └── zio │ │ └── http │ │ ├── RecursiveInputSpec.scala │ │ ├── ProtocolBuilderSpec.scala │ │ ├── compliance │ │ └── ZIOHttpRestJsonProtocolTests.scala │ │ └── ServiceBuilderZIOHttpSpec.scala └── shared │ └── src │ └── main │ └── scala │ └── smithy4s │ └── zio │ └── shared │ └── utils │ └── UrlCodingUtils.scala ├── .scalafmt.conf ├── .scalafix.conf ├── .gitignore ├── .git-blame-ignore-revs ├── publish.sbt ├── .mergify.yml ├── README.md ├── .github └── workflows │ └── clean.yml └── docs └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.11.6 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.smithy linguist-language=Kotlin 2 | -------------------------------------------------------------------------------- /.sbtopts: -------------------------------------------------------------------------------- 1 | -J-XX:MaxMetaspaceSize=4G 2 | -J-XX:MaxInlineLevel=20 3 | -J-Xss2m 4 | -J-Xms512M 5 | -J-Xmx6G 6 | -J-XX:ReservedCodeCacheSize=256M -------------------------------------------------------------------------------- /modules/transformers/src/resources/META-INF/services/software.amazon.smithy.build.ProjectionTransformer: -------------------------------------------------------------------------------- 1 | smithy4s.zio.transformers.SimpleRestJsonProtocolTransformer 2 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/NoInputOp.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests.internals 2 | 3 | case class NoInputOp[I_, E_, O_, SE_, SO_]() 4 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | 2 | version = 3.9.10 3 | runner.dialect = scala213source3 4 | docstrings.style = keep 5 | 6 | fileOverride { 7 | "glob:**/scala-3/**" { 8 | runner.dialect = scala3 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio 2 | 3 | import zio.{Scope, ZIO} 4 | 5 | package object examples { 6 | 7 | type ResourcefulTask[A] = ZIO[Scope, Throwable, A] 8 | } 9 | -------------------------------------------------------------------------------- /.scalafix.conf: -------------------------------------------------------------------------------- 1 | rules = [ 2 | LeakingImplicitClassVal, 3 | ProcedureSyntax, 4 | RemoveUnused 5 | ] 6 | OrganizeImports { 7 | coalesceToWildcardImportThreshold = 5 8 | expandRelative = true 9 | groupedImports = Merge 10 | } 11 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/IntendedShortCircuit.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests.internals 2 | 3 | private[compliancetests] case class IntendedShortCircuit() 4 | extends scala.util.control.NoStackTrace 5 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/todo/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.examples 2 | 3 | import zio.{Scope, ZIO} 4 | 5 | package object todo { 6 | 7 | org.http4s.Uri 8 | type ResourcefulTask[A] = ZIO[Scope, Throwable, A] 9 | } 10 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/instances/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | 3 | package object instances { 4 | 5 | private[prelude] object all 6 | extends EqualsInstances 7 | with HashInstances 8 | with DebugInstances 9 | 10 | } 11 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/guides/auth/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example.guides 2 | 3 | package object auth { 4 | type HelloWorldAuthService[F[_]] = smithy4s.kinds.FunctorAlgebra[HelloWorldAuthServiceGen, F] 5 | val HelloWorldAuthService = HelloWorldAuthServiceGen 6 | 7 | 8 | } -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/ShouldRun.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | sealed trait ShouldRun 4 | 5 | object ShouldRun { 6 | case object Yes extends ShouldRun 7 | case object No extends ShouldRun 8 | case object NotSure extends ShouldRun 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # sbt 2 | target/ 3 | project/plugins/project/ 4 | boot/ 5 | lib_managed/ 6 | src_managed/ 7 | 8 | # vim 9 | *.sw? 10 | 11 | # intellij 12 | .idea/ 13 | 14 | # ignore [ce]tags files 15 | tags 16 | 17 | # metals 18 | .metals/ 19 | .bsp/ 20 | .bloop/ 21 | metals.sbt 22 | .vscode 23 | 24 | # npm 25 | node_modules/ 26 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/todo/impl/PrimaryKeyGen.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.examples.todo.impl 2 | 3 | import zio.{Task, ZIO} 4 | 5 | trait PrimaryKeyGen { 6 | 7 | def generate(): Task[String] 8 | } 9 | 10 | object PrimaryKeyGen { 11 | 12 | def default(): PrimaryKeyGen = () => 13 | ZIO.succeed(java.util.UUID.randomUUID().toString) 14 | } 15 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio 2 | 3 | import scala.util.hashing.MurmurHash3 4 | 5 | package object prelude { 6 | 7 | def combineHash(start: Int, hashes: Int*): Int = { 8 | var hashResult = start 9 | hashes.foreach(hash => hashResult = MurmurHash3.mix(hashResult, hash)) 10 | MurmurHash3.finalizeHash(hashResult, hashes.length) 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/todo/impl/Database.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.examples.todo.impl 2 | 3 | import zio.Task 4 | 5 | trait Database[A] { 6 | 7 | def insert(a: A): Task[Unit] 8 | def get(id: String): Task[Option[A]] 9 | def update(id: String, a: A): Task[Unit] 10 | def delete(id: String): Task[Unit] 11 | def list(): Task[List[A]] 12 | def deleteAll(): Task[Unit] 13 | def count(): Task[Int] 14 | 15 | } 16 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/smithy/recursiveInput.smithy: -------------------------------------------------------------------------------- 1 | namespace smithy4s.example 2 | 3 | use alloy#simpleRestJson 4 | 5 | @simpleRestJson 6 | service RecursiveInputService { 7 | version: "0.0.1", 8 | operations: [RecursiveInputOperation], 9 | } 10 | 11 | @http(method: "PUT", uri: "/subscriptions") 12 | @idempotent 13 | operation RecursiveInputOperation { 14 | input: RecursiveInput, 15 | } 16 | 17 | structure RecursiveInput { 18 | hello: RecursiveInput 19 | } 20 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Scala Steward: Reformat with scalafmt 3.7.17 2 | cc0d9241acb5ff722b41c46fdcabf0d7e86dc736 3 | 4 | # Scala Steward: Reformat with scalafmt 3.8.1 5 | 9663bfec802e699762d6061de4e2372340cb4ef5 6 | 7 | # Scala Steward: Reformat with scalafmt 3.8.2 8 | 5d21e0bd7b146085a698b8bc4c3006eb1d4a9c80 9 | 10 | # Scala Steward: Reformat with scalafmt 3.8.6 11 | 4c483bf5824fc863177298e7d71fe541e649cb94 12 | 13 | # Scala Steward: Reformat with scalafmt 3.9.9 14 | c0a71df74e5f2a6286a45f42503ed57529aebb7f 15 | -------------------------------------------------------------------------------- /modules/prelude/src/test/scala/smithy4s/zio/prelude/testcases/RecursiveFoo.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | package testcases 3 | 4 | import smithy4s.schema.Schema 5 | import smithy4s.schema.Schema.* 6 | import smithy4s.ShapeId 7 | 8 | case class RecursiveFoo(foo: Option[RecursiveFoo]) 9 | 10 | object RecursiveFoo { 11 | val schema: Schema[RecursiveFoo] = 12 | recursive { 13 | val foos = schema.optional[RecursiveFoo]("foo", _.foo) 14 | struct(foos)(RecursiveFoo.apply) 15 | }.withId(ShapeId("", "RecursiveFoo")) 16 | } 17 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/protocol/SimpleProtocolCodecs.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.protocol 2 | 3 | import smithy4s.client.* 4 | import smithy4s.server.UnaryServerCodecs 5 | import smithy4s.zio.http.ResourcefulTask 6 | import zio.Task 7 | import zio.http.{Request, Response, URL} 8 | 9 | // scalafmt: { maxColumn = 120 } 10 | trait SimpleProtocolCodecs { 11 | def makeClientCodecs(baseUri: URL): UnaryClientCodecs.Make[ResourcefulTask, Request, Response] 12 | def makeServerCodecs: UnaryServerCodecs.Make[Task, Request, Response] 13 | 14 | } 15 | -------------------------------------------------------------------------------- /modules/examples/src/main/smithy/Hello.smithy: -------------------------------------------------------------------------------- 1 | $version: "2.0" 2 | 3 | 4 | namespace example.hello 5 | 6 | use alloy#simpleRestJson 7 | 8 | @simpleRestJson 9 | service HelloWorldService { 10 | version: "1.0.0", 11 | operations: [Hello] 12 | } 13 | 14 | @http(method: "POST", uri: "/{name}", code: 200) 15 | operation Hello { 16 | input: Person, 17 | output: Greeting 18 | } 19 | 20 | structure Person { 21 | @httpLabel 22 | @required 23 | name: String, 24 | 25 | @httpQuery("town") 26 | town: String 27 | } 28 | 29 | structure Greeting { 30 | @required 31 | message: String 32 | } 33 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/ComplianceTest.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import smithy4s.ShapeId 4 | import smithy4s.zio.compliancetests.ComplianceTest.ComplianceResult 5 | import zio.test.TestResult 6 | 7 | case class ComplianceTest[F[_]]( 8 | id: String, 9 | protocol: ShapeId, 10 | endpoint: ShapeId, 11 | documentation: Option[String], 12 | config: TestConfig, 13 | run: F[ComplianceResult] 14 | ) { 15 | def show = s"${endpoint.id}${config.show}: $id" 16 | } 17 | 18 | object ComplianceTest { 19 | type ComplianceResult = TestResult 20 | } 21 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/CityId.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Newtype 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.schema.Schema.bijection 8 | import smithy4s.schema.Schema.string 9 | 10 | object CityId extends Newtype[String] { 11 | val id: ShapeId = ShapeId("smithy4s.example", "CityId") 12 | val hints: Hints = Hints.empty 13 | val underlyingSchema: Schema[String] = string.withId(id).addHints(hints).validated(smithy.api.Pattern(s"^[A-Za-z0-9 ]+$$")) 14 | implicit val schema: Schema[CityId] = bijection(underlyingSchema, asBijection) 15 | } 16 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/Ingredients.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Newtype 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.schema.Schema.bijection 8 | import smithy4s.schema.Schema.list 9 | 10 | object Ingredients extends Newtype[List[Ingredient]] { 11 | val id: ShapeId = ShapeId("smithy4s.example", "Ingredients") 12 | val hints: Hints = Hints.empty 13 | val underlyingSchema: Schema[List[Ingredient]] = list(Ingredient.schema).withId(id).addHints(hints) 14 | implicit val schema: Schema[Ingredients] = bijection(underlyingSchema, asBijection) 15 | } 16 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/Router.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import smithy4s.{Service, ShapeTag} 4 | import smithy4s.kinds.FunctorAlgebra 5 | import zio.Task 6 | 7 | /* A construct encapsulating the action of turning an algebra implementation into 8 | * an http route (modelled using Http4s, but could be backed by any other library 9 | * by means of proxyfication) 10 | */ 11 | trait Router { 12 | type Protocol 13 | def protocolTag: ShapeTag[Protocol] 14 | 15 | def routes[Alg[_[_, _, _, _, _]]](alg: FunctorAlgebra[Alg, Task])(implicit 16 | service: Service[Alg] 17 | ): Task[HttpRoutes] 18 | } 19 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/CitySummaries.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Newtype 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.schema.Schema.bijection 8 | import smithy4s.schema.Schema.list 9 | 10 | object CitySummaries extends Newtype[List[CitySummary]] { 11 | val id: ShapeId = ShapeId("smithy4s.example", "CitySummaries") 12 | val hints: Hints = Hints.empty 13 | val underlyingSchema: Schema[List[CitySummary]] = list(CitySummary.schema).withId(id).addHints(hints) 14 | implicit val schema: Schema[CitySummaries] = bijection(underlyingSchema, asBijection) 15 | } 16 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/UVIndex.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Newtype 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.schema.Schema.bijection 8 | import smithy4s.schema.Schema.int 9 | 10 | object UVIndex extends Newtype[Int] { 11 | val id: ShapeId = ShapeId("smithy4s.example", "UVIndex") 12 | val hints: Hints = Hints( 13 | smithy.api.Default(smithy4s.Document.fromDouble(0.0d)), 14 | ).lazily 15 | val underlyingSchema: Schema[Int] = int.withId(id).addHints(hints) 16 | implicit val schema: Schema[UVIndex] = bijection(underlyingSchema, asBijection) 17 | } 18 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/Menu.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Newtype 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.schema.Schema.bijection 8 | import smithy4s.schema.Schema.map 9 | import smithy4s.schema.Schema.string 10 | 11 | object Menu extends Newtype[Map[String, MenuItem]] { 12 | val id: ShapeId = ShapeId("smithy4s.example", "Menu") 13 | val hints: Hints = Hints.empty 14 | val underlyingSchema: Schema[Map[String, MenuItem]] = map(string, MenuItem.schema).withId(id).addHints(hints) 15 | implicit val schema: Schema[Menu] = bijection(underlyingSchema, asBijection) 16 | } 17 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/ChanceOfRain.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Newtype 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.schema.Schema.bijection 8 | import smithy4s.schema.Schema.float 9 | 10 | object ChanceOfRain extends Newtype[Float] { 11 | val id: ShapeId = ShapeId("smithy4s.example", "ChanceOfRain") 12 | val hints: Hints = Hints( 13 | smithy.api.Default(smithy4s.Document.fromDouble(0.0d)), 14 | ).lazily 15 | val underlyingSchema: Schema[Float] = float.withId(id).addHints(hints) 16 | implicit val schema: Schema[ChanceOfRain] = bijection(underlyingSchema, asBijection) 17 | } 18 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/internal/ZHttpToSmithy4sClient.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.internal 2 | 3 | import smithy4s.client.UnaryLowLevelClient 4 | import smithy4s.zio.http.ResourcefulTask 5 | import zio.http.{Client, Request, Response} 6 | import zio.{RIO, Scope} 7 | 8 | private[http] object ZHttpToSmithy4sClient { 9 | 10 | def apply( 11 | client: Client 12 | ): UnaryLowLevelClient[ResourcefulTask, Request, Response] = { 13 | new UnaryLowLevelClient[ResourcefulTask, Request, Response] { 14 | def run[Output](request: Request)( 15 | cb: Response => RIO[Scope, Output] 16 | ): ResourcefulTask[Output] = 17 | client.request(request).flatMap(cb) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/middleware/ClientEndpointMiddleware.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.middleware 2 | 3 | import smithy4s.zio.http.ClientEndpointMiddleware 4 | import smithy4s.{Endpoint, Hints, Service} 5 | import zio.http.Client 6 | 7 | object ClientEndpointMiddleware { 8 | 9 | trait Simple extends ClientEndpointMiddleware { 10 | def prepareWithHints( 11 | serviceHints: Hints, 12 | endpointHints: Hints 13 | ): Client => Client 14 | 15 | final def prepare[Alg[_[_, _, _, _, _]]](service: Service[Alg])( 16 | endpoint: Endpoint[service.Operation, ?, ?, ?, ?, ?] 17 | ): Client => Client = 18 | prepareWithHints(service.hints, endpoint.hints) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /modules/prelude/src/test/scala/smithy4s/zio/prelude/testcases/IntOrInt.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | 3 | package testcases 4 | 5 | import smithy4s.schema.Schema.* 6 | import smithy4s.schema.Schema 7 | import smithy4s.ShapeId 8 | 9 | sealed trait IntOrInt 10 | object IntOrInt { 11 | case class IntValue0(value: Int) extends IntOrInt 12 | case class IntValue1(value: Int) extends IntOrInt 13 | 14 | val schema: Schema[IntOrInt] = { 15 | val intValue0 = int.oneOf[IntOrInt]("intValue0", IntValue0(_)) { 16 | case IntValue0(i) => i 17 | } 18 | val intValue1 = int.oneOf[IntOrInt]("intValue1", IntValue1(_)) { 19 | case IntValue1(i) => i 20 | } 21 | union(intValue0, intValue1).reflective 22 | }.withId(ShapeId("", "IntOrInt")) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.11.0") 2 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.8") 3 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.8") 4 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") 5 | addSbtPlugin( 6 | "com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % "0.18.42" 7 | ) 8 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.5") 9 | addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.1") 10 | addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % "0.8.1") 11 | addSbtPlugin("org.typelevel" % "sbt-typelevel-scalafix" % "0.8.1") 12 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.7.2") 13 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1") 14 | // sbt revolver 15 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.10.0") 16 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetEnumInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.struct 8 | 9 | final case class GetEnumInput(aa: TheEnum) 10 | 11 | object GetEnumInput extends ShapeTag.Companion[GetEnumInput] { 12 | val id: ShapeId = ShapeId("smithy4s.example", "GetEnumInput") 13 | 14 | val hints: Hints = Hints.empty 15 | 16 | // constructor using the original order from the spec 17 | private def make(aa: TheEnum): GetEnumInput = GetEnumInput(aa) 18 | 19 | implicit val schema: Schema[GetEnumInput] = struct( 20 | TheEnum.schema.required[GetEnumInput]("aa", _.aa).addHints(smithy.api.HttpLabel()), 21 | )(make).withId(id).addHints(hints) 22 | } 23 | -------------------------------------------------------------------------------- /publish.sbt: -------------------------------------------------------------------------------- 1 | import scala.collection.immutable.Seq 2 | 3 | ThisBuild / tlBaseVersion := "0.0" // your current series x.y 4 | 5 | ThisBuild / organization := "io.github.yisraelu" 6 | ThisBuild / organizationName := "yisraelu" 7 | ThisBuild / description := "ZIO bindings for Smithy4s" 8 | ThisBuild / startYear := Some(2023) 9 | ThisBuild / licenses := Seq(License.Apache2) 10 | ThisBuild / developers := List( 11 | // your GitHub handle and name 12 | tlGitHubDev("yisraelu", "Yisrael Union") 13 | ) 14 | 15 | // publish website from this branch 16 | ThisBuild / tlSitePublishBranch := Some("main") 17 | 18 | ThisBuild / tlCiHeaderCheck := false 19 | ThisBuild / tlFatalWarnings := false 20 | ThisBuild / tlCiMimaBinaryIssueCheck := false 21 | ThisBuild / tlJdkRelease := Some(11) 22 | ThisBuild / tlCiDependencyGraphJob := false 23 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/ReverseRouter.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import smithy4s.http.HttpMediaType 4 | import smithy4s.kinds.FunctorAlgebra 5 | import smithy4s.schema.Schema 6 | import smithy4s.{Service, ShapeTag} 7 | import zio.Task 8 | import zio.http.{Response, Routes} 9 | 10 | /* A construct encapsulating the action of turning an http4s route into 11 | * an an algebra 12 | */ 13 | trait ReverseRouter { 14 | type Protocol 15 | def protocolTag: ShapeTag[Protocol] 16 | def expectedResponseType(schema: Schema[_]): HttpMediaType 17 | 18 | def reverseRoutes[Alg[_[_, _, _, _, _]]]( 19 | routes: Routes[Any, Response], 20 | host: Option[String] = None 21 | )(implicit service: Service[Alg]): Task[FunctorAlgebra[Alg, ResourcefulTask]] 22 | } 23 | -------------------------------------------------------------------------------- /modules/prelude/src/test/scala/smithy4s/zio/prelude/testcases/IntOrString.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | 3 | package testcases 4 | 5 | import smithy4s.schema.Schema._ 6 | import smithy4s.schema.Schema 7 | import smithy4s.ShapeId 8 | 9 | sealed trait IntOrString 10 | 11 | object IntOrString { 12 | case class IntValue(value: Int) extends IntOrString 13 | 14 | case class StringValue(value: String) extends IntOrString 15 | 16 | val schema: Schema[IntOrString] = { 17 | val intValue = int.oneOf[IntOrString]("intValue", IntValue(_)) { 18 | case IntValue(int) => int 19 | } 20 | val stringValue = string.oneOf[IntOrString]("stringValue", StringValue(_)) { 21 | case StringValue(str) => str 22 | } 23 | union(intValue, stringValue).reflective.withId(ShapeId("", "IntOrString")) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/FreeForm.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Document 4 | import smithy4s.Hints 5 | import smithy4s.Newtype 6 | import smithy4s.Schema 7 | import smithy4s.ShapeId 8 | import smithy4s.schema.Schema.bijection 9 | import smithy4s.schema.Schema.document 10 | import smithy4s.schema.Schema.recursive 11 | 12 | object FreeForm extends Newtype[Document] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "freeForm") 14 | val hints: Hints = Hints( 15 | smithy.api.Trait(selector = None, structurallyExclusive = None, conflicts = None, breakingChanges = None), 16 | ).lazily 17 | val underlyingSchema: Schema[Document] = document.withId(id).addHints(hints) 18 | implicit val schema: Schema[FreeForm] = recursive(bijection(underlyingSchema, asBijection)) 19 | } 20 | -------------------------------------------------------------------------------- /project/BuildPlugin.scala: -------------------------------------------------------------------------------- 1 | import sbt.* 2 | import sbt.Keys.* 3 | 4 | object BuildPlugin extends AutoPlugin { 5 | 6 | val scalaVersionSuffix = Def 7 | .setting { 8 | scalaBinaryVersion.value match { 9 | case "2.11" => Seq("-2", "-2.11") 10 | case "2.12" => Seq("-2", "-2.12") 11 | case "2.13" => Seq("-2", "-2.13") 12 | case _ => Seq("-3") 13 | } 14 | } 15 | 16 | lazy val compilerPlugins = Seq( 17 | libraryDependencies ++= { 18 | if (scalaVersion.value.startsWith("2.")) 19 | Seq( 20 | compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), 21 | compilerPlugin( 22 | "org.typelevel" % "kind-projector" % "0.13.4" cross CrossVersion.full 23 | ) 24 | ) 25 | else Seq.empty 26 | } 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetForecastInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.struct 8 | 9 | final case class GetForecastInput(cityId: CityId) 10 | 11 | object GetForecastInput extends ShapeTag.Companion[GetForecastInput] { 12 | val id: ShapeId = ShapeId("smithy4s.example", "GetForecastInput") 13 | 14 | val hints: Hints = Hints.empty 15 | 16 | // constructor using the original order from the spec 17 | private def make(cityId: CityId): GetForecastInput = GetForecastInput(cityId) 18 | 19 | implicit val schema: Schema[GetForecastInput] = struct( 20 | CityId.schema.required[GetForecastInput]("cityId", _.cityId), 21 | )(make).withId(id).addHints(hints) 22 | } 23 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetEnumOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class GetEnumOutput(result: Option[String] = None) 11 | 12 | object GetEnumOutput extends ShapeTag.Companion[GetEnumOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "GetEnumOutput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(result: Option[String]): GetEnumOutput = GetEnumOutput(result) 19 | 20 | implicit val schema: Schema[GetEnumOutput] = struct( 21 | string.optional[GetEnumOutput]("result", _.result), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/CustomCodeInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.int 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class CustomCodeInput(code: Int) 11 | 12 | object CustomCodeInput extends ShapeTag.Companion[CustomCodeInput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "CustomCodeInput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(code: Int): CustomCodeInput = CustomCodeInput(code) 19 | 20 | implicit val schema: Schema[CustomCodeInput] = struct( 21 | int.required[CustomCodeInput]("code", _.code).addHints(smithy.api.HttpLabel()), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/EchoBody.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class EchoBody(data: Option[String] = None) 11 | 12 | object EchoBody extends ShapeTag.Companion[EchoBody] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "EchoBody") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(data: Option[String]): EchoBody = EchoBody(data) 19 | 20 | implicit val schema: Schema[EchoBody] = struct( 21 | string.validated(smithy.api.Length(min = Some(10L), max = None)).optional[EchoBody]("data", _.data), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetMenuResult.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.struct 8 | 9 | final case class GetMenuResult(menu: Map[String, MenuItem]) 10 | 11 | object GetMenuResult extends ShapeTag.Companion[GetMenuResult] { 12 | val id: ShapeId = ShapeId("smithy4s.example", "GetMenuResult") 13 | 14 | val hints: Hints = Hints.empty 15 | 16 | // constructor using the original order from the spec 17 | private def make(menu: Map[String, MenuItem]): GetMenuResult = GetMenuResult(menu) 18 | 19 | implicit val schema: Schema[GetMenuResult] = struct( 20 | Menu.underlyingSchema.required[GetMenuResult]("menu", _.menu).addHints(smithy.api.HttpPayload()), 21 | )(make).withId(id).addHints(hints) 22 | } 23 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/smithy/auth-guide.smithy: -------------------------------------------------------------------------------- 1 | $version: "2" 2 | 3 | namespace smithy4s.example.guides.auth 4 | 5 | use alloy#simpleRestJson 6 | 7 | @simpleRestJson 8 | @httpBearerAuth 9 | service HelloWorldAuthService { 10 | version: "1.0.0" 11 | operations: [SayWorld, HealthCheck] 12 | errors: [NotAuthorizedError] 13 | } 14 | 15 | @readonly 16 | @http(method: "GET", uri: "/hello", code: 200) 17 | operation SayWorld { 18 | output: World 19 | } 20 | 21 | @readonly 22 | @http(method: "GET", uri: "/health", code: 200) 23 | @auth([]) 24 | operation HealthCheck { 25 | output := { 26 | @required 27 | message: String 28 | } 29 | } 30 | 31 | structure World { 32 | message: String = "World !" 33 | } 34 | 35 | @error("client") 36 | @httpError(401) 37 | structure NotAuthorizedError { 38 | @required 39 | message: String 40 | } 41 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/MenuItem.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.float 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class MenuItem(food: Food, price: Float) 11 | 12 | object MenuItem extends ShapeTag.Companion[MenuItem] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "MenuItem") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(food: Food, price: Float): MenuItem = MenuItem(food, price) 19 | 20 | implicit val schema: Schema[MenuItem] = struct( 21 | Food.schema.required[MenuItem]("food", _.food), 22 | float.required[MenuItem]("price", _.price), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/VersionOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class VersionOutput(version: String) 11 | 12 | object VersionOutput extends ShapeTag.Companion[VersionOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "VersionOutput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(version: String): VersionOutput = VersionOutput(version) 19 | 20 | implicit val schema: Schema[VersionOutput] = struct( 21 | string.required[VersionOutput]("version", _.version).addHints(smithy.api.HttpPayload()), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetIntEnumOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.struct 8 | 9 | final case class GetIntEnumOutput(result: EnumResult) 10 | 11 | object GetIntEnumOutput extends ShapeTag.Companion[GetIntEnumOutput] { 12 | val id: ShapeId = ShapeId("smithy4s.example", "GetIntEnumOutput") 13 | 14 | val hints: Hints = Hints( 15 | smithy.api.Output(), 16 | ).lazily 17 | 18 | // constructor using the original order from the spec 19 | private def make(result: EnumResult): GetIntEnumOutput = GetIntEnumOutput(result) 20 | 21 | implicit val schema: Schema[GetIntEnumOutput] = struct( 22 | EnumResult.schema.required[GetIntEnumOutput]("result", _.result), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetIntEnumInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.struct 8 | 9 | final case class GetIntEnumInput(aa: EnumResult) 10 | 11 | object GetIntEnumInput extends ShapeTag.Companion[GetIntEnumInput] { 12 | val id: ShapeId = ShapeId("smithy4s.example", "GetIntEnumInput") 13 | 14 | val hints: Hints = Hints( 15 | smithy.api.Input(), 16 | ).lazily 17 | 18 | // constructor using the original order from the spec 19 | private def make(aa: EnumResult): GetIntEnumInput = GetIntEnumInput(aa) 20 | 21 | implicit val schema: Schema[GetIntEnumInput] = struct( 22 | EnumResult.schema.required[GetIntEnumInput]("aa", _.aa).addHints(smithy.api.HttpLabel()), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/guides/auth/World.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example.guides.auth 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class World(message: String = "World !") 11 | 12 | object World extends ShapeTag.Companion[World] { 13 | val id: ShapeId = ShapeId("smithy4s.example.guides.auth", "World") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(message: String): World = World(message) 19 | 20 | implicit val schema: Schema[World] = struct( 21 | string.field[World]("message", _.message).addHints(smithy.api.Default(smithy4s.Document.fromString("World !"))), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s 2 | 3 | package object example { 4 | type PizzaAdminService[F[_]] = smithy4s.kinds.FunctorAlgebra[PizzaAdminServiceGen, F] 5 | val PizzaAdminService = PizzaAdminServiceGen 6 | type Weather[F[_]] = smithy4s.kinds.FunctorAlgebra[WeatherGen, F] 7 | val Weather = WeatherGen 8 | type RecursiveInputService[F[_]] = smithy4s.kinds.FunctorAlgebra[RecursiveInputServiceGen, F] 9 | val RecursiveInputService = RecursiveInputServiceGen 10 | 11 | type ChanceOfRain = smithy4s.example.ChanceOfRain.Type 12 | type CityId = smithy4s.example.CityId.Type 13 | type CitySummaries = smithy4s.example.CitySummaries.Type 14 | type FreeForm = smithy4s.example.FreeForm.Type 15 | type Ingredients = smithy4s.example.Ingredients.Type 16 | type Menu = smithy4s.example.Menu.Type 17 | type UVIndex = smithy4s.example.UVIndex.Type 18 | 19 | } -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetMenuRequest.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class GetMenuRequest(restaurant: String) 11 | 12 | object GetMenuRequest extends ShapeTag.Companion[GetMenuRequest] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "GetMenuRequest") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(restaurant: String): GetMenuRequest = GetMenuRequest(restaurant) 19 | 20 | implicit val schema: Schema[GetMenuRequest] = struct( 21 | string.required[GetMenuRequest]("restaurant", _.restaurant).addHints(smithy.api.HttpLabel()), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/CustomCodeOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.int 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class CustomCodeOutput(code: Option[Int] = None) 11 | 12 | object CustomCodeOutput extends ShapeTag.Companion[CustomCodeOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "CustomCodeOutput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(code: Option[Int]): CustomCodeOutput = CustomCodeOutput(code) 19 | 20 | implicit val schema: Schema[CustomCodeOutput] = struct( 21 | int.optional[CustomCodeOutput]("code", _.code).addHints(smithy.api.HttpResponseCode()), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/HeadRequestOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class HeadRequestOutput(test: String) 11 | 12 | object HeadRequestOutput extends ShapeTag.Companion[HeadRequestOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "HeadRequestOutput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(test: String): HeadRequestOutput = HeadRequestOutput(test) 19 | 20 | implicit val schema: Schema[HeadRequestOutput] = struct( 21 | string.required[HeadRequestOutput]("test", _.test).addHints(smithy.api.HttpHeader("Test")), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/Salad.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class Salad(name: String, ingredients: List[Ingredient]) 11 | 12 | object Salad extends ShapeTag.Companion[Salad] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "Salad") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(name: String, ingredients: List[Ingredient]): Salad = Salad(name, ingredients) 19 | 20 | implicit val schema: Schema[Salad] = struct( 21 | string.required[Salad]("name", _.name), 22 | Ingredients.underlyingSchema.required[Salad]("ingredients", _.ingredients), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/ReservationOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class ReservationOutput(message: String) 11 | 12 | object ReservationOutput extends ShapeTag.Companion[ReservationOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "ReservationOutput") 14 | 15 | val hints: Hints = Hints( 16 | smithy.api.Output(), 17 | ).lazily 18 | 19 | // constructor using the original order from the spec 20 | private def make(message: String): ReservationOutput = ReservationOutput(message) 21 | 22 | implicit val schema: Schema[ReservationOutput] = struct( 23 | string.required[ReservationOutput]("message", _.message), 24 | )(make).withId(id).addHints(hints) 25 | } 26 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetCurrentTimeOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Timestamp 8 | import smithy4s.schema.Schema.struct 9 | import smithy4s.schema.Schema.timestamp 10 | 11 | final case class GetCurrentTimeOutput(time: Timestamp) 12 | 13 | object GetCurrentTimeOutput extends ShapeTag.Companion[GetCurrentTimeOutput] { 14 | val id: ShapeId = ShapeId("smithy4s.example", "GetCurrentTimeOutput") 15 | 16 | val hints: Hints = Hints.empty 17 | 18 | // constructor using the original order from the spec 19 | private def make(time: Timestamp): GetCurrentTimeOutput = GetCurrentTimeOutput(time) 20 | 21 | implicit val schema: Schema[GetCurrentTimeOutput] = struct( 22 | timestamp.required[GetCurrentTimeOutput]("time", _.time), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/FallbackError.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class FallbackError(error: String) extends Smithy4sThrowable 12 | 13 | object FallbackError extends ShapeTag.Companion[FallbackError] { 14 | val id: ShapeId = ShapeId("smithy4s.example", "FallbackError") 15 | 16 | val hints: Hints = Hints( 17 | smithy.api.Error.CLIENT.widen, 18 | ).lazily 19 | 20 | // constructor using the original order from the spec 21 | private def make(error: String): FallbackError = FallbackError(error) 22 | 23 | implicit val schema: Schema[FallbackError] = struct( 24 | string.required[FallbackError]("error", _.error), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/FallbackError2.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class FallbackError2(error: String) extends Smithy4sThrowable 12 | 13 | object FallbackError2 extends ShapeTag.Companion[FallbackError2] { 14 | val id: ShapeId = ShapeId("smithy4s.example", "FallbackError2") 15 | 16 | val hints: Hints = Hints( 17 | smithy.api.Error.CLIENT.widen, 18 | ).lazily 19 | 20 | // constructor using the original order from the spec 21 | private def make(error: String): FallbackError2 = FallbackError2(error) 22 | 23 | implicit val schema: Schema[FallbackError2] = struct( 24 | string.required[FallbackError2]("error", _.error), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/HealthRequest.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class HealthRequest(query: Option[String] = None) 11 | 12 | object HealthRequest extends ShapeTag.Companion[HealthRequest] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "HealthRequest") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(query: Option[String]): HealthRequest = HealthRequest(query) 19 | 20 | implicit val schema: Schema[HealthRequest] = struct( 21 | string.validated(smithy.api.Length(min = Some(0L), max = Some(5L))).optional[HealthRequest]("query", _.query).addHints(smithy.api.HttpQuery("query")), 22 | )(make).withId(id).addHints(hints) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/guides/auth/HealthCheckOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example.guides.auth 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class HealthCheckOutput(message: String) 11 | 12 | object HealthCheckOutput extends ShapeTag.Companion[HealthCheckOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example.guides.auth", "HealthCheckOutput") 14 | 15 | val hints: Hints = Hints( 16 | smithy.api.Output(), 17 | ).lazily 18 | 19 | // constructor using the original order from the spec 20 | private def make(message: String): HealthCheckOutput = HealthCheckOutput(message) 21 | 22 | implicit val schema: Schema[HealthCheckOutput] = struct( 23 | string.required[HealthCheckOutput]("message", _.message), 24 | )(make).withId(id).addHints(hints) 25 | } 26 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/RecursiveInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.recursive 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class RecursiveInput(hello: Option[smithy4s.example.RecursiveInput] = None) 11 | 12 | object RecursiveInput extends ShapeTag.Companion[RecursiveInput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "RecursiveInput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(hello: Option[smithy4s.example.RecursiveInput]): RecursiveInput = RecursiveInput(hello) 19 | 20 | implicit val schema: Schema[RecursiveInput] = recursive(struct( 21 | smithy4s.example.RecursiveInput.schema.optional[RecursiveInput]("hello", _.hello), 22 | )(make).withId(id).addHints(hints)) 23 | } 24 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/CityCoordinates.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.float 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class CityCoordinates(latitude: Float, longitude: Float) 11 | 12 | object CityCoordinates extends ShapeTag.Companion[CityCoordinates] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "CityCoordinates") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(latitude: Float, longitude: Float): CityCoordinates = CityCoordinates(latitude, longitude) 19 | 20 | implicit val schema: Schema[CityCoordinates] = struct( 21 | float.required[CityCoordinates]("latitude", _.latitude), 22 | float.required[CityCoordinates]("longitude", _.longitude), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetCityInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.optics.Lens 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class GetCityInput(cityId: CityId) 11 | 12 | object GetCityInput extends ShapeTag.Companion[GetCityInput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "GetCityInput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | object optics { 18 | val cityId: Lens[GetCityInput, CityId] = Lens[GetCityInput, CityId](_.cityId)(n => a => a.copy(cityId = n)) 19 | } 20 | 21 | // constructor using the original order from the spec 22 | private def make(cityId: CityId): GetCityInput = GetCityInput(cityId) 23 | 24 | implicit val schema: Schema[GetCityInput] = struct( 25 | CityId.schema.required[GetCityInput]("cityId", _.cityId), 26 | )(make).withId(id).addHints(hints) 27 | } 28 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/NotFoundError.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class NotFoundError(name: String) extends Smithy4sThrowable 12 | 13 | object NotFoundError extends ShapeTag.Companion[NotFoundError] { 14 | val id: ShapeId = ShapeId("smithy4s.example", "NotFoundError") 15 | 16 | val hints: Hints = Hints( 17 | smithy.api.Error.CLIENT.widen, 18 | smithy.api.HttpError(404), 19 | ).lazily 20 | 21 | // constructor using the original order from the spec 22 | private def make(name: String): NotFoundError = NotFoundError(name) 23 | 24 | implicit val schema: Schema[NotFoundError] = struct( 25 | string.required[NotFoundError]("name", _.name), 26 | )(make).withId(id).addHints(hints) 27 | } 28 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetCityOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class GetCityOutput(name: String, coordinates: CityCoordinates) 11 | 12 | object GetCityOutput extends ShapeTag.Companion[GetCityOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "GetCityOutput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(name: String, coordinates: CityCoordinates): GetCityOutput = GetCityOutput(name, coordinates) 19 | 20 | implicit val schema: Schema[GetCityOutput] = struct( 21 | string.required[GetCityOutput]("name", _.name), 22 | CityCoordinates.schema.required[GetCityOutput]("coordinates", _.coordinates), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/NoSuchResource.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class NoSuchResource(resourceType: String) extends Smithy4sThrowable 12 | 13 | object NoSuchResource extends ShapeTag.Companion[NoSuchResource] { 14 | val id: ShapeId = ShapeId("smithy4s.example", "NoSuchResource") 15 | 16 | val hints: Hints = Hints( 17 | smithy.api.Error.CLIENT.widen, 18 | ).lazily 19 | 20 | // constructor using the original order from the spec 21 | private def make(resourceType: String): NoSuchResource = NoSuchResource(resourceType) 22 | 23 | implicit val schema: Schema[NoSuchResource] = struct( 24 | string.required[NoSuchResource]("resourceType", _.resourceType), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/OptionalOutputOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class OptionalOutputOutput(body: Option[String] = None) 11 | 12 | object OptionalOutputOutput extends ShapeTag.Companion[OptionalOutputOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "OptionalOutputOutput") 14 | 15 | val hints: Hints = Hints( 16 | smithy.api.Output(), 17 | ).lazily 18 | 19 | // constructor using the original order from the spec 20 | private def make(body: Option[String]): OptionalOutputOutput = OptionalOutputOutput(body) 21 | 22 | implicit val schema: Schema[OptionalOutputOutput] = struct( 23 | string.optional[OptionalOutputOutput]("body", _.body).addHints(smithy.api.HttpPayload()), 24 | )(make).withId(id).addHints(hints) 25 | } 26 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/HealthResponse.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class HealthResponse(status: String) 11 | 12 | object HealthResponse extends ShapeTag.Companion[HealthResponse] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "HealthResponse") 14 | 15 | val hints: Hints = Hints( 16 | smithy4s.example.FreeForm(smithy4s.Document.obj("i" -> smithy4s.Document.fromDouble(1.0d), "a" -> smithy4s.Document.fromDouble(2.0d))), 17 | ).lazily 18 | 19 | // constructor using the original order from the spec 20 | private def make(status: String): HealthResponse = HealthResponse(status) 21 | 22 | implicit val schema: Schema[HealthResponse] = struct( 23 | string.required[HealthResponse]("status", _.status), 24 | )(make).withId(id).addHints(hints) 25 | } 26 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/Pizza.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class Pizza(name: String, base: PizzaBase, toppings: List[Ingredient]) 11 | 12 | object Pizza extends ShapeTag.Companion[Pizza] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "Pizza") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(name: String, base: PizzaBase, toppings: List[Ingredient]): Pizza = Pizza(name, base, toppings) 19 | 20 | implicit val schema: Schema[Pizza] = struct( 21 | string.required[Pizza]("name", _.name), 22 | PizzaBase.schema.required[Pizza]("base", _.base), 23 | Ingredients.underlyingSchema.required[Pizza]("toppings", _.toppings), 24 | )(make).withId(id).addHints(hints) 25 | } 26 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/instances/EqualsInstances.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude.instances 2 | 3 | import smithy4s.Blob 4 | import smithy4s.kinds.PolyFunction 5 | import smithy4s.schema.Primitive 6 | import zio.prelude.Equal 7 | import zio.prelude.coherent.AssociativeEqual.derive 8 | 9 | trait EqualsInstances { 10 | 11 | implicit val bigIntEquals: Equal[BigInt] = Equal.default 12 | implicit val bigDecimalEquals: Equal[BigDecimal] = Equal.default 13 | implicit val blobEquals: Equal[Blob] = Equal.default 14 | implicit val documentEquals: Equal[smithy4s.Document] = Equal.default 15 | implicit val shapeIdEquals: Equal[smithy4s.ShapeId] = Equal.default 16 | implicit val timestampEquals: Equal[smithy4s.Timestamp] = 17 | Equal[Long].contramap(_.epochSecond) 18 | implicit val uuidEquals: Equal[java.util.UUID] = Equal.default 19 | val primEqualPf: PolyFunction[Primitive, Equal] = Primitive.deriving[Equal] 20 | 21 | } 22 | 23 | object EqualsInstances extends EqualsInstances 24 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/instances/HashInstances.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | package instances 3 | 4 | import smithy4s.kinds.PolyFunction 5 | import smithy4s.schema.Primitive 6 | import smithy4s.{Blob, ShapeId, Timestamp} 7 | import zio.prelude.coherent.HashOrd.derive 8 | import zio.prelude.{Equal, Hash} 9 | 10 | import java.util.UUID 11 | 12 | private[instances] trait HashInstances { 13 | 14 | implicit val blobHash: Hash[Blob] = 15 | new Hash[Blob] { 16 | def hash(a: Blob): Int = a.hashCode() 17 | 18 | def checkEqual(l: Blob, r: Blob): Boolean = 19 | Equal[Blob].equal(l, r) 20 | } 21 | implicit val documentHash: Hash[smithy4s.Document] = 22 | Hash.default 23 | implicit val shapeIdHash: Hash[ShapeId] = Hash.default 24 | implicit val timeStampHash: Hash[Timestamp] = Hash.default 25 | implicit val uuidHash: Hash[UUID] = Hash.default 26 | val primHashPf: PolyFunction[Primitive, Hash] = Primitive.deriving[Hash] 27 | 28 | } 29 | 30 | object HashInstances extends HashInstances 31 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/ListCitiesOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class ListCitiesOutput(items: List[CitySummary], nextToken: Option[String] = None) 11 | 12 | object ListCitiesOutput extends ShapeTag.Companion[ListCitiesOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "ListCitiesOutput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(nextToken: Option[String], items: List[CitySummary]): ListCitiesOutput = ListCitiesOutput(items, nextToken) 19 | 20 | implicit val schema: Schema[ListCitiesOutput] = struct( 21 | string.optional[ListCitiesOutput]("nextToken", _.nextToken), 22 | CitySummaries.underlyingSchema.required[ListCitiesOutput]("items", _.items), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/ListCitiesInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.int 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class ListCitiesInput(nextToken: Option[String] = None, pageSize: Option[Int] = None) 12 | 13 | object ListCitiesInput extends ShapeTag.Companion[ListCitiesInput] { 14 | val id: ShapeId = ShapeId("smithy4s.example", "ListCitiesInput") 15 | 16 | val hints: Hints = Hints.empty 17 | 18 | // constructor using the original order from the spec 19 | private def make(nextToken: Option[String], pageSize: Option[Int]): ListCitiesInput = ListCitiesInput(nextToken, pageSize) 20 | 21 | implicit val schema: Schema[ListCitiesInput] = struct( 22 | string.optional[ListCitiesInput]("nextToken", _.nextToken), 23 | int.optional[ListCitiesInput]("pageSize", _.pageSize), 24 | )(make).withId(id).addHints(hints) 25 | } 26 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/instances/ZSchemaInstances.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude.instances 2 | 3 | import smithy4s.{Blob, Document, Timestamp} 4 | import smithy4s.kinds.PolyFunction 5 | import smithy4s.schema.Primitive 6 | import zio.Chunk 7 | import zio.schema.{Schema => ZSchema} 8 | import zio.schema.Schema.primitive 9 | 10 | trait ZSchemaInstances { 11 | 12 | implicit val zBlob: ZSchema[Blob] = ZSchema 13 | .chunk[Byte] 14 | .transform( 15 | chunk => Blob(chunk.toArray), 16 | blob => Chunk.fromArray(blob.toArray) 17 | ) 18 | implicit val zDocument: ZSchema[smithy4s.Document] = 19 | ZSchema[String].transform( 20 | Document.fromString, 21 | _.toString() 22 | ) 23 | implicit val zTimestamp: ZSchema[smithy4s.Timestamp] = 24 | ZSchema[Long].transform( 25 | Timestamp.fromEpochSecond, 26 | _.epochSecond 27 | ) 28 | val primSchemaPf: PolyFunction[Primitive, ZSchema] = 29 | Primitive.deriving[ZSchema] 30 | 31 | } 32 | 33 | object ZSchemaInstances extends ZSchemaInstances 34 | -------------------------------------------------------------------------------- /modules/prelude/src/test/scala/smithy4s/zio/prelude/testcases/FooBar.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | package testcases 3 | 4 | import smithy4s.schema.Schema 5 | import smithy4s.{Hints, ShapeId} 6 | 7 | sealed abstract class FooBar(val stringValue: String, val intValue: Int) 8 | extends smithy4s.Enumeration.Value { 9 | override type EnumType = FooBar 10 | 11 | override def enumeration: smithy4s.Enumeration[EnumType] = FooBar 12 | 13 | val name = stringValue 14 | val value = stringValue 15 | val hints = Hints.empty 16 | 17 | } 18 | 19 | object FooBar 20 | extends smithy4s.Enumeration[FooBar] 21 | with smithy4s.ShapeTag.Companion[FooBar] { 22 | case object Foo extends FooBar("foo", 0) 23 | 24 | case object Bar extends FooBar("neq", 1) 25 | 26 | override def id: ShapeId = ShapeId("smithy4s.example", "FooBar") 27 | 28 | override def hints: Hints = Hints.empty 29 | 30 | override def values: List[FooBar] = List(Foo, Bar) 31 | 32 | implicit val schema: Schema[FooBar] = 33 | Schema.stringEnumeration[FooBar](List(Foo, Bar)) 34 | } 35 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-typelevel-mergify using the 2 | # mergifyGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the mergify configuration 6 | # to meet your needs, then regenerate this file. 7 | 8 | pull_request_rules: 9 | - name: label scala-steward's PRs 10 | conditions: 11 | - or: 12 | - author=scala-steward 13 | - author=scala-steward-dev 14 | actions: 15 | label: 16 | add: 17 | - dependency-update 18 | remove: [] 19 | - name: merge scala-steward's PRs 20 | conditions: 21 | - or: 22 | - author=scala-steward 23 | - author=scala-steward-dev 24 | - status-success=Build and Test (ubuntu-latest, 2.13, temurin@17) 25 | - status-success=Build and Test (ubuntu-latest, 2.13, temurin@11) 26 | actions: 27 | merge: 28 | method: merge -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/ReservationInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class ReservationInput(name: String, town: Option[String] = None) 11 | 12 | object ReservationInput extends ShapeTag.Companion[ReservationInput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "ReservationInput") 14 | 15 | val hints: Hints = Hints( 16 | smithy.api.Input(), 17 | ).lazily 18 | 19 | // constructor using the original order from the spec 20 | private def make(name: String, town: Option[String]): ReservationInput = ReservationInput(name, town) 21 | 22 | implicit val schema: Schema[ReservationInput] = struct( 23 | string.required[ReservationInput]("name", _.name).addHints(smithy.api.HttpLabel()), 24 | string.optional[ReservationInput]("town", _.town).addHints(smithy.api.HttpQuery("town")), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/CitySummary.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class CitySummary(cityId: CityId, name: String) 11 | 12 | object CitySummary extends ShapeTag.Companion[CitySummary] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "CitySummary") 14 | 15 | val hints: Hints = Hints( 16 | smithy.api.References(List(smithy.api.Reference(resource = smithy.api.NonEmptyString("smithy4s.example#City"), ids = None, service = None, rel = None))), 17 | ).lazily 18 | 19 | // constructor using the original order from the spec 20 | private def make(cityId: CityId, name: String): CitySummary = CitySummary(cityId, name) 21 | 22 | implicit val schema: Schema[CitySummary] = struct( 23 | CityId.schema.required[CitySummary]("cityId", _.cityId), 24 | string.required[CitySummary]("name", _.name), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/prelude/src/test/scala/smithy4s/zio/prelude/testcases/IntFooBar.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | package testcases 3 | 4 | import smithy4s.schema.Schema 5 | import smithy4s.{Hints, ShapeId} 6 | 7 | sealed abstract class IntFooBar(val stringValue: String, val intValue: Int) 8 | extends smithy4s.Enumeration.Value { 9 | override type EnumType = IntFooBar 10 | 11 | override def enumeration: smithy4s.Enumeration[EnumType] = IntFooBar 12 | 13 | val name = stringValue 14 | val value = stringValue 15 | val hints = Hints() 16 | 17 | } 18 | 19 | object IntFooBar 20 | extends smithy4s.Enumeration[IntFooBar] 21 | with smithy4s.ShapeTag.Companion[IntFooBar] { 22 | case object Foo extends IntFooBar("foo", 0) 23 | 24 | case object Bar extends IntFooBar("neq", 1) 25 | 26 | override def id: ShapeId = ShapeId("smithy4s.example", "FooBar") 27 | 28 | override def values: List[IntFooBar] = List(Foo, Bar) 29 | 30 | implicit val schema: Schema[IntFooBar] = 31 | Schema.intEnumeration[IntFooBar](List(Foo, Bar)) 32 | 33 | override def hints: Hints = Hints.empty 34 | } 35 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/AddMenuItemRequest.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class AddMenuItemRequest(restaurant: String, menuItem: MenuItem) 11 | 12 | object AddMenuItemRequest extends ShapeTag.Companion[AddMenuItemRequest] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "AddMenuItemRequest") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(restaurant: String, menuItem: MenuItem): AddMenuItemRequest = AddMenuItemRequest(restaurant, menuItem) 19 | 20 | implicit val schema: Schema[AddMenuItemRequest] = struct( 21 | string.required[AddMenuItemRequest]("restaurant", _.restaurant).addHints(smithy.api.HttpLabel()), 22 | MenuItem.schema.required[AddMenuItemRequest]("menuItem", _.menuItem).addHints(smithy.api.HttpPayload()), 23 | )(make).withId(id).addHints(hints) 24 | } 25 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GenericClientError.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class GenericClientError(message: String) extends Smithy4sThrowable { 12 | override def getMessage(): String = message 13 | } 14 | 15 | object GenericClientError extends ShapeTag.Companion[GenericClientError] { 16 | val id: ShapeId = ShapeId("smithy4s.example", "GenericClientError") 17 | 18 | val hints: Hints = Hints( 19 | smithy.api.Error.CLIENT.widen, 20 | smithy.api.HttpError(418), 21 | ).lazily 22 | 23 | // constructor using the original order from the spec 24 | private def make(message: String): GenericClientError = GenericClientError(message) 25 | 26 | implicit val schema: Schema[GenericClientError] = struct( 27 | string.required[GenericClientError]("message", _.message), 28 | )(make).withId(id).addHints(hints) 29 | } 30 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GenericServerError.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class GenericServerError(message: String) extends Smithy4sThrowable { 12 | override def getMessage(): String = message 13 | } 14 | 15 | object GenericServerError extends ShapeTag.Companion[GenericServerError] { 16 | val id: ShapeId = ShapeId("smithy4s.example", "GenericServerError") 17 | 18 | val hints: Hints = Hints( 19 | smithy.api.Error.SERVER.widen, 20 | smithy.api.HttpError(502), 21 | ).lazily 22 | 23 | // constructor using the original order from the spec 24 | private def make(message: String): GenericServerError = GenericServerError(message) 25 | 26 | implicit val schema: Schema[GenericServerError] = struct( 27 | string.required[GenericServerError]("message", _.message), 28 | )(make).withId(id).addHints(hints) 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Smithy4s-Zio 3 | [![root Scala version support](https://index.scala-lang.org/yisraelu/smithy4s-zio/root/latest.svg)](https://index.scala-lang.org/yisraelu/smithy4s-zio/root)[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) 4 | 5 | - A few small libs based off the great [Smithy4s](https://disneystreaming.github.io/smithy4s/) to enable integration with ZIO ecosystem. 6 | 7 | #### Usage 8 | For usage information, check out the Documentation [here](https://yisraelu.github.io/smithy4s-zio/) 9 | 10 | #### Credits 11 | - This project is based completely off the [http4s](https://http4s.org/) integration in Smithy4s. 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/GetForecastOutput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.optics.Lens 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class GetForecastOutput(forecast: Option[ForecastResult] = None) 11 | 12 | object GetForecastOutput extends ShapeTag.Companion[GetForecastOutput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "GetForecastOutput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | object optics { 18 | val forecast: Lens[GetForecastOutput, Option[ForecastResult]] = Lens[GetForecastOutput, Option[ForecastResult]](_.forecast)(n => a => a.copy(forecast = n)) 19 | } 20 | 21 | // constructor using the original order from the spec 22 | private def make(forecast: Option[ForecastResult]): GetForecastOutput = GetForecastOutput(forecast) 23 | 24 | implicit val schema: Schema[GetForecastOutput] = struct( 25 | ForecastResult.schema.optional[GetForecastOutput]("forecast", _.forecast), 26 | )(make).withId(id).addHints(hints) 27 | } 28 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/guides/auth/NotAuthorizedError.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example.guides.auth 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class NotAuthorizedError(message: String) extends Smithy4sThrowable { 12 | override def getMessage(): String = message 13 | } 14 | 15 | object NotAuthorizedError extends ShapeTag.Companion[NotAuthorizedError] { 16 | val id: ShapeId = ShapeId("smithy4s.example.guides.auth", "NotAuthorizedError") 17 | 18 | val hints: Hints = Hints( 19 | smithy.api.Error.CLIENT.widen, 20 | smithy.api.HttpError(401), 21 | ).lazily 22 | 23 | // constructor using the original order from the spec 24 | private def make(message: String): NotAuthorizedError = NotAuthorizedError(message) 25 | 26 | implicit val schema: Schema[NotAuthorizedError] = struct( 27 | string.required[NotAuthorizedError]("message", _.message), 28 | )(make).withId(id).addHints(hints) 29 | } 30 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/PriceError.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.int 9 | import smithy4s.schema.Schema.string 10 | import smithy4s.schema.Schema.struct 11 | 12 | final case class PriceError(message: String, code: Int) extends Smithy4sThrowable { 13 | override def getMessage(): String = message 14 | } 15 | 16 | object PriceError extends ShapeTag.Companion[PriceError] { 17 | val id: ShapeId = ShapeId("smithy4s.example", "PriceError") 18 | 19 | val hints: Hints = Hints( 20 | smithy.api.Error.CLIENT.widen, 21 | ).lazily 22 | 23 | // constructor using the original order from the spec 24 | private def make(message: String, code: Int): PriceError = PriceError(message, code) 25 | 26 | implicit val schema: Schema[PriceError] = struct( 27 | string.required[PriceError]("message", _.message), 28 | int.required[PriceError]("code", _.code).addHints(smithy.api.HttpHeader("X-CODE")), 29 | )(make).withId(id).addHints(hints) 30 | } 31 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/eq/Smithy4sEqInstances.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests.internals.eq 2 | 3 | import smithy4s.{Blob, Document, Timestamp} 4 | import cats.Eq 5 | import cats.kernel.instances.StaticMethods 6 | import cats.syntax.all._ 7 | trait Smithy4sEqInstances { 8 | implicit def arrayEq[A: Eq]: Eq[Array[A]] = (x: Array[A], y: Array[A]) => 9 | x.zip(y).forall { case (a, b) => a === b } 10 | 11 | implicit def indexedSeqEq[A: Eq]: Eq[IndexedSeq[A]] = 12 | (xs: IndexedSeq[A], ys: IndexedSeq[A]) => 13 | if (xs eq ys) true 14 | else StaticMethods.iteratorEq(xs.iterator, ys.iterator) 15 | 16 | implicit val blobEq: Eq[Blob] = (x: Blob, y: Blob) => x.sameBytesAs(y) 17 | implicit val documentEq: Eq[Document] = Eq[String].contramap(_.show) 18 | implicit val timeStampEq: Eq[Timestamp] = Eq[Long].contramap(_.epochSecond) 19 | implicit val floatEq: Eq[Float] = (x: Float, y: Float) => 20 | x == y || (x.isNaN && y.isNaN) 21 | implicit val doubleEq: Eq[Double] = (x: Double, y: Double) => 22 | x == y || (x.isNaN && y.isNaN) 23 | 24 | } 25 | object Smithy4sEqInstances extends Smithy4sEqInstances 26 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/AddMenuItemResult.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Timestamp 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | import smithy4s.schema.Schema.timestamp 11 | 12 | final case class AddMenuItemResult(itemId: String, added: Timestamp) 13 | 14 | object AddMenuItemResult extends ShapeTag.Companion[AddMenuItemResult] { 15 | val id: ShapeId = ShapeId("smithy4s.example", "AddMenuItemResult") 16 | 17 | val hints: Hints = Hints.empty 18 | 19 | // constructor using the original order from the spec 20 | private def make(itemId: String, added: Timestamp): AddMenuItemResult = AddMenuItemResult(itemId, added) 21 | 22 | implicit val schema: Schema[AddMenuItemResult] = struct( 23 | string.required[AddMenuItemResult]("itemId", _.itemId).addHints(smithy.api.HttpPayload()), 24 | timestamp.required[AddMenuItemResult]("added", _.added).addHints(smithy.api.HttpHeader("X-ADDED-AT"), smithy.api.TimestampFormat.EPOCH_SECONDS.widen), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/HttpAppDriver.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests.internals 2 | 3 | import zio.* 4 | import zio.http.ZClient.Driver 5 | import zio.http.{ 6 | Body, 7 | ClientSSLConfig, 8 | Headers, 9 | Method, 10 | Request, 11 | Response, 12 | Routes, 13 | URL, 14 | Version, 15 | WebSocketApp 16 | } 17 | 18 | class HttpAppDriver(app: Routes[Any, Response]) 19 | extends Driver[Any, Scope, Throwable] { 20 | override def request( 21 | version: Version, 22 | method: Method, 23 | url: URL, 24 | headers: Headers, 25 | body: Body, 26 | sslConfig: Option[ClientSSLConfig], 27 | proxy: Option[http.Proxy] 28 | )(implicit trace: Trace): ZIO[Any, Throwable, Response] = { 29 | app(Request(version, method, url, headers, body, None)) 30 | .mapError(e => new RuntimeException(e.toString)) 31 | 32 | } 33 | 34 | override def socket[Env1 <: Any]( 35 | version: Version, 36 | url: URL, 37 | headers: Headers, 38 | app: WebSocketApp[Env1] 39 | )(implicit 40 | trace: Trace, 41 | ev: Scope =:= Scope 42 | ): ZIO[Env1 & Scope, Throwable, Response] = ZIO.never 43 | } 44 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/smithy4s/zio/http/RecursiveInputSpec.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http 2 | 3 | import smithy4s.example.{RecursiveInputService, RecursiveInput} 4 | import zio.Scope 5 | import zio.http.Client 6 | import zio.test.* 7 | 8 | // This is a non-regression test for https://github.com/disneystreaming/smithy4s/issues/181 9 | // CREDITS to https://github.com/disneystreaming/smithy4s/blob/series/0.18/modules/http4s/test/src/smithy4s/http4s/RecursiveInputSpec.scala 10 | object RecursiveInputSpec extends ZIOSpecDefault { 11 | 12 | override def spec: Spec[TestEnvironment & Scope, Any] = 13 | suite("recursive input test")( 14 | test("simpleRestJson works with recursive input operations") { 15 | val res = { 16 | for { 17 | client <- Client.default.build 18 | x <- SimpleRestJsonBuilder 19 | .apply(RecursiveInputService) 20 | .client(client.get) 21 | .lift 22 | } yield x 23 | }.provide(Scope.default) 24 | 25 | assertZIO( 26 | res 27 | .map(_.recursiveInputOperation(Some(RecursiveInput(None)))) 28 | .as(true) 29 | )(Assertion.isTrue) 30 | } 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/EchoInput.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class EchoInput(pathParam: String, body: EchoBody, queryParam: Option[String] = None) 11 | 12 | object EchoInput extends ShapeTag.Companion[EchoInput] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "EchoInput") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(pathParam: String, queryParam: Option[String], body: EchoBody): EchoInput = EchoInput(pathParam, body, queryParam) 19 | 20 | implicit val schema: Schema[EchoInput] = struct( 21 | string.validated(smithy.api.Length(min = Some(10L), max = None)).required[EchoInput]("pathParam", _.pathParam).addHints(smithy.api.HttpLabel()), 22 | string.validated(smithy.api.Length(min = Some(10L), max = None)).optional[EchoInput]("queryParam", _.queryParam).addHints(smithy.api.HttpQuery("queryParam")), 23 | EchoBody.schema.required[EchoInput]("body", _.body).addHints(smithy.api.HttpPayload()), 24 | )(make).withId(id).addHints(hints) 25 | } 26 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/todo/ServerExample.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.examples.todo 2 | 3 | import com.comcast.ip4s.* 4 | import example.todo.Todo 5 | import example.todo.TodoServiceGen.serviceInstance 6 | import smithy4s.zio.examples.todo.impl.{ 7 | InMemoryDatabase, 8 | PrimaryKeyGen, 9 | ToDoImpl 10 | } 11 | import smithy4s.zio.http.SimpleRestJsonBuilder 12 | import zio.http.{Routes, Server} 13 | import zio.{Scope, ZIO, ZIOAppArgs, ZIOAppDefault} 14 | 15 | object Main extends ZIOAppDefault { 16 | 17 | private val port: Port = Port.fromInt(8091).get 18 | 19 | val app: ZIO[Any, Throwable, Routes[Any, Nothing]] = { 20 | for { 21 | _ <- zio.Console.printLine(s"Starting server on http://localhost:$port") 22 | keyGen = PrimaryKeyGen.default() 23 | db <- InMemoryDatabase.make[Todo](keyGen) 24 | routes <- SimpleRestJsonBuilder 25 | .routes(ToDoImpl(db, "http://localhost/todos")) 26 | .lift 27 | app = routes.sandbox 28 | _ <- zio.Console.printLine(s"Starting server on http://localhost:$port") 29 | } yield app 30 | } 31 | 32 | override def run: ZIO[Any & ZIOAppArgs & Scope, Throwable, Nothing] = { 33 | app 34 | .flatMap(Server.serve(_).provide(Server.defaultWithPort(port.value))) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/RoundTripData.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class RoundTripData(label: String, header: Option[String] = None, query: Option[String] = None, body: Option[String] = None) 11 | 12 | object RoundTripData extends ShapeTag.Companion[RoundTripData] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "RoundTripData") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(label: String, header: Option[String], query: Option[String], body: Option[String]): RoundTripData = RoundTripData(label, header, query, body) 19 | 20 | implicit val schema: Schema[RoundTripData] = struct( 21 | string.required[RoundTripData]("label", _.label).addHints(smithy.api.HttpLabel()), 22 | string.optional[RoundTripData]("header", _.header).addHints(smithy.api.HttpHeader("HEADER")), 23 | string.optional[RoundTripData]("query", _.query).addHints(smithy.api.HttpQuery("query")), 24 | string.optional[RoundTripData]("body", _.body), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/TheEnum.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Enumeration 4 | import smithy4s.Hints 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.ShapeTag 8 | import smithy4s.schema.EnumTag 9 | import smithy4s.schema.Schema.enumeration 10 | 11 | sealed abstract class TheEnum(_value: String, _name: String, _intValue: Int, _hints: Hints) extends Enumeration.Value { 12 | override type EnumType = TheEnum 13 | override val value: String = _value 14 | override val name: String = _name 15 | override val intValue: Int = _intValue 16 | override val hints: Hints = _hints 17 | override def enumeration: Enumeration[EnumType] = TheEnum 18 | @inline final def widen: TheEnum = this 19 | } 20 | object TheEnum extends Enumeration[TheEnum] with ShapeTag.Companion[TheEnum] { 21 | val id: ShapeId = ShapeId("smithy4s.example", "TheEnum") 22 | 23 | val hints: Hints = Hints.empty 24 | 25 | case object V1 extends TheEnum("v1", "V1", 0, Hints.empty) 26 | case object V2 extends TheEnum("v2", "V2", 1, Hints.empty) 27 | 28 | val values: List[TheEnum] = List( 29 | V1, 30 | V2, 31 | ) 32 | val tag: EnumTag[TheEnum] = EnumTag.ClosedStringEnum 33 | implicit val schema: Schema[TheEnum] = enumeration(tag, values).withId(id).addHints(hints) 34 | } 35 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/middleware/ServerEndpointMiddleware.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.middleware 2 | 3 | import smithy4s.zio.http.internal.EffectOps 4 | import smithy4s.zio.http.{HttpRoutes, ServerEndpointMiddleware} 5 | import smithy4s.{Endpoint, Service} 6 | import zio.{Task, ZIO} 7 | 8 | object ServerEndpointMiddleware { 9 | 10 | trait Simple[F[_]] extends Endpoint.Middleware.Simple[HttpRoutes] 11 | 12 | def mapErrors( 13 | f: PartialFunction[Throwable, Throwable] 14 | ): ServerEndpointMiddleware = flatMapErrors(f(_).pure) 15 | 16 | def flatMapErrors( 17 | f: PartialFunction[Throwable, Task[Throwable]] 18 | ): ServerEndpointMiddleware = 19 | new ServerEndpointMiddleware { 20 | def prepare[Alg[_[_, _, _, _, _]]](service: Service[Alg])( 21 | endpoint: Endpoint[service.Operation, ?, ?, ?, ?, ?] 22 | ): HttpRoutes => HttpRoutes = routes => { 23 | /* val fx: PartialFunction[Throwable, Task[Nothing]] = { 24 | case e @ endpoint.Error(_, _) => ZIO.die(e) 25 | case scala.util.control.NonFatal(other) if f.isDefinedAt(other) => 26 | f(other).flatMap(ZIO.die(_)) 27 | }*/ 28 | 29 | f.andThen(_.flatMap(ZIO.die(_))) 30 | // todo pending error mapping added to Routes 31 | routes 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/instances/DebugInstances.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude.instances 2 | 3 | import smithy4s.{Blob, Document, ShapeId, Timestamp} 4 | import smithy4s.kinds.PolyFunction 5 | import smithy4s.schema.Primitive 6 | import zio.prelude.{Debug, DebugOps} 7 | import zio.prelude.Debug._ 8 | import zio.prelude.Debug.Repr 9 | 10 | import java.util.UUID 11 | 12 | trait DebugInstances { 13 | private final def ToString[A]: Debug[A] = 14 | Debug.make(a => Repr.String(a.toString)) 15 | 16 | implicit val sIdDebug: Debug[ShapeId] = ToString 17 | implicit val blobDebug: Debug[Blob] = (a: Blob) => 18 | Repr.String(a.toBase64String) 19 | implicit val documentDebug: Debug[Document] = ToString 20 | implicit val tsDebug: Debug[Timestamp] = ToString 21 | implicit val uuidDebug: Debug[UUID] = ToString 22 | implicit def setDebug[A: Debug]: Debug[Set[A]] = { 23 | Debug.make { set => 24 | Repr.VConstructor(List("scala"), "Set", set.toList.map(_.debug)) 25 | } 26 | } 27 | implicit def indexedSeqDebug[A: Debug]: Debug[IndexedSeq[A]] = { 28 | Debug.make { seq => 29 | Repr.VConstructor(List("scala"), "IndexedSeq", seq.toList.map(_.debug)) 30 | } 31 | } 32 | val primDebugPf: PolyFunction[Primitive, Debug] = 33 | Primitive.deriving[Debug] 34 | } 35 | 36 | object DebugInstances extends DebugInstances 37 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/PizzaBase.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Enumeration 4 | import smithy4s.Hints 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.ShapeTag 8 | import smithy4s.schema.EnumTag 9 | import smithy4s.schema.Schema.enumeration 10 | 11 | sealed abstract class PizzaBase(_value: String, _name: String, _intValue: Int, _hints: Hints) extends Enumeration.Value { 12 | override type EnumType = PizzaBase 13 | override val value: String = _value 14 | override val name: String = _name 15 | override val intValue: Int = _intValue 16 | override val hints: Hints = _hints 17 | override def enumeration: Enumeration[EnumType] = PizzaBase 18 | @inline final def widen: PizzaBase = this 19 | } 20 | object PizzaBase extends Enumeration[PizzaBase] with ShapeTag.Companion[PizzaBase] { 21 | val id: ShapeId = ShapeId("smithy4s.example", "PizzaBase") 22 | 23 | val hints: Hints = Hints.empty 24 | 25 | case object CREAM extends PizzaBase("C", "CREAM", 0, Hints.empty) 26 | case object TOMATO extends PizzaBase("T", "TOMATO", 1, Hints.empty) 27 | 28 | val values: List[PizzaBase] = List( 29 | CREAM, 30 | TOMATO, 31 | ) 32 | val tag: EnumTag[PizzaBase] = EnumTag.ClosedStringEnum 33 | implicit val schema: Schema[PizzaBase] = enumeration(tag, values).withId(id).addHints(hints) 34 | } 35 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/EnumResult.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Enumeration 4 | import smithy4s.Hints 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.ShapeTag 8 | import smithy4s.schema.EnumTag 9 | import smithy4s.schema.Schema.enumeration 10 | 11 | sealed abstract class EnumResult(_value: String, _name: String, _intValue: Int, _hints: Hints) extends Enumeration.Value { 12 | override type EnumType = EnumResult 13 | override val value: String = _value 14 | override val name: String = _name 15 | override val intValue: Int = _intValue 16 | override val hints: Hints = _hints 17 | override def enumeration: Enumeration[EnumType] = EnumResult 18 | @inline final def widen: EnumResult = this 19 | } 20 | object EnumResult extends Enumeration[EnumResult] with ShapeTag.Companion[EnumResult] { 21 | val id: ShapeId = ShapeId("smithy4s.example", "EnumResult") 22 | 23 | val hints: Hints = Hints.empty 24 | 25 | case object FIRST extends EnumResult("FIRST", "FIRST", 1, Hints.empty) 26 | case object SECOND extends EnumResult("SECOND", "SECOND", 2, Hints.empty) 27 | 28 | val values: List[EnumResult] = List( 29 | FIRST, 30 | SECOND, 31 | ) 32 | val tag: EnumTag[EnumResult] = EnumTag.ClosedIntEnum 33 | implicit val schema: Schema[EnumResult] = enumeration(tag, values).withId(id).addHints(hints) 34 | } 35 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/UnknownServerError.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.Smithy4sThrowable 8 | import smithy4s.schema.Schema.string 9 | import smithy4s.schema.Schema.struct 10 | 11 | final case class UnknownServerError(errorCode: UnknownServerErrorCode, description: Option[String] = None, stateHash: Option[String] = None) extends Smithy4sThrowable 12 | 13 | object UnknownServerError extends ShapeTag.Companion[UnknownServerError] { 14 | val id: ShapeId = ShapeId("smithy4s.example", "UnknownServerError") 15 | 16 | val hints: Hints = Hints( 17 | smithy.api.Error.SERVER.widen, 18 | smithy.api.HttpError(500), 19 | ).lazily 20 | 21 | // constructor using the original order from the spec 22 | private def make(errorCode: UnknownServerErrorCode, description: Option[String], stateHash: Option[String]): UnknownServerError = UnknownServerError(errorCode, description, stateHash) 23 | 24 | implicit val schema: Schema[UnknownServerError] = struct( 25 | UnknownServerErrorCode.schema.required[UnknownServerError]("errorCode", _.errorCode), 26 | string.optional[UnknownServerError]("description", _.description), 27 | string.optional[UnknownServerError]("stateHash", _.stateHash), 28 | )(make).withId(id).addHints(hints) 29 | } 30 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/UnknownServerErrorCode.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Enumeration 4 | import smithy4s.Hints 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.ShapeTag 8 | import smithy4s.schema.EnumTag 9 | import smithy4s.schema.Schema.enumeration 10 | 11 | sealed abstract class UnknownServerErrorCode(_value: String, _name: String, _intValue: Int, _hints: Hints) extends Enumeration.Value { 12 | override type EnumType = UnknownServerErrorCode 13 | override val value: String = _value 14 | override val name: String = _name 15 | override val intValue: Int = _intValue 16 | override val hints: Hints = _hints 17 | override def enumeration: Enumeration[EnumType] = UnknownServerErrorCode 18 | @inline final def widen: UnknownServerErrorCode = this 19 | } 20 | object UnknownServerErrorCode extends Enumeration[UnknownServerErrorCode] with ShapeTag.Companion[UnknownServerErrorCode] { 21 | val id: ShapeId = ShapeId("smithy4s.example", "UnknownServerErrorCode") 22 | 23 | val hints: Hints = Hints.empty 24 | 25 | case object ERROR_CODE extends UnknownServerErrorCode("server.error", "ERROR_CODE", 0, Hints.empty) 26 | 27 | val values: List[UnknownServerErrorCode] = List( 28 | ERROR_CODE, 29 | ) 30 | val tag: EnumTag[UnknownServerErrorCode] = EnumTag.ClosedStringEnum 31 | implicit val schema: Schema[UnknownServerErrorCode] = enumeration(tag, values).withId(id).addHints(hints) 32 | } 33 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/smithy4s/zio/http/ProtocolBuilderSpec.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http 2 | 3 | import alloy.SimpleRestJson 4 | import smithy4s.example.{WeatherGen, PizzaAdminServiceGen} 5 | import smithy4s.zio.http.internal.builders.client.ClientBuilder 6 | import zio.http.Client 7 | import zio.test.{Assertion, Spec, TestEnvironment, ZIOSpecDefault, assertZIO} 8 | import zio.{Scope, ZEnvironment, ZIO, ZLayer} 9 | 10 | object ProtocolBuilderSpec extends ZIOSpecDefault { 11 | 12 | private val client = Client.default 13 | 14 | override def spec: Spec[TestEnvironment with Scope, Any] = 15 | suite("protocol builder checks")( 16 | test( 17 | "SimpleProtocolBuilder (client) fails when the protocol is not present" 18 | ) { 19 | val result: ZIO[Any, Throwable, ZEnvironment[ 20 | ClientBuilder[WeatherGen, SimpleRestJson] 21 | ]] = ZLayer 22 | .fromFunction(SimpleRestJsonBuilder(WeatherGen).client(_)) 23 | .build 24 | .provide(client, Scope.default) 25 | 26 | assertZIO(result.map(_.get.make))(Assertion.isLeft) 27 | }, 28 | test( 29 | "SimpleProtocolBuilder (client) succeeds when the protocol is present" 30 | ) { 31 | val result = ZLayer 32 | .fromFunction(SimpleRestJsonBuilder(PizzaAdminServiceGen).client(_)) 33 | .build 34 | .provide(client, Scope.default) 35 | 36 | assertZIO(result.map(_.get.make))(Assertion.isRight) 37 | } 38 | ) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/TestConfig.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import smithy4s.zio.compliancetests.TestConfig.TestType 4 | import smithy4s.{Enumeration, Hints, Schema, ShapeId} 5 | import smithy.test.AppliesTo 6 | 7 | case class TestConfig( 8 | appliesTo: AppliesTo, 9 | testType: TestType 10 | ) { 11 | def show: String = s"(${appliesTo.name.toLowerCase}|$testType)" 12 | } 13 | 14 | object TestConfig { 15 | 16 | val clientReq = TestConfig(AppliesTo.CLIENT, TestType.Request) 17 | val clientRes = TestConfig(AppliesTo.CLIENT, TestType.Response) 18 | val serverReq = TestConfig(AppliesTo.SERVER, TestType.Request) 19 | val serverRes = TestConfig(AppliesTo.SERVER, TestType.Response) 20 | sealed abstract class TestType( 21 | val value: String, 22 | val intValue: Int 23 | ) extends Enumeration.Value { 24 | type EnumType = TestType 25 | def name: String = value 26 | def hints: Hints = Hints.empty 27 | def enumeration: Enumeration[EnumType] = TestType 28 | } 29 | object TestType extends smithy4s.Enumeration[TestType] { 30 | 31 | def id: ShapeId = ShapeId("smithy4s.compliancetests.internals", "TestType") 32 | def hints: Hints = Hints.empty 33 | def values: List[TestType] = List(Request, Response) 34 | case object Request extends TestType("request", 0) 35 | case object Response extends TestType("response", 0) 36 | 37 | val schema: Schema[TestType] = Schema.stringEnumeration(values) 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/HttpProtocolCompliance.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import smithy4s.Service 4 | import zio.Task 5 | import zio.interop.catz.asyncInstance 6 | 7 | /** 8 | * A construct allowing for running http protocol compliance tests against the implementation of a given protocol. 9 | * 10 | * Http protocol compliance tests are a bunch of Smithy traits provided by AWS to express expectations against 11 | * service definitions, making test specifications protocol-agnostic. 12 | * 13 | * See https://awslabs.github.io/smithy/2.0/additional-specs/http-protocol-compliance-tests.html?highlight=test 14 | */ 15 | object HttpProtocolCompliance { 16 | 17 | def clientTests[Alg[_[_, _, _, _, _]]]( 18 | reverseRouter: ReverseRouter, 19 | service: Service[Alg] 20 | ): List[ComplianceTest[Task]] = 21 | new internals.ClientHttpComplianceTestCase[Alg]( 22 | reverseRouter, 23 | service 24 | ).allClientTests() 25 | 26 | def serverTests[Alg[_[_, _, _, _, _]]]( 27 | router: Router, 28 | service: Service[Alg] 29 | ): List[ComplianceTest[Task]] = 30 | new internals.ServerHttpComplianceTestCase[Task, Alg]( 31 | router, 32 | service 33 | ).allServerTests() 34 | 35 | def clientAndServerTests[Alg[_[_, _, _, _, _]]]( 36 | router: Router with ReverseRouter, 37 | service: Service[Alg] 38 | ): List[ComplianceTest[Task]] = 39 | clientTests(router, service) ++ serverTests(router, service) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/SimpleRestJsonBuilder.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http 2 | 3 | import smithy4s.zio.http.protocol.SimpleProtocolBuilder 4 | 5 | object SimpleRestJsonBuilder extends SimpleRestJsonBuilder(1024, false, true) 6 | 7 | class SimpleRestJsonBuilder private ( 8 | simpleRestJsonCodecs: internal.SimpleRestJsonCodecs 9 | ) extends SimpleProtocolBuilder[alloy.SimpleRestJson]( 10 | simpleRestJsonCodecs 11 | ) { 12 | 13 | def this( 14 | maxArity: Int, 15 | explicitDefaultsEncoding: Boolean, 16 | hostPrefixInjection: Boolean 17 | ) = 18 | this( 19 | new internal.SimpleRestJsonCodecs( 20 | maxArity, 21 | explicitDefaultsEncoding, 22 | hostPrefixInjection 23 | ) 24 | ) 25 | 26 | def withMaxArity(maxArity: Int): SimpleRestJsonBuilder = 27 | new SimpleRestJsonBuilder( 28 | maxArity, 29 | simpleRestJsonCodecs.explicitDefaultsEncoding, 30 | simpleRestJsonCodecs.hostPrefixInjection 31 | ) 32 | 33 | def withExplicitDefaultsEncoding( 34 | explicitDefaultsEncoding: Boolean 35 | ): SimpleRestJsonBuilder = 36 | new SimpleRestJsonBuilder( 37 | simpleRestJsonCodecs.maxArity, 38 | explicitDefaultsEncoding, 39 | simpleRestJsonCodecs.hostPrefixInjection 40 | ) 41 | 42 | def disableHostPrefixInjection(): SimpleRestJsonBuilder = 43 | new SimpleRestJsonBuilder( 44 | simpleRestJsonCodecs.maxArity, 45 | simpleRestJsonCodecs.explicitDefaultsEncoding, 46 | false 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/todo/impl/InMemoryDatabase.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.examples.todo.impl 2 | 3 | import zio.{Ref, Task} 4 | 5 | class InMemoryDatabase[A]( 6 | ref: Ref[Map[String, A]], 7 | primaryKeyGen: PrimaryKeyGen 8 | ) extends Database[A] { 9 | override def insert(a: A): Task[Unit] = { 10 | for { 11 | id <- primaryKeyGen.generate() 12 | _ <- ref.update(m => m + (id -> a)) 13 | } yield () 14 | } 15 | 16 | override def get(id: String): Task[Option[A]] = { 17 | for { 18 | map <- ref.get 19 | } yield map.get(id) 20 | } 21 | 22 | override def update(id: String, a: A): Task[Unit] = { 23 | for { 24 | _ <- ref.update(m => m + (id -> a)) 25 | } yield () 26 | } 27 | 28 | override def delete(id: String): Task[Unit] = { 29 | for { 30 | _ <- ref.update(m => m - id) 31 | } yield () 32 | } 33 | 34 | override def list(): Task[List[A]] = { 35 | for { 36 | map <- ref.get 37 | } yield map.values.toList 38 | } 39 | 40 | override def deleteAll(): Task[Unit] = { 41 | for { 42 | _ <- ref.update(_ => Map.empty) 43 | } yield () 44 | } 45 | 46 | override def count(): Task[Int] = { 47 | for { 48 | map <- ref.get 49 | } yield map.size 50 | } 51 | } 52 | 53 | object InMemoryDatabase { 54 | def make[A](primaryKeyGen: PrimaryKeyGen): Task[InMemoryDatabase[A]] = { 55 | for { 56 | ref <- Ref.make(Map.empty[String, A]) 57 | } yield new InMemoryDatabase[A](ref, primaryKeyGen) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/Ingredient.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Enumeration 4 | import smithy4s.Hints 5 | import smithy4s.Schema 6 | import smithy4s.ShapeId 7 | import smithy4s.ShapeTag 8 | import smithy4s.schema.EnumTag 9 | import smithy4s.schema.Schema.enumeration 10 | 11 | sealed abstract class Ingredient(_value: String, _name: String, _intValue: Int, _hints: Hints) extends Enumeration.Value { 12 | override type EnumType = Ingredient 13 | override val value: String = _value 14 | override val name: String = _name 15 | override val intValue: Int = _intValue 16 | override val hints: Hints = _hints 17 | override def enumeration: Enumeration[EnumType] = Ingredient 18 | @inline final def widen: Ingredient = this 19 | } 20 | object Ingredient extends Enumeration[Ingredient] with ShapeTag.Companion[Ingredient] { 21 | val id: ShapeId = ShapeId("smithy4s.example", "Ingredient") 22 | 23 | val hints: Hints = Hints.empty 24 | 25 | case object MUSHROOM extends Ingredient("Mushroom", "MUSHROOM", 0, Hints.empty) 26 | case object CHEESE extends Ingredient("Cheese", "CHEESE", 1, Hints.empty) 27 | case object SALAD extends Ingredient("Salad", "SALAD", 2, Hints.empty) 28 | case object TOMATO extends Ingredient("Tomato", "TOMATO", 3, Hints.empty) 29 | 30 | val values: List[Ingredient] = List( 31 | MUSHROOM, 32 | CHEESE, 33 | SALAD, 34 | TOMATO, 35 | ) 36 | val tag: EnumTag[Ingredient] = EnumTag.ClosedStringEnum 37 | implicit val schema: Schema[Ingredient] = enumeration(tag, values).withId(id).addHints(hints) 38 | } 39 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/HeaderEndpointData.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.string 8 | import smithy4s.schema.Schema.struct 9 | 10 | final case class HeaderEndpointData(uppercaseHeader: Option[String] = None, capitalizedHeader: Option[String] = None, lowercaseHeader: Option[String] = None, mixedHeader: Option[String] = None) 11 | 12 | object HeaderEndpointData extends ShapeTag.Companion[HeaderEndpointData] { 13 | val id: ShapeId = ShapeId("smithy4s.example", "HeaderEndpointData") 14 | 15 | val hints: Hints = Hints.empty 16 | 17 | // constructor using the original order from the spec 18 | private def make(uppercaseHeader: Option[String], capitalizedHeader: Option[String], lowercaseHeader: Option[String], mixedHeader: Option[String]): HeaderEndpointData = HeaderEndpointData(uppercaseHeader, capitalizedHeader, lowercaseHeader, mixedHeader) 19 | 20 | implicit val schema: Schema[HeaderEndpointData] = struct( 21 | string.optional[HeaderEndpointData]("uppercaseHeader", _.uppercaseHeader).addHints(smithy.api.HttpHeader("X-UPPERCASE-HEADER")), 22 | string.optional[HeaderEndpointData]("capitalizedHeader", _.capitalizedHeader).addHints(smithy.api.HttpHeader("X-Capitalized-Header")), 23 | string.optional[HeaderEndpointData]("lowercaseHeader", _.lowercaseHeader).addHints(smithy.api.HttpHeader("x-lowercase-header")), 24 | string.optional[HeaderEndpointData]("mixedHeader", _.mixedHeader).addHints(smithy.api.HttpHeader("x-MiXeD-hEaDEr")), 25 | )(make).withId(id).addHints(hints) 26 | } 27 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/todo/ClientExample.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.examples.todo 2 | 3 | import example.todo.TodoServiceGen 4 | import smithy4s.zio.http.SimpleRestJsonBuilder 5 | import zio.http.{Client, URL} 6 | import zio.{Scope, ZIO, ZIOAppArgs, ZIOAppDefault} 7 | object ClientExample extends ZIOAppDefault { 8 | 9 | private val client = { 10 | for { 11 | url <- ZIO.fromEither(URL.decode("http://localhost:8091")) 12 | client <- ZIO.service[Client] 13 | clientService <- SimpleRestJsonBuilder(TodoServiceGen) 14 | .client(client) 15 | .url(url) 16 | .lift 17 | } yield clientService 18 | } 19 | 20 | private val program: ZIO[Scope & Client, Throwable, Unit] = 21 | for { 22 | client <- client 23 | _ <- client.healthCheck().debug 24 | todo <- client 25 | .createTodo( 26 | title = "My todo", 27 | order = Some(1), 28 | description = Some("My description") 29 | ) 30 | .debug 31 | _ <- client 32 | .getTodo( 33 | id = todo.id 34 | ) 35 | .debug 36 | _ <- client 37 | .updateTodo( 38 | id = todo.id, 39 | title = Some("My todo"), 40 | order = Some(1), 41 | description = Some("My description"), 42 | completed = Some(true) 43 | ) 44 | .debug 45 | _ <- client 46 | .deleteTodo( 47 | id = todo.id 48 | ) 49 | .debug 50 | _ <- client.deleteAll().debug 51 | } yield () 52 | 53 | override def run: ZIO[Any & ZIOAppArgs & Scope, Any, Any] = 54 | program.provide(Client.default, Scope.default) 55 | } 56 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio 2 | 3 | import smithy4s.Endpoint 4 | import smithy4s.http.PathParams 5 | import zio.{Scope, Task, ZIO} 6 | import zio.http.* 7 | 8 | package object http { 9 | type ResourcefulTask[Output] = ZIO[Scope, Throwable, Output] 10 | type HttpRoutes = Routes[Any, Throwable] 11 | type ClientEndpointMiddleware = Endpoint.Middleware[Client] 12 | type ServerEndpointMiddleware = Endpoint.Middleware[HttpRoutes] 13 | type SimpleHandler = Request => Task[Response] 14 | 15 | private val pathParamsKey: String = "x-smithy4s-path-params" 16 | 17 | private def serializePathParams(pathParams: PathParams): String = { 18 | pathParams.map { case (key, value) => s"$key=$value" }.mkString("&") 19 | } 20 | 21 | private def deserializePathParams(pathParamsString: String): PathParams = { 22 | pathParamsString 23 | .split("&") 24 | .filterNot(_.isEmpty) 25 | .map { param => 26 | { 27 | param.split("=", 2) match { 28 | case Array(key, value) => key -> value 29 | case Array(k) => (k, "") 30 | case _ => 31 | throw new Exception( 32 | s"Invalid path params string: $pathParamsString" 33 | ) 34 | } 35 | } 36 | } 37 | .toMap 38 | 39 | } 40 | def tagRequest(req: Request, pathParams: PathParams): Request = { 41 | val serializedPathParams = serializePathParams(pathParams) 42 | req.addHeader(Header.Custom(pathParamsKey, serializedPathParams)) 43 | } 44 | def lookupPathParams(req: Request): (Request, Option[PathParams]) = { 45 | val pathParamsString = req.headers.get(pathParamsKey) 46 | ( 47 | req.removeHeader(pathParamsKey), 48 | pathParamsString.map(deserializePathParams) 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/protocol/SimpleProtocolBuilder.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.protocol 2 | 3 | import smithy4s.kinds.FunctorAlgebra 4 | import smithy4s.zio.http.internal.builders.client.ClientBuilder 5 | import smithy4s.zio.http.internal.builders.server.RouterBuilder 6 | import smithy4s.{Endpoint, ShapeTag} 7 | import zio.Task 8 | import zio.http.* 9 | 10 | /** 11 | * Abstract construct helping the construction of routers and clients 12 | * for a given protocol. Upon constructing the routers/clients, it will 13 | * first check that they are indeed annotated with the protocol in question. 14 | */ 15 | abstract class SimpleProtocolBuilder[P]( 16 | simpleProtocolCodecs: SimpleProtocolCodecs 17 | )(implicit 18 | protocolTag: ShapeTag[P] 19 | ) { 20 | 21 | def apply[Alg[_[_, _, _, _, _]]]( 22 | service: smithy4s.Service[Alg] 23 | ): ServiceBuilder[Alg] = new ServiceBuilder(service) 24 | 25 | def routes[Alg[_[_, _, _, _, _]]]( 26 | impl: FunctorAlgebra[Alg, Task] 27 | )(implicit 28 | service: smithy4s.Service[Alg] 29 | ): RouterBuilder[Alg, P] = { 30 | new RouterBuilder[Alg, P]( 31 | protocolTag, 32 | simpleProtocolCodecs, 33 | service, 34 | impl, 35 | PartialFunction.empty, 36 | Endpoint.Middleware.noop 37 | ) 38 | } 39 | 40 | class ServiceBuilder[ 41 | Alg[_[_, _, _, _, _]] 42 | ] private[http] (val service: smithy4s.Service[Alg]) { 43 | self => 44 | 45 | def routes( 46 | impl: FunctorAlgebra[Alg, Task] 47 | )(implicit 48 | service: smithy4s.Service[Alg] 49 | ): RouterBuilder[Alg, P] = { 50 | new RouterBuilder[Alg, P]( 51 | protocolTag, 52 | simpleProtocolCodecs, 53 | service, 54 | impl, 55 | PartialFunction.empty, 56 | Endpoint.Middleware.noop 57 | ) 58 | } 59 | 60 | def client(client: Client) = 61 | new ClientBuilder[Alg, P](simpleProtocolCodecs, client, service) 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/ErrorResponseTest.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests.internals 2 | 3 | import cats.{ApplicativeThrow, Eq} 4 | import cats.syntax.all._ 5 | import smithy4s.Document 6 | import smithy4s.schema.{ErrorSchema, Schema} 7 | import smithy4s.zio.compliancetests.ComplianceTest.ComplianceResult 8 | import smithy4s.zio.compliancetests.internals.eq.EqSchemaVisitor 9 | 10 | private[compliancetests] final case class ErrorResponseTest[A, E]( 11 | schema: Schema[A], 12 | inject: A => E, 13 | dispatcher: E => Option[A], 14 | errorschema: ErrorSchema[E] 15 | ) { 16 | 17 | lazy val errorDecoder: Document.Decoder[A] = 18 | CanonicalSmithyDecoder.fromSchema(schema) 19 | implicit lazy val eq: Eq[A] = EqSchemaVisitor(schema) 20 | 21 | private def dispatchThrowable(t: Throwable): Option[A] = { 22 | errorschema.liftError(t).flatMap(dispatcher(_)) 23 | } 24 | 25 | def errorEq[F[_]: ApplicativeThrow] 26 | : (Document, Throwable) => F[ComplianceResult] = { 27 | 28 | (doc: Document, throwable: Throwable) => 29 | errorDecoder 30 | .decode(doc) 31 | .map(inject) 32 | .map { e => 33 | (dispatcher(e), dispatchThrowable(throwable)) match { 34 | case (Some(expected), Some(result)) => 35 | asserts.eql(result, expected) 36 | case _ => 37 | asserts.fail( 38 | s"Could not decode error response to known model: $throwable" 39 | ) 40 | } 41 | } 42 | .liftTo[F] 43 | } 44 | def kleisliFy[F[_]: ApplicativeThrow]: Document => F[Throwable] = { 45 | (doc: Document) => 46 | errorDecoder 47 | .decode(doc) 48 | .map(inject) 49 | .map(errorschema.unliftError) 50 | .liftTo[F] 51 | } 52 | 53 | } 54 | 55 | private[compliancetests] object ErrorResponseTest { 56 | def from[E, A]( 57 | errorAlt: smithy4s.schema.Alt[E, A], 58 | errorschema: smithy4s.schema.ErrorSchema[E] 59 | ): ErrorResponseTest[A, E] = 60 | ErrorResponseTest( 61 | errorAlt.schema, 62 | errorAlt.inject, 63 | errorAlt.project.lift, 64 | errorschema 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/Food.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.schema.Schema.bijection 8 | import smithy4s.schema.Schema.union 9 | 10 | sealed trait Food extends scala.Product with scala.Serializable { self => 11 | @inline final def widen: Food = this 12 | def $ordinal: Int 13 | 14 | object project { 15 | def pizza: Option[Pizza] = Food.PizzaCase.alt.project.lift(self).map(_.pizza) 16 | def salad: Option[Salad] = Food.SaladCase.alt.project.lift(self).map(_.salad) 17 | } 18 | 19 | def accept[A](visitor: Food.Visitor[A]): A = this match { 20 | case value: Food.PizzaCase => visitor.pizza(value.pizza) 21 | case value: Food.SaladCase => visitor.salad(value.salad) 22 | } 23 | } 24 | object Food extends ShapeTag.Companion[Food] { 25 | 26 | def pizza(pizza: Pizza): Food = PizzaCase(pizza) 27 | def salad(salad: Salad): Food = SaladCase(salad) 28 | 29 | val id: ShapeId = ShapeId("smithy4s.example", "Food") 30 | 31 | val hints: Hints = Hints.empty 32 | 33 | final case class PizzaCase(pizza: Pizza) extends Food { final def $ordinal: Int = 0 } 34 | final case class SaladCase(salad: Salad) extends Food { final def $ordinal: Int = 1 } 35 | 36 | object PizzaCase { 37 | val hints: Hints = Hints.empty 38 | val schema: Schema[Food.PizzaCase] = bijection(Pizza.schema.addHints(hints), Food.PizzaCase(_), _.pizza) 39 | val alt = schema.oneOf[Food]("pizza") 40 | } 41 | object SaladCase { 42 | val hints: Hints = Hints.empty 43 | val schema: Schema[Food.SaladCase] = bijection(Salad.schema.addHints(hints), Food.SaladCase(_), _.salad) 44 | val alt = schema.oneOf[Food]("salad") 45 | } 46 | 47 | trait Visitor[A] { 48 | def pizza(value: Pizza): A 49 | def salad(value: Salad): A 50 | } 51 | 52 | object Visitor { 53 | trait Default[A] extends Visitor[A] { 54 | def default: A 55 | def pizza(value: Pizza): A = default 56 | def salad(value: Salad): A = default 57 | } 58 | } 59 | 60 | implicit val schema: Schema[Food] = union( 61 | Food.PizzaCase.alt, 62 | Food.SaladCase.alt, 63 | ){ 64 | _.$ordinal 65 | }.withId(id).addHints(hints) 66 | } 67 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/internal/builders/client/ClientBuilder.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.internal.builders.client 2 | 3 | import smithy4s.client.UnaryClientCompiler 4 | import smithy4s.zio.http.internal.{ZHttpToSmithy4sClient, zioMonadThrowLike} 5 | import smithy4s.zio.http.protocol.SimpleProtocolCodecs 6 | import smithy4s.zio.http.{ClientEndpointMiddleware, ResourcefulTask} 7 | import smithy4s.{Endpoint, ShapeTag, UnsupportedProtocolError, checkProtocol} 8 | import zio.http.{Client, Response, URL} 9 | import zio.{IO, Scope, ZIO} 10 | 11 | class ClientBuilder[ 12 | Alg[_[_, _, _, _, _]], 13 | P 14 | ] private[http] ( 15 | simpleProtocolCodecs: SimpleProtocolCodecs, 16 | client: Client, 17 | val service: smithy4s.Service[Alg], 18 | url: URL = URL 19 | .decode("http://localhost:8080") 20 | .getOrElse(throw new Exception("Invalid URL")), 21 | middleware: ClientEndpointMiddleware = Endpoint.Middleware.noop[Client] 22 | )(implicit protocolTag: ShapeTag[P]) { 23 | 24 | def url(url: URL): ClientBuilder[Alg, P] = 25 | new ClientBuilder[Alg, P]( 26 | simpleProtocolCodecs, 27 | this.client, 28 | this.service, 29 | url, 30 | this.middleware 31 | ) 32 | 33 | def middleware( 34 | mid: ClientEndpointMiddleware 35 | ): ClientBuilder[Alg, P] = 36 | new ClientBuilder[Alg, P]( 37 | simpleProtocolCodecs, 38 | this.client, 39 | this.service, 40 | this.url, 41 | mid 42 | ) 43 | 44 | def make: Either[UnsupportedProtocolError, service.Impl[ResourcefulTask]] = { 45 | 46 | checkProtocol(service, protocolTag) 47 | // Making sure the router is evaluated lazily, so that all the compilation inside it 48 | // doesn't happen in case of a missing protocol 49 | .map { _ => 50 | service.impl { 51 | implicit val monadThrow = zioMonadThrowLike[Scope] 52 | UnaryClientCompiler( 53 | service, 54 | client, 55 | (client: Client) => ZHttpToSmithy4sClient(client), 56 | simpleProtocolCodecs.makeClientCodecs(url), 57 | middleware, 58 | (response: Response) => !response.status.isError 59 | ) 60 | } 61 | } 62 | } 63 | 64 | def lift: IO[UnsupportedProtocolError, service.Impl[ResourcefulTask]] = 65 | ZIO.fromEither(make) 66 | } 67 | -------------------------------------------------------------------------------- /modules/examples/src/main/scala/smithy4s/zio/examples/todo/impl/ToDoImpl.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.examples.todo.impl 2 | 3 | import example.todo.* 4 | import zio.{Task, ZIO} 5 | 6 | class ToDoImpl(database: Database[Todo], host: String) 7 | extends TodoService[Task] { 8 | 9 | override def healthCheck(): Task[Status] = 10 | ZIO.attempt(println("All good")).as(Status("OK")) 11 | 12 | override def createTodo( 13 | title: Title, 14 | order: Option[Order], 15 | description: Option[TodoDescription] 16 | ): Task[Todo] = 17 | for { 18 | id <- ZIO.attempt(java.util.UUID.randomUUID().toString) 19 | todo = Todo( 20 | id = id, 21 | title = title, 22 | order = order, 23 | description = description, 24 | url = Url(s"$host/todos/$id"), 25 | completed = false 26 | ) 27 | _ <- database.insert(todo) 28 | } yield todo 29 | 30 | override def getTodo(id: Id): Task[Todo] = { 31 | database 32 | .get(id.value) 33 | .flatMap(ZIO.fromOption(_)) 34 | .orElseFail(TodoNotFound(s"Todo with id ${id.value} not found")) 35 | } 36 | 37 | override def updateTodo( 38 | id: Id, 39 | title: Option[Title], 40 | order: Option[Order], 41 | description: Option[TodoDescription], 42 | completed: Option[Boolean] 43 | ): Task[Todo] = { 44 | for { 45 | todo <- database.get(id.value) 46 | updatedTodo = todo.get.copy( 47 | title = title.getOrElse(todo.get.title), 48 | order = order.orElse(todo.get.order), 49 | description = description.orElse(todo.get.description), 50 | completed = completed.getOrElse(todo.get.completed) 51 | ) 52 | _ <- database.update(id.value, updatedTodo) 53 | } yield updatedTodo 54 | } 55 | 56 | override def deleteTodo(id: Id): Task[Unit] = { 57 | for { 58 | _ <- database.delete(id.value) 59 | } yield () 60 | } 61 | 62 | override def deleteAll(): Task[Unit] = { 63 | for { 64 | _ <- database.deleteAll() 65 | } yield () 66 | } 67 | 68 | override def listTodos(): Task[ListTodosOutput] = { 69 | for { 70 | todos <- database.list() 71 | } yield ListTodosOutput(todos) 72 | } 73 | 74 | override def apiVersion(): Task[ApiVersionOutput] = { 75 | ZIO.succeed(ApiVersionOutput("1.0")) 76 | } 77 | } 78 | 79 | object ToDoImpl { 80 | def apply(database: Database[Todo], host: String): ToDoImpl = 81 | new ToDoImpl(database, host) 82 | } 83 | -------------------------------------------------------------------------------- /modules/transformers/src/main/scala/smithy4s/zio/transformers/SimpleRestJsonProtocolTransformer.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.transformers 2 | 3 | import alloy.SimpleRestJsonTrait 4 | import software.amazon.smithy.aws.traits.protocols.RestJson1Trait 5 | import software.amazon.smithy.build.{ProjectionTransformer, TransformContext} 6 | import software.amazon.smithy.model.Model 7 | import software.amazon.smithy.model.shapes.{Shape, ShapeId} 8 | import software.amazon.smithy.model.traits.Trait 9 | import software.amazon.smithy.protocoltests.traits.{ 10 | HttpRequestTestCase, 11 | HttpRequestTestsTrait, 12 | HttpResponseTestCase, 13 | HttpResponseTestsTrait 14 | } 15 | import java.util.function.BiFunction 16 | import scala.jdk.CollectionConverters.{CollectionHasAsScala, SeqHasAsJava} 17 | final class SimpleRestJsonProtocolTransformer extends ProjectionTransformer { 18 | override def getName: String = "ProtocolTransformer" 19 | 20 | def transform(ctx: TransformContext): Model = { 21 | val traitMapper: BiFunction[Shape, Trait, Trait] = 22 | (_: Shape, theTrait: Trait) => { 23 | theTrait match { 24 | case _: RestJson1Trait => new SimpleRestJsonTrait() 25 | case c: HttpRequestTestsTrait => 26 | new HttpRequestTestsTrait( 27 | c.getSourceLocation, 28 | c.getTestCases.asScala.toList.map { 29 | case req: HttpRequestTestCase => 30 | if ( 31 | req.getProtocol == ShapeId.from("aws.protocols#restJson1") 32 | ) 33 | req.toBuilder 34 | .protocol(ShapeId.from("alloy#simpleRestJson")) 35 | .build() 36 | else req 37 | }.asJava 38 | ) 39 | case c: HttpResponseTestsTrait => 40 | new HttpResponseTestsTrait( 41 | c.getSourceLocation, 42 | c.getTestCases.asScala.toList.map { 43 | case res: HttpResponseTestCase => 44 | if ( 45 | res.getProtocol == ShapeId.from("aws.protocols#restJson1") 46 | ) 47 | res.toBuilder 48 | .protocol(ShapeId.from("alloy#simpleRestJson")) 49 | .build() 50 | else res 51 | }.asJava 52 | ) 53 | case _ => theTrait 54 | } 55 | } 56 | ctx.getTransformer().mapTraits(ctx.getModel(), traitMapper) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done 60 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/CanonicalSmithyDecoder.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests.internals 2 | 3 | import smithy4s.Document._ 4 | import smithy4s.codecs.PayloadError 5 | import smithy4s.internals._ 6 | import smithy4s.schema._ 7 | import smithy4s.schema.Primitive._ 8 | import smithy4s.{Schema, _} 9 | 10 | object CanonicalSmithyDecoder { 11 | 12 | /** 13 | * Produces a document decoder that 14 | * 15 | * @param schema 16 | * @return 17 | */ 18 | def fromSchema[A]( 19 | schema: Schema[A] 20 | ): Document.Decoder[A] = { 21 | decoder.fromSchema( 22 | schema.transformHintsTransitively(_ => Hints.empty) 23 | ) 24 | } 25 | 26 | private object decoder extends CachedSchemaCompiler.Impl[Decoder] { 27 | 28 | protected type Aux[A] = smithy4s.internals.DocumentDecoder[A] 29 | 30 | def fromSchema[A]( 31 | schema: Schema[A], 32 | cache: Cache 33 | ): Decoder[A] = { 34 | val decodeFunction = 35 | schema.compile(new SmithyNodeDocumentDecoderSchemaVisitor(cache)) 36 | new Decoder[A] { 37 | def decode(a: Document): Either[PayloadError, A] = 38 | try { 39 | Right(decodeFunction(Nil, a)) 40 | } catch { 41 | case e: PayloadError => Left(e) 42 | } 43 | } 44 | } 45 | 46 | } 47 | private class SmithyNodeDocumentDecoderSchemaVisitor( 48 | override val cache: CompilationCache[DocumentDecoder] 49 | ) extends DocumentDecoderSchemaVisitor(cache) { 50 | self => 51 | override def primitive[P]( 52 | shapeId: ShapeId, 53 | hints: Hints, 54 | tag: Primitive[P] 55 | ): DocumentDecoder[P] = tag match { 56 | case PFloat => float 57 | case PDouble => double 58 | case PTimestamp => 59 | DocumentDecoder.instance("Timestamp", "Number") { 60 | case (_, DNumber(value)) => 61 | val epochSeconds = value.toLong 62 | Timestamp( 63 | epochSeconds, 64 | ((value - epochSeconds) * 1000000000).toInt 65 | ) 66 | } 67 | case PBlob => 68 | from("Base64 binary blob") { case DString(string) => 69 | Blob(string) 70 | } 71 | case _ => super.primitive(shapeId, hints, tag) 72 | } 73 | 74 | val double = from("Double") { 75 | case DNumber(bd) => 76 | bd.toDouble 77 | case DString(string) => 78 | string.toDouble 79 | } 80 | 81 | val float = from("Float") { 82 | case DNumber(bd) => 83 | bd.toFloat 84 | case DString(string) => 85 | string.toFloat 86 | } 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/DefaultSchemaVisitor.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests.internals 2 | 3 | import smithy4s.Document.DNull 4 | import smithy4s.schema._ 5 | import smithy4s.schema.Primitive._ 6 | import smithy4s.{Schema, _} 7 | import cats.Id 8 | import java.util.UUID 9 | 10 | private[compliancetests] object DefaultSchemaVisitor extends SchemaVisitor[Id] { 11 | self => 12 | 13 | override def primitive[P]( 14 | shapeId: ShapeId, 15 | hints: Hints, 16 | tag: Primitive[P] 17 | ): Id[P] = tag match { 18 | case PFloat => 0: Float 19 | case PBigDecimal => 0: BigDecimal 20 | case PBigInt => 0: BigInt 21 | case PBlob => Blob(Array.emptyByteArray) 22 | case PDocument => DNull 23 | case PByte => 0: Byte 24 | case PInt => 0 25 | case PShort => 0: Short 26 | case PString => "" 27 | case PLong => 0: Long 28 | case PDouble => 0: Double 29 | case PBoolean => true 30 | case PTimestamp => Timestamp(0L, 0) 31 | case PUUID => new UUID(0, 0) 32 | } 33 | 34 | override def collection[C[_], A]( 35 | shapeId: ShapeId, 36 | hints: Hints, 37 | tag: CollectionTag[C], 38 | member: Schema[A] 39 | ): Id[C[A]] = tag.empty 40 | 41 | override def map[K, V]( 42 | shapeId: ShapeId, 43 | hints: Hints, 44 | key: Schema[K], 45 | value: Schema[V] 46 | ): Id[Map[K, V]] = Map.empty 47 | 48 | override def enumeration[E]( 49 | shapeId: ShapeId, 50 | hints: Hints, 51 | tag: EnumTag[E], 52 | values: List[EnumValue[E]], 53 | total: E => EnumValue[E] 54 | ): Id[E] = values.head.value 55 | 56 | override def struct[S]( 57 | shapeId: ShapeId, 58 | hints: Hints, 59 | fields: Vector[Field[S, _]], 60 | make: IndexedSeq[Any] => S 61 | ): Id[S] = make(fields.map(_.schema.compile(self))) 62 | 63 | override def union[U]( 64 | shapeId: ShapeId, 65 | hints: Hints, 66 | alternatives: Vector[Alt[U, _]], 67 | dispatch: Alt.Dispatcher[U] 68 | ): Id[U] = { 69 | def processAlt[A](alt: Alt[U, A]) = alt.inject(apply(alt.schema)) 70 | processAlt(alternatives.head) 71 | } 72 | 73 | override def biject[A, B]( 74 | schema: Schema[A], 75 | bijection: Bijection[A, B] 76 | ): Id[B] = bijection(apply(schema)) 77 | 78 | override def refine[A, B]( 79 | schema: Schema[A], 80 | refinement: Refinement[A, B] 81 | ): Id[B] = refinement.unsafe(apply(schema)) 82 | 83 | override def lazily[A](suspend: Lazy[Schema[A]]): Id[A] = { 84 | suspend.map(apply).value 85 | } 86 | 87 | override def option[A](schema: Schema[A]): Id[Option[A]] = None 88 | 89 | } 90 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/ForecastResult.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Hints 4 | import smithy4s.Schema 5 | import smithy4s.ShapeId 6 | import smithy4s.ShapeTag 7 | import smithy4s.optics.Prism 8 | import smithy4s.schema.Schema.bijection 9 | import smithy4s.schema.Schema.union 10 | 11 | sealed trait ForecastResult extends scala.Product with scala.Serializable { self => 12 | @inline final def widen: ForecastResult = this 13 | def $ordinal: Int 14 | 15 | object project { 16 | def rain: Option[ChanceOfRain] = ForecastResult.RainCase.alt.project.lift(self).map(_.rain) 17 | def sun: Option[UVIndex] = ForecastResult.SunCase.alt.project.lift(self).map(_.sun) 18 | } 19 | 20 | def accept[A](visitor: ForecastResult.Visitor[A]): A = this match { 21 | case value: ForecastResult.RainCase => visitor.rain(value.rain) 22 | case value: ForecastResult.SunCase => visitor.sun(value.sun) 23 | } 24 | } 25 | object ForecastResult extends ShapeTag.Companion[ForecastResult] { 26 | 27 | def rain(rain: ChanceOfRain): ForecastResult = RainCase(rain) 28 | def sun(sun: UVIndex): ForecastResult = SunCase(sun) 29 | 30 | val id: ShapeId = ShapeId("smithy4s.example", "ForecastResult") 31 | 32 | val hints: Hints = Hints.empty 33 | 34 | object optics { 35 | val rain: Prism[ForecastResult, ChanceOfRain] = Prism.partial[ForecastResult, ChanceOfRain]{ case ForecastResult.RainCase(t) => t }(ForecastResult.RainCase.apply) 36 | val sun: Prism[ForecastResult, UVIndex] = Prism.partial[ForecastResult, UVIndex]{ case ForecastResult.SunCase(t) => t }(ForecastResult.SunCase.apply) 37 | } 38 | 39 | final case class RainCase(rain: ChanceOfRain) extends ForecastResult { final def $ordinal: Int = 0 } 40 | final case class SunCase(sun: UVIndex) extends ForecastResult { final def $ordinal: Int = 1 } 41 | 42 | object RainCase { 43 | val hints: Hints = Hints.empty 44 | val schema: Schema[ForecastResult.RainCase] = bijection(ChanceOfRain.schema.addHints(hints), ForecastResult.RainCase(_), _.rain) 45 | val alt = schema.oneOf[ForecastResult]("rain") 46 | } 47 | object SunCase { 48 | val hints: Hints = Hints.empty 49 | val schema: Schema[ForecastResult.SunCase] = bijection(UVIndex.schema.addHints(hints), ForecastResult.SunCase(_), _.sun) 50 | val alt = schema.oneOf[ForecastResult]("sun") 51 | } 52 | 53 | trait Visitor[A] { 54 | def rain(value: ChanceOfRain): A 55 | def sun(value: UVIndex): A 56 | } 57 | 58 | object Visitor { 59 | trait Default[A] extends Visitor[A] { 60 | def default: A 61 | def rain(value: ChanceOfRain): A = default 62 | def sun(value: UVIndex): A = default 63 | } 64 | } 65 | 66 | implicit val schema: Schema[ForecastResult] = union( 67 | ForecastResult.RainCase.alt, 68 | ForecastResult.SunCase.alt, 69 | ){ 70 | _.$ordinal 71 | }.withId(id).addHints(hints) 72 | } 73 | -------------------------------------------------------------------------------- /modules/examples/src/main/smithy/Todo.smithy: -------------------------------------------------------------------------------- 1 | $version: "2.0" 2 | 3 | namespace example.todo 4 | 5 | use alloy#simpleRestJson 6 | use smithy4s.meta#unwrap 7 | 8 | @simpleRestJson 9 | service TodoService { 10 | version: "1.0" 11 | operations: [CreateTodo, GetTodo, UpdateTodo, DeleteTodo,DeleteAll ListTodos,ApiVersion,HealthCheck] 12 | } 13 | 14 | @http(method: "GET", uri: "/version") 15 | operation ApiVersion { 16 | output: ApiVersionOutput 17 | } 18 | 19 | @http(method: "POST", uri: "/todo") 20 | operation CreateTodo { 21 | input: CreateTodoInput 22 | output: Todo 23 | 24 | } 25 | @idempotent 26 | @http(method: "DELETE", uri: "/todo") 27 | operation DeleteAll { 28 | } 29 | 30 | @http(method: "GET", uri: "/todo/{id}") 31 | @readonly 32 | operation GetTodo { 33 | input: GetTodoInput 34 | output: Todo 35 | errors: [TodoNotFound] 36 | } 37 | 38 | @http(method: "PATCH", uri: "/todo/{id}") 39 | @idempotent 40 | operation UpdateTodo { 41 | input: UpdateTodoInput 42 | output: Todo 43 | errors: [TodoNotFound] 44 | } 45 | 46 | @idempotent 47 | @http(method: "DELETE", uri: "/todo/{id}") 48 | operation DeleteTodo { 49 | input: DeleteTodoInput 50 | errors: [TodoNotFound] 51 | } 52 | 53 | @readonly 54 | @http(method: "GET", uri: "/todo") 55 | operation ListTodos { 56 | output: ListTodosOutput 57 | 58 | } 59 | 60 | @http(method: "GET", uri: "/healthcheck") 61 | operation HealthCheck { 62 | output: Status 63 | } 64 | 65 | structure Status { 66 | @required 67 | status: String 68 | } 69 | 70 | @documentation("outputs the git commit hash for the current app version") 71 | structure ApiVersionOutput { 72 | @required 73 | version: String 74 | 75 | } 76 | 77 | string Id 78 | 79 | string Title 80 | 81 | integer Order 82 | 83 | string TodoDescription 84 | 85 | string Url 86 | 87 | structure Todo { 88 | @required 89 | id: Id 90 | @required 91 | title: Title 92 | description: TodoDescription 93 | @required 94 | completed: Boolean 95 | order: Order, 96 | @required 97 | url: Url 98 | } 99 | 100 | structure CreateTodoInput { 101 | @required 102 | title: Title 103 | order:Order 104 | description: TodoDescription 105 | } 106 | 107 | structure GetTodoInput { 108 | @required 109 | @httpLabel 110 | id: Id 111 | } 112 | 113 | 114 | @error("client") 115 | @httpError(404) 116 | structure TodoNotFound { 117 | @required 118 | message: String 119 | } 120 | 121 | structure UpdateTodoInput { 122 | @required 123 | @httpLabel 124 | id: Id 125 | title: Title 126 | order: Order 127 | description: TodoDescription 128 | completed: Boolean 129 | } 130 | 131 | structure DeleteTodoInput { 132 | @httpLabel 133 | @required 134 | id: Id 135 | } 136 | 137 | 138 | list TodoList { 139 | member: Todo 140 | } 141 | 142 | 143 | structure ListTodosOutput { 144 | @required 145 | @httpPayload 146 | todos: TodoList 147 | } -------------------------------------------------------------------------------- /modules/test-scenarios/src/smithy/weather.smithy: -------------------------------------------------------------------------------- 1 | namespace smithy4s.example 2 | 3 | use smithy4s.meta#generateOptics 4 | 5 | /// Provides weather forecasts. 6 | @paginated(inputToken: "nextToken", outputToken: "nextToken", 7 | pageSize: "pageSize") 8 | service Weather { 9 | version: "2006-03-01", 10 | resources: [City], 11 | operations: [GetCurrentTime] 12 | } 13 | 14 | resource City { 15 | identifiers: { cityId: CityId }, 16 | read: GetCity, 17 | list: ListCities, 18 | resources: [Forecast], 19 | } 20 | 21 | resource Forecast { 22 | identifiers: { cityId: CityId }, 23 | read: GetForecast, 24 | } 25 | 26 | // "pattern" is a trait. 27 | @pattern("^[A-Za-z0-9 ]+$") 28 | string CityId 29 | 30 | @readonly 31 | operation GetCity { 32 | input: GetCityInput, 33 | output: GetCityOutput, 34 | errors: [NoSuchResource] 35 | } 36 | 37 | @generateOptics 38 | structure GetCityInput { 39 | // "cityId" provides the identifier for the resource and 40 | // has to be marked as required. 41 | @required 42 | cityId: CityId 43 | } 44 | 45 | structure GetCityOutput { 46 | // "required" is used on output to indicate if the service 47 | // will always provide a value for the member. 48 | @required 49 | name: String, 50 | 51 | @required 52 | coordinates: CityCoordinates, 53 | } 54 | 55 | // This structure is nested within GetCityOutput. 56 | structure CityCoordinates { 57 | @required 58 | latitude: Float, 59 | 60 | @required 61 | longitude: Float, 62 | } 63 | 64 | // "error" is a trait that is used to specialize 65 | // a structure as an error. 66 | @error("client") 67 | structure NoSuchResource { 68 | @required 69 | resourceType: String 70 | } 71 | 72 | // The paginated trait indicates that the operation may 73 | // return truncated results. 74 | @readonly 75 | @paginated(items: "items") 76 | operation ListCities { 77 | input: ListCitiesInput, 78 | output: ListCitiesOutput 79 | } 80 | 81 | structure ListCitiesInput { 82 | nextToken: String, 83 | pageSize: Integer 84 | } 85 | 86 | structure ListCitiesOutput { 87 | nextToken: String, 88 | 89 | @required 90 | items: CitySummaries, 91 | } 92 | 93 | // CitySummaries is a list of CitySummary structures. 94 | list CitySummaries { 95 | member: CitySummary 96 | } 97 | 98 | // CitySummary contains a reference to a City. 99 | @references([{resource: City}]) 100 | structure CitySummary { 101 | @required 102 | cityId: CityId, 103 | 104 | @required 105 | name: String, 106 | } 107 | 108 | @readonly 109 | operation GetCurrentTime { 110 | output: GetCurrentTimeOutput 111 | } 112 | 113 | structure GetCurrentTimeOutput { 114 | @required 115 | time: Timestamp 116 | } 117 | 118 | @readonly 119 | operation GetForecast { 120 | input: GetForecastInput, 121 | output: GetForecastOutput 122 | } 123 | 124 | // "cityId" provides the only identifier for the resource since 125 | // a Forecast doesn't have its own. 126 | structure GetForecastInput { 127 | @required 128 | cityId: CityId, 129 | } 130 | 131 | @generateOptics 132 | structure GetForecastOutput { 133 | forecast: ForecastResult 134 | } 135 | 136 | @generateOptics 137 | union ForecastResult { 138 | rain: ChanceOfRain, 139 | sun: UVIndex 140 | } 141 | 142 | float ChanceOfRain 143 | integer UVIndex 144 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/AllowRules.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import smithy.test.AppliesTo 4 | import smithy4s.ShapeId 5 | import smithy4s.schema.Schema 6 | import smithy4s.zio.compliancetests.TestConfig.TestType 7 | 8 | import java.util.regex.Pattern 9 | 10 | final case class AlloyBorrowedTests( 11 | simpleRestJsonBorrowedTests: Map[ShapeId, AllowRules] 12 | ) 13 | 14 | object AlloyBorrowedTests { 15 | implicit val schema: Schema[AlloyBorrowedTests] = { 16 | val borrowedTestsField = Schema 17 | .map(ShapeId.schema, AllowRules.schema) 18 | .required[AlloyBorrowedTests]( 19 | "alloySimpleRestJsonBorrowedTests", 20 | _.simpleRestJsonBorrowedTests 21 | ) 22 | Schema.struct(borrowedTestsField)(AlloyBorrowedTests(_)) 23 | } 24 | } 25 | 26 | final case class AllowRules( 27 | allowList: Vector[AllowRule], 28 | disallowList: Vector[AllowRule] 29 | ) { 30 | 31 | def shouldRun[F[_]](complianceTest: ComplianceTest[F]): ShouldRun = { 32 | if (disallowList.exists(_.matches(complianceTest))) ShouldRun.No 33 | else if (allowList.exists(_.matches(complianceTest))) ShouldRun.Yes 34 | else ShouldRun.NotSure 35 | } 36 | 37 | def filterRules(predicate: AllowRule => Boolean): AllowRules = { 38 | val filteredAllowList = allowList.filter(predicate) 39 | AllowRules(filteredAllowList, disallowList) 40 | } 41 | 42 | } 43 | 44 | object AllowRules { 45 | val empty = AllowRules(Vector.empty, Vector.empty) 46 | 47 | implicit val schema: Schema[AllowRules] = { 48 | val allowListField = Schema 49 | .vector(AllowRule.schema) 50 | .required[AllowRules]("allowList", _.allowList) 51 | 52 | val disallowListField = Schema 53 | .vector(AllowRule.schema) 54 | .required[AllowRules]("disallowList", _.disallowList) 55 | Schema.struct(allowListField, disallowListField)(AllowRules(_, _)) 56 | } 57 | } 58 | 59 | case class Glob(glob: String) { 60 | private lazy val pattern: Pattern = { 61 | val parts = glob 62 | .split("\\*", -1) 63 | .map { // Don't discard trailing empty string, if any. 64 | case "" => "" 65 | case str => Pattern.quote(str) 66 | } 67 | Pattern.compile(parts.mkString(".*")) 68 | } 69 | 70 | def matches(str: String): Boolean = pattern.matcher(str).matches() 71 | } 72 | 73 | object Glob { 74 | val schema = Schema.string.biject[Glob](Glob(_))(_.glob) 75 | } 76 | 77 | case class AllowRule( 78 | id: Glob, 79 | appliesTo: Option[AppliesTo], 80 | testType: Option[TestType] 81 | ) { 82 | def matches[F[_]](complianceTest: ComplianceTest[F]): Boolean = { 83 | id.matches(complianceTest.id) && 84 | appliesTo.forall(_ == complianceTest.config.appliesTo) && 85 | testType.forall(_ == complianceTest.config.testType) 86 | } 87 | } 88 | object AllowRule { 89 | 90 | val schema: Schema[AllowRule] = { 91 | val idField = Glob.schema.required[AllowRule]("id", _.id) 92 | val appliesToField = 93 | AppliesTo.schema.optional[AllowRule]("appliesTo", _.appliesTo) 94 | val testTypeField = 95 | TestType.schema.optional[AllowRule]("testType", _.testType) 96 | Schema.struct(idField, appliesToField, testTypeField) { 97 | case (id, appliesTo, testType) => 98 | AllowRule(id, appliesTo, testType) 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/SchemaVisitorZSchemaGen.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | 3 | import smithy4s.schema._ 4 | import smithy4s.zio.prelude.instances.ZSchemaInstances.primSchemaPf 5 | import smithy4s.{Schema, _} 6 | import _root_.zio.schema.{Schema => ZSchema} 7 | 8 | object SchemaVisitorZSchemaGen extends CachedSchemaCompiler.Impl[ZSchema] { 9 | 10 | protected type Aux[A] = ZSchema[A] 11 | 12 | def fromSchema[A]( 13 | schema: Schema[A], 14 | cache: Cache 15 | ): ZSchema[A] = { 16 | schema.compile(new SchemaVisitorZSchemaGen(cache)) 17 | } 18 | 19 | } 20 | final class SchemaVisitorZSchemaGen( 21 | val cache: CompilationCache[ZSchema] 22 | ) extends SchemaVisitor.Cached[ZSchema] { 23 | self => 24 | 25 | override def primitive[P]( 26 | shapeId: ShapeId, 27 | hints: Hints, 28 | tag: Primitive[P] 29 | ): ZSchema[P] = { 30 | primSchemaPf(tag) 31 | } 32 | 33 | override def collection[C[_], A]( 34 | shapeId: ShapeId, 35 | hints: Hints, 36 | tag: CollectionTag[C], 37 | member: Schema[A] 38 | ): ZSchema[C[A]] = { 39 | implicit val memberSchema: ZSchema[A] = member.compile(self) 40 | tag match { 41 | case CollectionTag.ListTag => ZSchema.list[A] 42 | case CollectionTag.SetTag => ZSchema.set[A] 43 | case CollectionTag.VectorTag => ZSchema.vector[A] 44 | case CollectionTag.IndexedSeqTag => 45 | ZSchema.vector[A].transform(_.toIndexedSeq, _.toVector) 46 | } 47 | } 48 | 49 | override def map[K, V]( 50 | shapeId: ShapeId, 51 | hints: Hints, 52 | key: Schema[K], 53 | value: Schema[V] 54 | ): ZSchema[Map[K, V]] = { 55 | implicit val keySchema: ZSchema[K] = key.compile(self) 56 | implicit val valueSchema: ZSchema[V] = value.compile(self) 57 | ZSchema.map[K, V] 58 | } 59 | 60 | override def enumeration[E]( 61 | shapeId: ShapeId, 62 | hints: Hints, 63 | tag: EnumTag[E], 64 | values: List[EnumValue[E]], 65 | total: E => EnumValue[E] 66 | ): ZSchema[E] = { 67 | 68 | ??? 69 | } 70 | 71 | override def struct[S]( 72 | shapeId: ShapeId, 73 | hints: Hints, 74 | fields: Vector[Field[S, ?]], 75 | make: IndexedSeq[Any] => S 76 | ): ZSchema[S] = ??? 77 | 78 | override def union[U]( 79 | shapeId: ShapeId, 80 | hints: Hints, 81 | alternatives: Vector[Alt[U, ?]], 82 | dispatch: Alt.Dispatcher[U] 83 | ): ZSchema[U] = ??? 84 | 85 | override def biject[A, B]( 86 | schema: Schema[A], 87 | bijection: Bijection[A, B] 88 | ): ZSchema[B] = { 89 | val memberSchema: ZSchema[A] = schema.compile(self) 90 | memberSchema.transform(bijection.to, bijection.from) 91 | } 92 | 93 | override def refine[A, B]( 94 | schema: Schema[A], 95 | refinement: Refinement[A, B] 96 | ): ZSchema[B] = { 97 | val memberSchema: ZSchema[A] = schema.compile(self) 98 | memberSchema.transformOrFail( 99 | refinement.apply, 100 | b => Right(refinement.from(b)) 101 | ) 102 | } 103 | 104 | override def lazily[A](suspend: Lazy[Schema[A]]): ZSchema[A] = { 105 | val zschema: Lazy[ZSchema[A]] = suspend.map(self(_)) 106 | ZSchema.defer(zschema.value) 107 | 108 | } 109 | 110 | override def option[A](schema: Schema[A]): ZSchema[Option[A]] = { 111 | implicit val memberSchema: ZSchema[A] = schema.compile(self) 112 | ZSchema.option[A] 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/internal/SimpleRestJsonCodecs.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.internal 2 | 3 | import smithy4s.Blob 4 | import smithy4s.client.UnaryClientCodecs 5 | import smithy4s.codecs.BlobEncoder 6 | import smithy4s.http.* 7 | import smithy4s.json.Json 8 | import smithy4s.schema.FieldFilter 9 | import smithy4s.server.UnaryServerCodecs 10 | import smithy4s.zio.http.ResourcefulTask 11 | import smithy4s.zio.http.protocol.SimpleProtocolCodecs 12 | import zio.{Scope, Task} 13 | import zio.http.{Request, Response, URL} 14 | 15 | // scalafmt: {maxColumn = 120} 16 | private[http] class SimpleRestJsonCodecs( 17 | val maxArity: Int, 18 | val explicitDefaultsEncoding: Boolean, 19 | val hostPrefixInjection: Boolean 20 | ) extends SimpleProtocolCodecs { 21 | private val hintMask = 22 | alloy.SimpleRestJson.protocol.hintMask 23 | 24 | private val fieldFilter = 25 | if (explicitDefaultsEncoding) FieldFilter.EncodeAll else FieldFilter.Default 26 | 27 | private val jsonCodecs = Json.payloadCodecs 28 | .withJsoniterCodecCompiler( 29 | Json.jsoniter 30 | .withHintMask(hintMask) 31 | .withMaxArity(maxArity) 32 | .withFieldFilter(fieldFilter) 33 | ) 34 | 35 | // val mediaType = HttpMediaType("application/json") 36 | private val payloadEncoders: BlobEncoder.Compiler = 37 | jsonCodecs.encoders 38 | 39 | private val payloadDecoders = 40 | jsonCodecs.decoders 41 | 42 | // Adding X-Amzn-Errortype as well to facilitate interop with Amazon-issued code-generators. 43 | private val errorHeaders = List( 44 | smithy4s.http.errorTypeHeader, 45 | smithy4s.http.amazonErrorTypeHeader 46 | ) 47 | 48 | def makeClientCodecs( 49 | url: URL 50 | ): UnaryClientCodecs.Make[ResourcefulTask, Request, Response] = { 51 | implicit val monadThrowLike = zioMonadThrowLike[Scope] 52 | val baseRequest = HttpRequest(HttpMethod.POST, toSmithy4sHttpUri(url, None), Map.empty, Blob.empty) 53 | HttpUnaryClientCodecs.builder 54 | .withBodyEncoders(payloadEncoders) 55 | .withSuccessBodyDecoders(payloadDecoders) 56 | .withErrorBodyDecoders(payloadDecoders) 57 | .withErrorDiscriminator(HttpDiscriminator.fromResponse(errorHeaders, _).pure) 58 | .withMetadataDecoders(Metadata.Decoder) 59 | .withMetadataEncoders( 60 | Metadata.Encoder.withFieldFilter(fieldFilter) 61 | ) 62 | .withBaseRequest(_ => baseRequest.pure) 63 | .withRequestMediaType("application/json") 64 | .withRequestTransformation(fromSmithy4sHttpRequest(_).pure) 65 | .withResponseTransformation(toSmithy4sHttpResponse) 66 | .withHostPrefixInjection(hostPrefixInjection) 67 | .build() 68 | 69 | } 70 | 71 | def makeServerCodecs: UnaryServerCodecs.Make[Task, Request, Response] = { 72 | implicit val monadThrowLike = zioMonadThrowLike[Any] 73 | val baseResponse = HttpResponse(200, Map.empty, Blob.empty) 74 | 75 | HttpUnaryServerCodecs.builder 76 | .withBodyDecoders(payloadDecoders) 77 | .withSuccessBodyEncoders(payloadEncoders) 78 | .withErrorBodyEncoders(payloadEncoders) 79 | .withErrorTypeHeaders(errorHeaders*) 80 | .withMetadataDecoders(Metadata.Decoder) 81 | .withMetadataEncoders(Metadata.Encoder) 82 | .withBaseResponse(_ => baseResponse.pure) 83 | .withResponseMediaType("application/json") 84 | .withWriteEmptyStructs(!_.isUnit) 85 | .withRequestTransformation[Request](toSmithy4sHttpRequest) 86 | .withResponseTransformation(fromSmithy4sHttpResponse(_).pure) 87 | .build() 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt.{Def, ModuleID, *} 2 | import smithy4s.codegen.Smithy4sCodegenPlugin.autoImport.smithy4sVersion 3 | 4 | object Dependencies { 5 | object Http4s { 6 | val http4sVersion = Def.setting("0.23.32") 7 | 8 | val circe: Def.Initialize[ModuleID] = 9 | Def.setting("org.http4s" %% "http4s-circe" % http4sVersion.value) 10 | 11 | val emberServer: Def.Initialize[ModuleID] = 12 | Def.setting("org.http4s" %% "http4s-ember-server" % http4sVersion.value) 13 | val emberClient: Def.Initialize[ModuleID] = 14 | Def.setting("org.http4s" %% "http4s-ember-client" % http4sVersion.value) 15 | val core: Def.Initialize[ModuleID] = 16 | Def.setting("org.http4s" %% "http4s-core" % http4sVersion.value) 17 | val dsl: Def.Initialize[ModuleID] = 18 | Def.setting("org.http4s" %% "http4s-dsl" % http4sVersion.value) 19 | } 20 | object Fs2Data { 21 | val xml: Def.Initialize[ModuleID] = 22 | Def.setting("org.gnieh" %% "fs2-data-xml" % "1.12.0") 23 | } 24 | object LiHaoyi { 25 | val sourcecode = "com.lihaoyi" %% "sourcecode" % "0.4.2" 26 | val pprint = "com.lihaoyi" %% "pprint" % "0.9.4" 27 | } 28 | 29 | object Circe { 30 | val version = "0.14.14" 31 | val core = "io.circe" %% "circe-core" % version 32 | val parser = "io.circe" %% "circe-parser" % version 33 | } 34 | 35 | object Smithy4s { 36 | val version = smithy4sVersion 37 | val complianceTests = Def.setting( 38 | "com.disneystreaming.smithy4s" %% "smithy4s-compliance-tests" % version.value % Test 39 | ) 40 | val core = Def.setting( 41 | "com.disneystreaming.smithy4s" %% "smithy4s-core" % version.value 42 | ) 43 | val `codegen-cli` = Def.setting( 44 | "com.disneystreaming.smithy4s" %% "smithy4s-codegen-cli" % version.value 45 | ) 46 | val json = Def.setting( 47 | "com.disneystreaming.smithy4s" %% "smithy4s-json" % version.value 48 | ) 49 | val http4s = Def.setting( 50 | "com.disneystreaming.smithy4s" %% "smithy4s-http4s" % version.value 51 | ) 52 | val dynamic = Def.setting( 53 | "com.disneystreaming.smithy4s" %% "smithy4s-dynamic" % version.value 54 | ) 55 | val tests = Def.setting( 56 | "com.disneystreaming.smithy4s" %% "smithy4s-tests" % version.value % Test 57 | ) 58 | } 59 | 60 | object Smithy { 61 | val smithyVersion = "1.61.0" 62 | val org = "software.amazon.smithy" 63 | val testTraits = org % "smithy-protocol-test-traits" % smithyVersion 64 | val model = org % "smithy-model" % smithyVersion 65 | val build = org % "smithy-build" % smithyVersion 66 | val awsTraits = org % "smithy-aws-traits" % smithyVersion 67 | val waiters = org % "smithy-waiters" % smithyVersion 68 | 69 | } 70 | 71 | val Alloy = new { 72 | val org = "com.disneystreaming.alloy" 73 | val alloyVersion = "0.3.33" 74 | val core = org % "alloy-core" % alloyVersion 75 | val openapi = org %% "alloy-openapi" % alloyVersion 76 | val `protocol-tests` = org % "alloy-protocol-tests" % alloyVersion 77 | } 78 | 79 | object ZIO { 80 | val zioVersion = "2.1.21" 81 | val core = "dev.zio" %% "zio" % zioVersion 82 | 83 | val http = "dev.zio" %% "zio-http" % "3.0.1" 84 | val prelude = "dev.zio" %% "zio-prelude" % "1.0.0-RC41" 85 | val schema = "dev.zio" %% "zio-schema" % "1.7.5" 86 | val catsInterop = "dev.zio" %% "zio-interop-cats" % "23.1.0.5" 87 | val test = "dev.zio" %% "zio-test" % zioVersion 88 | val testSbt = "dev.zio" %% "zio-test-sbt" % zioVersion % Test 89 | val testMagnolia = "dev.zio" %% "zio-test-magnolia" % zioVersion % Test 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/smithy4s/zio/http/compliance/ZIOHttpRestJsonProtocolTests.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.compliance 2 | 3 | import alloy.SimpleRestJson 4 | import smithy4s.dynamic.DynamicSchemaIndex 5 | import smithy4s.http.HttpMediaType 6 | import smithy4s.kinds.FunctorAlgebra 7 | import smithy4s.schema.Schema 8 | import smithy4s.zio.compliancetests 9 | import smithy4s.zio.compliancetests.internals.HttpAppDriver 10 | import smithy4s.zio.compliancetests.{ 11 | AllowRules, 12 | AlloyBorrowedTests, 13 | HttpRoutes, 14 | ProtocolComplianceSuite, 15 | ReverseRouter, 16 | Router, 17 | ShouldRun 18 | } 19 | import smithy4s.zio.http.{ResourcefulTask, SimpleRestJsonBuilder} 20 | import smithy4s.{Service, ShapeId} 21 | import zio.http.{Body, Response, Routes, URL, ZClient} 22 | import zio.{Scope, Task, ZIO} 23 | 24 | import java.nio.file.Path 25 | 26 | object ZIOHttpRestJsonProtocolTests extends ProtocolComplianceSuite { 27 | 28 | override def allRules( 29 | dsi: DynamicSchemaIndex 30 | ): Task[compliancetests.ComplianceTest[Task] => compliancetests.ShouldRun] = { 31 | // Decoding borrowed tests 32 | ZIO 33 | .fromEither( 34 | smithy4s.Document 35 | .DObject(dsi.metadata) 36 | .decode[AlloyBorrowedTests] 37 | ) 38 | .map { borrowedTests => 39 | borrowedTests.simpleRestJsonBorrowedTests 40 | .getOrElse(ShapeId("aws.protocols", "restJson1"), AllowRules.empty) 41 | } 42 | .map { decodedRules => (c: compliancetests.ComplianceTest[Task]) => 43 | if (c.show.contains("alloy")) ShouldRun.Yes 44 | else decodedRules.shouldRun(c) 45 | } 46 | 47 | } 48 | 49 | override def allTests( 50 | dsi: DynamicSchemaIndex 51 | ): List[compliancetests.ComplianceTest[Task]] = 52 | genClientTests( 53 | SimpleRestJsonIntegration, 54 | simpleRestJsonSpec, 55 | pizzaSpec 56 | )(dsi) 57 | private val modelDump: Task[Path] = fileFromEnv("MODEL_DUMP") 58 | 59 | override def dynamicSchemaIndexLoader: Task[DynamicSchemaIndex] = { 60 | for { 61 | p <- modelDump 62 | dsi <- ZIO 63 | .readFile(p) 64 | .map(_.getBytes) 65 | .map( 66 | decodeDocument( 67 | _, 68 | smithy4s.json.Json.payloadCodecs 69 | .configureJsoniterCodecCompiler(_.withMaxArity(Int.MaxValue)) 70 | .decoders 71 | ) 72 | ) 73 | .flatMap(e => ZIO.fromEither(loadDynamic(e))) 74 | } yield dsi 75 | } 76 | 77 | private val simpleRestJsonSpec = 78 | ShapeId("aws.protocoltests.restjson", "RestJson") 79 | 80 | private val pizzaSpec = ShapeId("alloy.test", "PizzaAdminService") 81 | object SimpleRestJsonIntegration extends Router with ReverseRouter { 82 | 83 | type Protocol = SimpleRestJson 84 | val protocolTag = alloy.SimpleRestJson 85 | 86 | override def routes[Alg[_[_, _, _, _, _]]](impl: FunctorAlgebra[Alg, Task])( 87 | implicit service: Service[Alg] 88 | ): Task[HttpRoutes] = 89 | SimpleRestJsonBuilder(service).routes(impl).lift 90 | 91 | def expectedResponseType(schema: Schema[?]): HttpMediaType = HttpMediaType( 92 | "application/json" 93 | ) 94 | override def reverseRoutes[Alg[_[_, _, _, _, _]]]( 95 | routes: Routes[Any, Response], 96 | testHost: Option[String] 97 | )(implicit 98 | service: Service[Alg] 99 | ): Task[FunctorAlgebra[Alg, ResourcefulTask]] = { 100 | val driver: HttpAppDriver = new HttpAppDriver(routes) 101 | val client: ZClient[Any, Scope, Body, Throwable, Response] = 102 | ZClient 103 | .fromDriver(driver) 104 | 105 | val baseUri = URL.decode("http://localhost").toOption.get 106 | val suppliedHost = 107 | testHost.flatMap(host => URL.decode(s"http://$host").toOption) 108 | SimpleRestJsonBuilder(service) 109 | .client(client) 110 | .url(suppliedHost.getOrElse(baseUri)) 111 | .lift 112 | } 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /modules/http/src/main/scala/smithy4s/zio/http/internal/builders/server/RouterBuilder.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http.internal.builders.server 2 | 3 | import smithy4s.http.HttpUnaryServerRouter 4 | import smithy4s.kinds.FunctorAlgebra 5 | import smithy4s.zio.http.internal.{ 6 | tagRequest, 7 | toSmithy4sHttpMethod, 8 | toSmithy4sHttpUri, 9 | zioMonadThrowLike 10 | } 11 | import smithy4s.zio.http.middleware.ServerEndpointMiddleware 12 | import smithy4s.zio.http.protocol.SimpleProtocolCodecs 13 | import smithy4s.zio.http.{HttpRoutes, ServerEndpointMiddleware, SimpleHandler} 14 | import smithy4s.{ 15 | Bijection, 16 | Endpoint, 17 | ShapeTag, 18 | UnsupportedProtocolError, 19 | checkProtocol 20 | } 21 | import zio.http.* 22 | import zio.{IO, Task, ZIO} 23 | 24 | class RouterBuilder[ 25 | Alg[_[_, _, _, _, _]], 26 | P 27 | ] private[http] ( 28 | protocolTag: ShapeTag[P], 29 | simpleProtocolCodecs: SimpleProtocolCodecs, 30 | service: smithy4s.Service[Alg], 31 | impl: FunctorAlgebra[Alg, Task], 32 | fe: PartialFunction[Throwable, Task[Throwable]], 33 | middleware: ServerEndpointMiddleware = Endpoint.Middleware.noop[HttpRoutes] 34 | ) { 35 | 36 | def mapErrors( 37 | fe: PartialFunction[Throwable, Throwable] 38 | ): RouterBuilder[Alg, P] = 39 | new RouterBuilder( 40 | protocolTag, 41 | simpleProtocolCodecs, 42 | service, 43 | impl, 44 | fe andThen (e => ZIO.succeed(e)), 45 | middleware 46 | ) 47 | 48 | def flatMapErrors( 49 | fe: PartialFunction[Throwable, Task[Throwable]] 50 | ): RouterBuilder[Alg, P] = 51 | new RouterBuilder( 52 | protocolTag, 53 | simpleProtocolCodecs, 54 | service, 55 | impl, 56 | fe, 57 | middleware 58 | ) 59 | 60 | def middleware( 61 | mid: ServerEndpointMiddleware 62 | ): RouterBuilder[Alg, P] = 63 | new RouterBuilder[Alg, P]( 64 | protocolTag, 65 | simpleProtocolCodecs, 66 | service, 67 | impl, 68 | fe, 69 | mid 70 | ) 71 | 72 | def make: Either[UnsupportedProtocolError, HttpRoutes] = 73 | checkProtocol(service, protocolTag) 74 | // Making sure the router is evaluated lazily, so that all the compilation inside it 75 | // doesn't happen in case of a missing protocol 76 | .map { _ => 77 | implicit val monadThrow = zioMonadThrowLike[Any] 78 | val errorHandler: ServerEndpointMiddleware = 79 | ServerEndpointMiddleware.flatMapErrors(fe) 80 | val finalMiddleware: Endpoint.Middleware[HttpRoutes] = 81 | errorHandler.andThen(middleware).andThen(errorHandler) 82 | 83 | val router: Request => Option[Task[Response]] = 84 | HttpUnaryServerRouter(service)( 85 | impl, 86 | simpleProtocolCodecs.makeServerCodecs, 87 | finalMiddleware.biject[SimpleHandler](bijection.to)(bijection.from), 88 | getMethod = 89 | (request: Request) => toSmithy4sHttpMethod(request.method), 90 | getUri = (request: Request) => toSmithy4sHttpUri(request.url, None), 91 | addDecodedPathParams = 92 | (request: Request, pathParams) => tagRequest(request, pathParams) 93 | ) 94 | 95 | val ir: Request => Task[Response] = 96 | router(_).getOrElse(ZIO.succeed(Response.status(Status.NotFound))) 97 | bijection.from(ir) 98 | } 99 | 100 | def lift: IO[UnsupportedProtocolError, HttpRoutes] = ZIO.fromEither(make) 101 | 102 | val bijection: Bijection[HttpRoutes, SimpleHandler] = 103 | new Bijection[HttpRoutes, SimpleHandler] { 104 | override def to(httpRoutes: HttpRoutes): SimpleHandler = { 105 | val absolved = httpRoutes.handleError(throw _) 106 | ( 107 | (req: Request) => absolved.runZIO(req) 108 | ).asInstanceOf[SimpleHandler] 109 | } 110 | 111 | override def from(b: SimpleHandler): HttpRoutes = { 112 | val handler: Handler[Any, Throwable, (Path, Request), Response] = 113 | Handler.fromFunctionZIO[(Path, Request)](requestAndPath => 114 | b(requestAndPath._2).catchAllDefect(e => ZIO.die(e)) 115 | ) 116 | val singleRoute = Route.route(RoutePattern.any)(handler) 117 | Routes(singleRoute) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /modules/tests/src/test/scala/smithy4s/zio/http/ServiceBuilderZIOHttpSpec.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.http 2 | 3 | import smithy4s.Service 4 | import smithy4s.kinds.PolyFunction5 5 | import smithy4s.example.{ 6 | PizzaAdminService, 7 | PizzaAdminServiceGen, 8 | HealthResponse, 9 | UnknownServerError, 10 | UnknownServerErrorCode 11 | } 12 | import smithy4s.example.guides.auth.{ 13 | HelloWorldAuthService, 14 | HelloWorldAuthServiceGen, 15 | World, 16 | HealthCheckOutput 17 | } 18 | import zio.http.Request 19 | import zio.test.Assertion.* 20 | import zio.test.{Assertion, Spec, TestEnvironment, ZIOSpecDefault, assertZIO} 21 | import zio.{Scope, Task, ZIO} 22 | 23 | object ServiceBuilderZIOHttpSpec extends ZIOSpecDefault { 24 | 25 | override def spec: Spec[TestEnvironment with Scope, Any] = 26 | suite("tests service oriented customizations")( 27 | test("Capable of altering the URI path of an endpoint") { 28 | val serviceImpl: HelloWorldAuthService[Task] = 29 | new HelloWorldAuthService[Task] { 30 | override def sayWorld(): Task[World] = ZIO.succeed(World("hello")) 31 | 32 | override def healthCheck(): Task[HealthCheckOutput] = ??? 33 | } 34 | 35 | val builder = Service.Builder.fromService(HelloWorldAuthService) 36 | 37 | val mapper = new PolyFunction5[ 38 | HelloWorldAuthServiceGen.Endpoint, 39 | HelloWorldAuthServiceGen.Endpoint 40 | ] { 41 | def apply[I, E, O, SI, SO]( 42 | endpoint: HelloWorldAuthServiceGen.Endpoint[I, E, O, SI, SO] 43 | ): HelloWorldAuthServiceGen.Endpoint[I, E, O, SI, SO] = { 44 | if (endpoint.name == "SayWorld") { 45 | endpoint.mapSchema( 46 | _.withHints( 47 | smithy.api.Http( 48 | method = smithy.api.NonEmptyString("GET"), 49 | uri = smithy.api.NonEmptyString("/yeap"), 50 | code = 200 51 | ), 52 | smithy.api.Readonly() 53 | ) 54 | ) 55 | } else { 56 | endpoint 57 | } 58 | } 59 | } 60 | val modifiedService = builder 61 | .mapEndpointEach(mapper) 62 | .build 63 | 64 | assertZIO( 65 | SimpleRestJsonBuilder(modifiedService) 66 | .routes(serviceImpl) 67 | .lift 68 | .map { routes => 69 | routes.sandbox.runZIO(Request.get("/yeap")).map { response => 70 | response.status.code == 200 71 | } 72 | } 73 | )(Assertion.anything) 74 | }, 75 | test( 76 | "when an errorschema is removed and the service raises an error, it behaves in the same way as any other throwable" 77 | ) { 78 | val serviceImpl: PizzaAdminService[Task] = 79 | new PizzaAdminService.Default[Task]( 80 | ZIO.fail(new NotImplementedError("This IO is not implemented")) 81 | ) { 82 | override def health(query: Option[String]): Task[HealthResponse] = 83 | ZIO.fail( 84 | UnknownServerError( 85 | UnknownServerErrorCode.ERROR_CODE 86 | ) 87 | ) 88 | 89 | } 90 | 91 | val servicebuilder = Service.Builder.fromService(PizzaAdminService) 92 | val mapper = new PolyFunction5[ 93 | PizzaAdminServiceGen.Endpoint, 94 | PizzaAdminServiceGen.Endpoint 95 | ] { 96 | def apply[I, E, O, SI, SO]( 97 | endpoint: PizzaAdminServiceGen.Endpoint[I, E, O, SI, SO] 98 | ): PizzaAdminServiceGen.Endpoint[I, E, O, SI, SO] = 99 | endpoint.mapSchema(_.withoutError) 100 | } 101 | 102 | val modifiedService = servicebuilder 103 | .mapEndpointEach(mapper) 104 | .build 105 | 106 | assertZIO( 107 | SimpleRestJsonBuilder(modifiedService) 108 | .routes(serviceImpl) 109 | .lift 110 | .flatMap { routes => 111 | routes 112 | .handleErrorCause(ca => throw ca.squash) 113 | .runZIO(Request.get("/health")) 114 | 115 | } 116 | .exit 117 | )( 118 | dies( 119 | equalTo( 120 | UnknownServerError( 121 | UnknownServerErrorCode.ERROR_CODE 122 | ) 123 | ) 124 | ) 125 | ) 126 | } 127 | ) 128 | } 129 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/SchemaVisitorDebug.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | 3 | import smithy4s.capability.EncoderK 4 | import smithy4s.schema.Alt.Precompiler 5 | import smithy4s.schema._ 6 | import smithy4s.zio.prelude.instances.all._ 7 | import smithy4s.{Schema, _} 8 | import _root_.zio.prelude.Debug 9 | import _root_.zio.prelude.Debug.VectorDebug 10 | import _root_.zio.prelude.Debug.Repr 11 | import _root_.zio.prelude.Debug.Repr.Constructor 12 | 13 | import scala.collection.immutable.ListMap 14 | 15 | object SchemaVisitorDebug extends CachedSchemaCompiler.Impl[Debug] { 16 | protected type Aux[A] = Debug[A] 17 | def fromSchema[A]( 18 | schema: Schema[A], 19 | cache: Cache 20 | ): Debug[A] = { 21 | schema.compile(new SchemaVisitorDebug(cache)) 22 | } 23 | } 24 | 25 | final class SchemaVisitorDebug( 26 | val cache: CompilationCache[Debug] 27 | ) extends SchemaVisitor.Cached[Debug] { 28 | self => 29 | override def primitive[P]( 30 | shapeId: ShapeId, 31 | hints: Hints, 32 | tag: Primitive[P] 33 | ): Debug[P] = primDebugPf(tag) 34 | 35 | override def collection[C[_], A]( 36 | shapeId: ShapeId, 37 | hints: Hints, 38 | tag: CollectionTag[C], 39 | member: Schema[A] 40 | ): Debug[C[A]] = { 41 | implicit val memberDebug: Debug[A] = self(member) 42 | tag match { 43 | case CollectionTag.ListTag => Debug[List[A]] 44 | case CollectionTag.SetTag => setDebug 45 | case CollectionTag.VectorTag => VectorDebug 46 | case CollectionTag.IndexedSeqTag => indexedSeqDebug 47 | 48 | } 49 | } 50 | 51 | override def map[K, V]( 52 | shapeId: ShapeId, 53 | hints: Hints, 54 | key: Schema[K], 55 | value: Schema[V] 56 | ): Debug[Map[K, V]] = { 57 | implicit val keyD: Debug[K] = self(key) 58 | implicit val valueD: Debug[V] = self(value) 59 | Debug[Map[K, V]] 60 | } 61 | 62 | override def enumeration[E]( 63 | shapeId: ShapeId, 64 | hints: Hints, 65 | tag: EnumTag[E], 66 | values: List[EnumValue[E]], 67 | total: E => EnumValue[E] 68 | ): Debug[E] = Debug.make(e => Repr.String(total(e).stringValue)) 69 | 70 | override def struct[S]( 71 | shapeId: ShapeId, 72 | hints: Hints, 73 | fields: Vector[Field[S, ?]], 74 | make: IndexedSeq[Any] => S 75 | ): Debug[S] = { 76 | def compileField[A]( 77 | field: Field[S, A] 78 | ): S => (String, Repr) = { 79 | val debugField = self(field.schema) 80 | s => (field.label, debugField.debug(field.get(s))) 81 | } 82 | 83 | val functions = fields.map(f => compileField(f)) 84 | Debug.make { s => 85 | val values = functions 86 | .map(f => f(s)) 87 | Constructor( 88 | shapeId.namespace.split(".").toList, 89 | shapeId.name, 90 | ListMap(values*) 91 | ) 92 | } 93 | } 94 | 95 | override def union[U]( 96 | shapeId: ShapeId, 97 | hints: Hints, 98 | alternatives: Vector[Alt[U, ?]], 99 | dispatch: Alt.Dispatcher[U] 100 | ): Debug[U] = { 101 | val precomputed: Precompiler[Debug] = new Precompiler[Debug] { 102 | override def apply[A](label: String, instance: Schema[A]): Debug[A] = { 103 | val debugUnionInst = self(instance) 104 | (t: A) => 105 | Constructor( 106 | shapeId.namespace.split(".").toList, 107 | shapeId.name, 108 | ListMap(label -> debugUnionInst.debug(t)) 109 | ) 110 | } 111 | } 112 | implicit val encoderKShow: EncoderK[Debug, Repr] = 113 | new EncoderK[Debug, Repr] { 114 | override def apply[A](fa: Debug[A], a: A): Repr = fa.debug(a) 115 | 116 | override def absorb[A](f: A => Repr): Debug[A] = Debug.make(f) 117 | } 118 | dispatch.compile(precomputed) 119 | } 120 | 121 | override def biject[A, B]( 122 | schema: Schema[A], 123 | bijection: Bijection[A, B] 124 | ): Debug[B] = { 125 | val debugA = self(schema) 126 | Debug.make(b => debugA.debug(bijection.from(b))) 127 | } 128 | 129 | override def refine[A, B]( 130 | schema: Schema[A], 131 | refinement: Refinement[A, B] 132 | ): Debug[B] = { 133 | val debugA = self(schema) 134 | Debug.make(b => debugA.debug(refinement.from(b))) 135 | } 136 | 137 | override def lazily[A](suspend: Lazy[Schema[A]]): Debug[A] = { 138 | val ss = suspend.map { 139 | self(_) 140 | } 141 | a => ss.value.debug(a) 142 | } 143 | 144 | override def option[A](schema: Schema[A]): Debug[Option[A]] = { 145 | implicit val debugA: Debug[A] = self(schema) 146 | Debug[Option[A]] 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/internals/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import cats.implicits.toFoldableOps 4 | import smithy4s.{Hints, Service, ShapeId} 5 | import smithy4s.schema.Schema 6 | import smithy4s.zio.compliancetests.ComplianceTest.ComplianceResult 7 | import zio.ZIO 8 | import zio.http.{Headers, Request, Response} 9 | 10 | import java.net.URLDecoder 11 | import java.nio.charset.StandardCharsets 12 | import scala.collection.immutable.ListMap 13 | 14 | package object internals { 15 | 16 | private val urlDecoder: String => String = 17 | URLDecoder.decode(_, StandardCharsets.UTF_8.toString) 18 | 19 | def prepareService[I, E, O, SE, SO, Alg[_[_, _, _, _, _]]]( 20 | originalService: Service[Alg], 21 | endpoint: Service[Alg]#Endpoint[I, E, O, SE, SO] 22 | ): (Service.Reflective[NoInputOp], Request) = { 23 | val newHints = { 24 | val newHttp = smithy.api.Http( 25 | method = smithy.api.NonEmptyString("GET"), 26 | uri = smithy.api.NonEmptyString("/") 27 | ) 28 | val code = 29 | endpoint.hints.get[smithy.api.Http].map(_.code).getOrElse(newHttp.code) 30 | Hints(newHttp.copy(code = code)) 31 | } 32 | val amendedOperation = 33 | Schema 34 | .operation(ShapeId("custom", "endpoint")) 35 | .withHints(newHints) 36 | .withOutput(endpoint.output) 37 | .withErrorOption(endpoint.error) 38 | 39 | val amendedEndpoint = 40 | smithy4s.Endpoint[NoInputOp, Unit, E, O, Nothing, Nothing]( 41 | amendedOperation, 42 | (_: Unit) => NoInputOp() 43 | ) 44 | val request = Request.get("/") 45 | val amendedService = 46 | // format: off 47 | new Service.Reflective[NoInputOp] { 48 | override def id: ShapeId = ShapeId("custom", "service") 49 | override def endpoints: Vector[Endpoint[_, _, _, _, _]] = Vector(amendedEndpoint) 50 | override def input[I_, E_, O_, SI_, SO_](op: NoInputOp[I_, E_, O_, SI_, SO_]) : I_ = ??? 51 | override def ordinal[I_, E_, O_, SI_, SO_](op: NoInputOp[I_, E_, O_, SI_, SO_]): Int = ??? 52 | override def version: String = originalService.version 53 | override def hints: Hints = originalService.hints 54 | } 55 | // format: on 56 | (amendedService, request) 57 | } 58 | 59 | def failWithBodyAsMessage( 60 | response: Response 61 | ): ZIO[Any, Throwable, ComplianceResult] = { 62 | response.body.asString.map(message => 63 | asserts.fail( 64 | s"Expected either an IntendedShortCircuit error or a 5xx response, but got a response with status ${response.status} and message ${message}" 65 | ) 66 | ) 67 | } 68 | private[compliancetests] def splitQuery( 69 | queryString: String 70 | ): (String, String) = { 71 | queryString.split("=", 2) match { 72 | case Array(k, v) => 73 | ( 74 | k, 75 | urlDecoder(v) 76 | ) 77 | case Array(k) => (k, "") 78 | case _ => throw new IllegalArgumentException("Invalid query") 79 | } 80 | } 81 | 82 | private[compliancetests] def parseQueryParams( 83 | queryParams: Option[List[String]] 84 | ): ListMap[String, List[String]] = { 85 | queryParams.combineAll 86 | .map(splitQuery) 87 | .foldLeft[ListMap[String, List[String]]](ListMap.empty) { 88 | case (acc, (k, v)) => 89 | acc.get(k) match { 90 | case Some(value) => acc + (k -> (value :+ v)) 91 | case None => acc + (k -> List(v)) 92 | } 93 | } 94 | } 95 | 96 | private def escape(cs: CharSequence): String = { 97 | val str = cs.toString 98 | val withEscapedQuotes = str.replace("\"", "\\\"") 99 | if (str.contains(",")) { 100 | "\"" + withEscapedQuotes + "\"" 101 | } else withEscapedQuotes 102 | } 103 | 104 | /** 105 | * If there's a single value for a given key, injects in the map without changes. 106 | * If there a multiple values for a given key, escape each value, escape quotes, then add quotes. 107 | */ 108 | private[compliancetests] def collapseHeaders( 109 | headers: Headers 110 | ): Map[CharSequence, CharSequence] = { 111 | def append( 112 | acc: Map[CharSequence, List[CharSequence]], 113 | key: CharSequence, 114 | newValue: CharSequence 115 | ) = { 116 | 117 | (key -> acc 118 | .get(key) 119 | .map(existing => existing :+ newValue) 120 | .getOrElse(List(newValue))) 121 | } 122 | 123 | val multimap = 124 | headers.headers.iterator.foldLeft( 125 | Map.empty[CharSequence, List[CharSequence]] 126 | ) { case (acc, header) => 127 | acc + append(acc, header.headerName, header.renderedValue) 128 | } 129 | multimap.collect { 130 | case (key, value :: Nil) => (key, value) 131 | case (key, values) if values.size > 1 => 132 | (key, values.map(escape).mkString(", ")) 133 | } 134 | 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /modules/shared/src/main/scala/smithy4s/zio/shared/utils/UrlCodingUtils.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.shared.utils 2 | 3 | import java.util.Locale 4 | import util.matching.Regex 5 | import util.matching.Regex.Match 6 | import java.nio.charset.{Charset, StandardCharsets} 7 | import java.nio.{ByteBuffer, CharBuffer} 8 | import collection.immutable.BitSet 9 | 10 | // CREDITS : https://github.com/scalatra/rl/blob/v0.4.10/core/src/main/scala/rl/UrlCodingUtils.scala 11 | trait UrlCodingUtils { 12 | 13 | private val alphaNum = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') 14 | private val unreserved = alphaNum ++ "-_.~" 15 | private val toSkip = BitSet( 16 | (alphaNum ++ "!$&'()*+,;=:/?@-._~".toSet) 17 | .map(_.toInt): _* 18 | ) 19 | private lazy val SkipEncodeInPath = BitSet( 20 | (unreserved ++ ":@!$&'()*+,;=").map(_.toInt): _* 21 | ) 22 | private val space = ' '.toInt 23 | private val PctEncoded = """%([0-9a-fA-F][0-9a-fA-F])""".r 24 | private val LowerPctEncoded = """%([0-9a-f][0-9a-f])""".r 25 | private val InvalidChars = "[^\\.a-zA-Z0-9!$&'()*+,;=:/?#\\[\\]@-_~]".r 26 | 27 | private val HexUpperCaseChars = (0 until 16) map { i => 28 | Character.toUpperCase(Character.forDigit(i, 16)) 29 | } 30 | 31 | private val UTF_8 = "UTF-8" 32 | private val Utf8 = Charset.forName(UTF_8) 33 | 34 | private def isUrlEncoded(string: String) = { 35 | PctEncoded.findFirstIn(string).isDefined 36 | } 37 | 38 | def containsInvalidUriChars(string: String) = { 39 | InvalidChars.findFirstIn(string).isDefined 40 | } 41 | 42 | def needsUrlEncoding(string: String) = { 43 | !isUrlEncoded(string) && containsInvalidUriChars(string) 44 | } 45 | 46 | def ensureUrlEncoding(string: String) = 47 | if (needsUrlEncoding(string)) urlEncode(string) else string 48 | 49 | def ensureUppercasedEncodings(string: String) = { 50 | LowerPctEncoded.replaceAllIn( 51 | string, 52 | (_: Match) match { 53 | case Regex.Groups(v) => "%" + v.toUpperCase(Locale.ENGLISH) 54 | case _ => 55 | throw new IllegalArgumentException("Invalid URL encoding pattern") 56 | } 57 | ) 58 | } 59 | 60 | def pathEncode(s: String): String = urlEncode(s, toSkip = SkipEncodeInPath) 61 | 62 | def pathDecode(str: String): String = urlDecode(str) 63 | 64 | def urlEncode( 65 | toEncode: String, 66 | charset: Charset = StandardCharsets.UTF_8, 67 | spaceIsPlus: Boolean = false, 68 | toSkip: BitSet = toSkip 69 | ): String = { 70 | val in = charset.encode(ensureUppercasedEncodings(toEncode)) 71 | val out = CharBuffer.allocate((in.remaining() * 3.toFloat).ceil.toInt) 72 | while (in.hasRemaining) { 73 | val b = in.get() & 0xff 74 | if (toSkip.contains(b)) { 75 | out.put(b.toChar) 76 | } else if (b == space && spaceIsPlus) { 77 | out.put('+') 78 | } else { 79 | out.put('%') 80 | out.put(HexUpperCaseChars((b >> 4) & 0xf)) 81 | out.put(HexUpperCaseChars(b & 0xf)) 82 | } 83 | } 84 | out.flip() 85 | out.toString 86 | } 87 | 88 | def urlDecode( 89 | toDecode: String, 90 | charset: Charset = Utf8, 91 | plusIsSpace: Boolean = false, 92 | toSkip: String = "" 93 | ): String = { 94 | val in = CharBuffer.wrap(toDecode) 95 | // reserve enough space for 3-byte chars like japanese, and hope nobody uses a string of only 4-byte chars 96 | val out = ByteBuffer.allocate(in.remaining() * 3) 97 | val skip = BitSet(toSkip.toSet[Char].map(c => c.toInt).toSeq: _*) 98 | while (in.hasRemaining) { 99 | val mark = in.position() 100 | val c = in.get() 101 | if (c == '%') { 102 | if (in.remaining() >= 2) { 103 | val xc = in.get() 104 | val yc = in.get() 105 | val x = Character.digit(xc, 0x10) 106 | val y = Character.digit(yc, 0x10) 107 | if (x != -1 && y != -1) { 108 | val oo = (x << 4) + y 109 | if (!skip.contains(oo)) { 110 | out.put(oo.toByte) 111 | } else { 112 | out.put('%'.toByte) 113 | out.put(xc.toByte) 114 | out.put(yc.toByte) 115 | } 116 | } else { 117 | out.put('%'.toByte) 118 | in.position(mark + 1) 119 | } 120 | } else { 121 | out.put('%'.toByte) 122 | } 123 | } else if (c == '+' && plusIsSpace) { 124 | out.put(' '.toByte) 125 | } else { 126 | // normally `out.put(c.toByte)` would be enough since the url is %-encoded, 127 | // however there are cases where a string can be partially decoded 128 | // so we have to make sure the non us-ascii chars get preserved properly. 129 | if (this.toSkip.contains(c)) 130 | out.put(c.toByte) 131 | else { 132 | out.put(charset.encode(String.valueOf(c))) 133 | } 134 | } 135 | } 136 | out.flip() 137 | charset.decode(out).toString 138 | } 139 | 140 | } 141 | object UrlCodingUtils extends UrlCodingUtils 142 | -------------------------------------------------------------------------------- /modules/test-scenarios/src/generated/smithy4s/example/RecursiveInputService.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.example 2 | 3 | import smithy4s.Endpoint 4 | import smithy4s.Hints 5 | import smithy4s.Schema 6 | import smithy4s.Service 7 | import smithy4s.ShapeId 8 | import smithy4s.Transformation 9 | import smithy4s.kinds.PolyFunction5 10 | import smithy4s.kinds.toPolyFunction5.const5 11 | import smithy4s.schema.OperationSchema 12 | import smithy4s.schema.Schema.unit 13 | 14 | trait RecursiveInputServiceGen[F[_, _, _, _, _]] { 15 | self => 16 | 17 | /** HTTP PUT /subscriptions */ 18 | def recursiveInputOperation(hello: Option[RecursiveInput] = None): F[RecursiveInput, Nothing, Unit, Nothing, Nothing] 19 | 20 | final def transform: Transformation.PartiallyApplied[RecursiveInputServiceGen[F]] = Transformation.of[RecursiveInputServiceGen[F]](this) 21 | } 22 | 23 | object RecursiveInputServiceGen extends Service.Mixin[RecursiveInputServiceGen, RecursiveInputServiceOperation] { 24 | 25 | val id: ShapeId = ShapeId("smithy4s.example", "RecursiveInputService") 26 | val version: String = "0.0.1" 27 | 28 | val hints: Hints = Hints( 29 | alloy.SimpleRestJson(), 30 | ).lazily 31 | 32 | def apply[F[_]](implicit F: Impl[F]): F.type = F 33 | 34 | object ErrorAware { 35 | def apply[F[_, _]](implicit F: ErrorAware[F]): F.type = F 36 | type Default[F[+_, +_]] = Constant[smithy4s.kinds.stubs.Kind2[F]#toKind5] 37 | } 38 | 39 | val endpoints: Vector[smithy4s.Endpoint[RecursiveInputServiceOperation, _, _, _, _, _]] = Vector( 40 | RecursiveInputServiceOperation.RecursiveInputOperation, 41 | ) 42 | 43 | def input[I, E, O, SI, SO](op: RecursiveInputServiceOperation[I, E, O, SI, SO]): I = op.input 44 | def ordinal[I, E, O, SI, SO](op: RecursiveInputServiceOperation[I, E, O, SI, SO]): Int = op.ordinal 45 | override def endpoint[I, E, O, SI, SO](op: RecursiveInputServiceOperation[I, E, O, SI, SO]) = op.endpoint 46 | class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing]) extends RecursiveInputServiceOperation.Transformed[RecursiveInputServiceOperation, P](reified, const5(value)) 47 | type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5] 48 | def reified: RecursiveInputServiceGen[RecursiveInputServiceOperation] = RecursiveInputServiceOperation.reified 49 | def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](alg: RecursiveInputServiceGen[P], f: PolyFunction5[P, P1]): RecursiveInputServiceGen[P1] = new RecursiveInputServiceOperation.Transformed(alg, f) 50 | def fromPolyFunction[P[_, _, _, _, _]](f: PolyFunction5[RecursiveInputServiceOperation, P]): RecursiveInputServiceGen[P] = new RecursiveInputServiceOperation.Transformed(reified, f) 51 | def toPolyFunction[P[_, _, _, _, _]](impl: RecursiveInputServiceGen[P]): PolyFunction5[RecursiveInputServiceOperation, P] = RecursiveInputServiceOperation.toPolyFunction(impl) 52 | 53 | } 54 | 55 | sealed trait RecursiveInputServiceOperation[Input, Err, Output, StreamedInput, StreamedOutput] { 56 | def run[F[_, _, _, _, _]](impl: RecursiveInputServiceGen[F]): F[Input, Err, Output, StreamedInput, StreamedOutput] 57 | def ordinal: Int 58 | def input: Input 59 | def endpoint: Endpoint[RecursiveInputServiceOperation, Input, Err, Output, StreamedInput, StreamedOutput] 60 | } 61 | 62 | object RecursiveInputServiceOperation { 63 | 64 | object reified extends RecursiveInputServiceGen[RecursiveInputServiceOperation] { 65 | def recursiveInputOperation(hello: Option[RecursiveInput] = None): RecursiveInputOperation = RecursiveInputOperation(RecursiveInput(hello)) 66 | } 67 | class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: RecursiveInputServiceGen[P], f: PolyFunction5[P, P1]) extends RecursiveInputServiceGen[P1] { 68 | def recursiveInputOperation(hello: Option[RecursiveInput] = None): P1[RecursiveInput, Nothing, Unit, Nothing, Nothing] = f[RecursiveInput, Nothing, Unit, Nothing, Nothing](alg.recursiveInputOperation(hello)) 69 | } 70 | 71 | def toPolyFunction[P[_, _, _, _, _]](impl: RecursiveInputServiceGen[P]): PolyFunction5[RecursiveInputServiceOperation, P] = new PolyFunction5[RecursiveInputServiceOperation, P] { 72 | def apply[I, E, O, SI, SO](op: RecursiveInputServiceOperation[I, E, O, SI, SO]): P[I, E, O, SI, SO] = op.run(impl) 73 | } 74 | final case class RecursiveInputOperation(input: RecursiveInput) extends RecursiveInputServiceOperation[RecursiveInput, Nothing, Unit, Nothing, Nothing] { 75 | def run[F[_, _, _, _, _]](impl: RecursiveInputServiceGen[F]): F[RecursiveInput, Nothing, Unit, Nothing, Nothing] = impl.recursiveInputOperation(input.hello) 76 | def ordinal: Int = 0 77 | def endpoint: smithy4s.Endpoint[RecursiveInputServiceOperation,RecursiveInput, Nothing, Unit, Nothing, Nothing] = RecursiveInputOperation 78 | } 79 | object RecursiveInputOperation extends smithy4s.Endpoint[RecursiveInputServiceOperation,RecursiveInput, Nothing, Unit, Nothing, Nothing] { 80 | val schema: OperationSchema[RecursiveInput, Nothing, Unit, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example", "RecursiveInputOperation")) 81 | .withInput(RecursiveInput.schema) 82 | .withOutput(unit) 83 | .withHints(smithy.api.Http(method = smithy.api.NonEmptyString("PUT"), uri = smithy.api.NonEmptyString("/subscriptions"), code = 200), smithy.api.Idempotent()) 84 | def wrap(input: RecursiveInput): RecursiveInputOperation = RecursiveInputOperation(input) 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/package.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio 2 | 3 | import cats.syntax.all.* 4 | import cats.{Eq, Monoid} 5 | import smithy.test.HttpRequestTestCase 6 | import smithy4s.zio.compliancetests.ComplianceTest.ComplianceResult 7 | import smithy4s.zio.compliancetests.internals.asserts.* 8 | import smithy4s.zio.compliancetests.internals.asserts.testCase.{ 9 | checkHeaders, 10 | checkQueryParameters 11 | } 12 | import smithy4s.zio.compliancetests.internals.{asserts, parseQueryParams} 13 | import smithy4s.zio.shared.utils.UrlCodingUtils.pathDecode 14 | import zio.http.{ 15 | Body, 16 | Header, 17 | Headers, 18 | MediaType, 19 | Method, 20 | Path, 21 | QueryParams, 22 | Request, 23 | Routes, 24 | URL 25 | } 26 | import zio.interop.catz.core.* 27 | import zio.{Chunk, Promise, Scope, Task, ZIO, durationInt} 28 | 29 | import java.util.concurrent.TimeoutException 30 | 31 | package object compliancetests { 32 | 33 | type HttpRoutes = Routes[Any, Throwable] 34 | type ResourcefulTask[Output] = ZIO[Scope, Throwable, Output] 35 | 36 | private[compliancetests] def makeRequest( 37 | baseUri: URL, 38 | testCase: HttpRequestTestCase 39 | ): Request = { 40 | val expectedHeaders = testCase.headers 41 | .map(headers => Headers.apply(headers.toList.map(Header.Custom.tupled))) 42 | .getOrElse(Headers.empty) 43 | 44 | val expectedContentType = testCase.bodyMediaType 45 | .flatMap(MediaType.forContentType) 46 | .map(contentType => Headers(Header.ContentType(contentType))) 47 | .getOrElse(Headers.empty) 48 | 49 | val allExpectedHeaders = expectedHeaders ++ expectedContentType 50 | 51 | val expectedMethod = Method 52 | .fromString(testCase.method) 53 | 54 | val expectedUrl: URL = constructUrl(baseUri, testCase) 55 | 56 | val body = 57 | testCase.body 58 | .map(Body.fromString(_)) 59 | .getOrElse(Body.empty) 60 | 61 | Request( 62 | method = expectedMethod, 63 | url = expectedUrl, 64 | headers = allExpectedHeaders, 65 | body = body 66 | ) 67 | } 68 | 69 | private def constructUrl(baseUri: URL, testCase: HttpRequestTestCase) = { 70 | val expectedUri = baseUri 71 | .addPath(testCase.uri) 72 | .addQueryParams( 73 | QueryParams(parseQueryParams(testCase.queryParams).toList.map { 74 | case (k, v) => 75 | (k, Chunk.fromIterable(v)) 76 | }: _*) 77 | ) 78 | expectedUri 79 | } 80 | 81 | def matchRequest( 82 | request: Request, 83 | testCase: HttpRequestTestCase, 84 | baseUri: URL 85 | ): Task[ComplianceResult] = { 86 | 87 | val bodyAssert: ZIO[Any, Throwable, ComplianceResult] = 88 | request.body.asString.map { responseBody => 89 | bodyEql( 90 | responseBody, 91 | testCase.body, 92 | testCase.bodyMediaType 93 | ) 94 | } 95 | 96 | val resolvedHostPrefix = 97 | testCase.resolvedHost 98 | .zip(testCase.host) 99 | .map { case (resolved, host) => resolved.split(host)(0) } 100 | 101 | val resolvedHostAssert: List[ComplianceResult] = 102 | request.url.host 103 | .zip(resolvedHostPrefix) 104 | .map { case (a, b) => 105 | contains(a, b, "resolved host test :") 106 | } 107 | .toList 108 | 109 | val receivedPathSegments = 110 | request.url.path.segments.map(pathDecode) 111 | val expectedPathSegments = 112 | Path 113 | .decode(testCase.uri) 114 | .segments 115 | .map(pathDecode) 116 | 117 | val expectedUrl = constructUrl(baseUri, testCase) 118 | val pathAssert: ComplianceResult = 119 | eql( 120 | receivedPathSegments.toList, 121 | expectedPathSegments.toList, 122 | "path test :" 123 | ) 124 | val queryAssert: ComplianceResult = checkQueryParameters( 125 | testCase, 126 | expectedUrl.queryParams.map 127 | ) 128 | val methodAssert: ComplianceResult = eql( 129 | request.method.name.toLowerCase(), 130 | testCase.method.toLowerCase(), 131 | "method test :" 132 | ) 133 | val ioAsserts = (resolvedHostAssert ++ List( 134 | checkHeaders(testCase, request.headers), 135 | pathAssert, 136 | queryAssert, 137 | methodAssert 138 | )).map(ZIO.succeed(_)) :+ bodyAssert 139 | ioAsserts.combineAll(cats.Applicative.monoid[Task, ComplianceResult]) 140 | 141 | } 142 | 143 | val timeOutMessage = 144 | """|Timed-out while waiting for an input. 145 | | 146 | |This probably means that the Router implementation either failed to decode the request 147 | |or failed to route the decoded input to the correct service method. 148 | |""".stripMargin 149 | 150 | def runCompare[I: Eq]( 151 | inputPromise: Promise[Nothing, I], 152 | testModel: I 153 | ): ZIO[Any, TimeoutException, ComplianceResult] = { 154 | inputPromise.await 155 | .timeoutFail(new TimeoutException())(1.second) 156 | .map { foundInput => 157 | asserts.eql(foundInput, testModel) 158 | } 159 | .catchSome { case _: TimeoutException => 160 | ZIO.succeed(asserts.fail(timeOutMessage)) 161 | } 162 | } 163 | 164 | implicit val headerMonoid: Monoid[Headers] = new Monoid[Headers] { 165 | override def empty: Headers = Headers.empty 166 | override def combine(x: Headers, y: Headers): Headers = x ++ y 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/SchemaVisitorEqual.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | 3 | import smithy4s.capability.EncoderK 4 | import smithy4s.zio.prelude.instances.all.primEqualPf 5 | import smithy4s.schema._ 6 | import smithy4s.{Bijection, Hints, Lazy, Refinement, Schema, ShapeId} 7 | import zio.prelude.Equal 8 | 9 | object SchemaVisitorEqual extends CachedSchemaCompiler.Impl[Equal] { 10 | protected type Aux[A] = Equal[A] 11 | def fromSchema[A]( 12 | schema: Schema[A], 13 | cache: Cache 14 | ): Equal[A] = { 15 | schema.compile(new SchemaVisitorEqual(cache)) 16 | } 17 | } 18 | 19 | final class SchemaVisitorEqual( 20 | val cache: CompilationCache[Equal] 21 | ) extends SchemaVisitor.Cached[Equal] { 22 | self => 23 | override def primitive[P]( 24 | shapeId: ShapeId, 25 | hints: Hints, 26 | tag: Primitive[P] 27 | ): Equal[P] = primEqualPf(tag) 28 | 29 | override def collection[C[_], A]( 30 | shapeId: ShapeId, 31 | hints: Hints, 32 | tag: CollectionTag[C], 33 | member: Schema[A] 34 | ): Equal[C[A]] = { 35 | implicit val memberHash: Equal[A] = self(member) 36 | tag match { 37 | case CollectionTag.ListTag => Equal[List[A]] 38 | case CollectionTag.SetTag => Equal[Set[A]] 39 | case CollectionTag.VectorTag => Equal[Vector[A]] 40 | case CollectionTag.IndexedSeqTag => 41 | Equal[scala.collection.immutable.List[A]].contramap(_.toList) 42 | } 43 | } 44 | 45 | override def map[K, V]( 46 | shapeId: ShapeId, 47 | hints: Hints, 48 | key: Schema[K], 49 | value: Schema[V] 50 | ): Equal[Map[K, V]] = { 51 | implicit val eV = self(value) 52 | Equal[Map[K, V]] 53 | } 54 | 55 | override def enumeration[E]( 56 | shapeId: ShapeId, 57 | hints: Hints, 58 | tag: EnumTag[E], 59 | values: List[EnumValue[E]], 60 | total: E => EnumValue[E] 61 | ): Equal[E] = { 62 | implicit val enumValueHash: Equal[EnumValue[E]] = 63 | tag match { 64 | case EnumTag.IntEnum() => 65 | Equal[Int].contramap(_.intValue) 66 | case _ => 67 | Equal[String].contramap(_.stringValue) 68 | } 69 | 70 | Equal[EnumValue[E]].contramap(total) 71 | } 72 | 73 | override def struct[S]( 74 | shapeId: ShapeId, 75 | hints: Hints, 76 | fields: Vector[Field[S, ?]], 77 | make: IndexedSeq[Any] => S 78 | ): Equal[S] = { 79 | def forField[A2](field: Field[S, A2]): Equal[S] = { 80 | field.schema.compile(self).contramap(field.get) 81 | } 82 | val equalInstances: Vector[Equal[S]] = fields.map(field => forField(field)) 83 | (l: S, r: S) => equalInstances.forall(_.equal(l, r)) 84 | } 85 | 86 | override def union[U]( 87 | shapeId: ShapeId, 88 | hints: Hints, 89 | alternatives: Vector[Alt[U, ?]], 90 | dispatch: Alt.Dispatcher[U] 91 | ): Equal[U] = { 92 | 93 | // A version of `Equal` that assumes for the eqv method that the RHS is "up-casted" to U. 94 | trait AltEqual[A] { 95 | def eqv(a: A, u: U): Boolean 96 | } 97 | 98 | // The encoded form that Hash works against for the eqV method, is a partially-applied curried function. 99 | implicit val encoderKInstance: EncoderK[AltEqual, U => Boolean] = 100 | new EncoderK[AltEqual, U => Boolean] { 101 | def apply[A](fa: AltEqual[A], a: A): U => Boolean = { (u: U) => 102 | fa.eqv(a, u) 103 | } 104 | 105 | def absorb[A](f: A => U => Boolean): AltEqual[A] = new AltEqual[A] { 106 | def eqv(a: A, u: U): Boolean = f(a)(u) 107 | } 108 | } 109 | 110 | val precompiler = new Alt.Precompiler[AltEqual] { 111 | def apply[A](label: String, instance: Schema[A]): AltEqual[A] = { 112 | // Here we "cheat" to recover the `Alt` corresponding to `A`, as this information 113 | // is lost in the precompiler. 114 | val altA = 115 | alternatives.find(_.label == label).get.asInstanceOf[Alt[U, A]] 116 | 117 | // We're using it to get a function that lets us project the `U` against `A`. 118 | // `U` is not necessarily an `A, so this function returns an `Option` 119 | val eqvA = instance.compile(self) 120 | new AltEqual[A] { 121 | def eqv(a: A, u: U): Boolean = altA.project.lift(u) match { 122 | case None => false // U is not an A. 123 | case Some(a2) => 124 | eqvA.equal(a, a2) // U is an A, we delegate the comparison 125 | } 126 | } 127 | } 128 | } 129 | val altEqualU: AltEqual[U] = dispatch.compile(precompiler) 130 | (l: U, r: U) => altEqualU.eqv(l, r) 131 | } 132 | 133 | override def biject[A, B]( 134 | schema: Schema[A], 135 | bijection: Bijection[A, B] 136 | ): Equal[B] = { 137 | val eq = self(schema) 138 | eq.contramap(bijection.from) 139 | } 140 | 141 | override def refine[A, B]( 142 | schema: Schema[A], 143 | refinement: Refinement[A, B] 144 | ): Equal[B] = { 145 | val eq = self(schema) 146 | eq.contramap(refinement.from) 147 | } 148 | 149 | override def lazily[A](suspend: Lazy[Schema[A]]): Equal[A] = { 150 | val lazyEq: Lazy[Equal[A]] = suspend.map(self(_)) 151 | (l: A, r: A) => lazyEq.value.equal(l, r) 152 | } 153 | 154 | override def option[A](schema: Schema[A]): Equal[Option[A]] = { 155 | implicit val eq: Equal[A] = self(schema) 156 | Equal[Option[A]] 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /modules/compliance-tests/src/main/scala/smithy4s/zio/compliancetests/ProtocolComplianceSuite.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.compliancetests 2 | 3 | import smithy4s.codecs._ 4 | import smithy4s.dynamic.DynamicSchemaIndex 5 | import smithy4s.dynamic.DynamicSchemaIndex.load 6 | import smithy4s.dynamic.model.Model 7 | import smithy4s.{Blob, Document, Schema, ShapeId} 8 | import zio.test._ 9 | import zio.{Scope, Task, ZIO} 10 | 11 | import java.nio.file.{Path, Paths} 12 | 13 | abstract class ProtocolComplianceSuite extends ZIOSpecDefault { 14 | 15 | def allRules(dsi: DynamicSchemaIndex): Task[ComplianceTest[Task] => ShouldRun] 16 | 17 | def allTests(dsi: DynamicSchemaIndex): List[ComplianceTest[Task]] 18 | 19 | override def spec: Spec[TestEnvironment with Scope, Any] = { 20 | suite("Protocol Compliance Tests") { 21 | makeTests() 22 | } 23 | } 24 | 25 | def makeTests(): ZIO[Any, Throwable, List[Spec[Any, Throwable]]] = { 26 | // val includeTest = Filters.filterTests(this.)(getArgs) 27 | dynamicSchemaIndexLoader 28 | .flatMap(dsi => allRules(dsi).map(_ -> allTests(dsi))) 29 | .map { case (rules, tests) => tests.map((_, rules)) } 30 | /* .flatMap { case (rules, test) => 31 | if (includeTest(test.show)) ZStream.from((rules, test)) else ZStream.empty 32 | }*/ 33 | .map { all => 34 | all.map { case (test, rules) => 35 | makeTest(rules, test) 36 | } 37 | } 38 | } 39 | 40 | def dynamicSchemaIndexLoader: Task[DynamicSchemaIndex] 41 | 42 | def genClientTests( 43 | impl: ReverseRouter, 44 | shapeIds: ShapeId* 45 | )(dsi: DynamicSchemaIndex): List[ComplianceTest[Task]] = 46 | shapeIds.toList.flatMap(shapeId => 47 | dsi 48 | .getService(shapeId) 49 | .toList 50 | .flatMap(wrapper => { 51 | HttpProtocolCompliance 52 | .clientTests( 53 | impl, 54 | wrapper.service 55 | ) 56 | }) 57 | ) 58 | 59 | def genServerTests( 60 | impl: Router, 61 | shapeIds: ShapeId* 62 | )(dsi: DynamicSchemaIndex): List[ComplianceTest[Task]] = 63 | shapeIds.toList.flatMap(shapeId => 64 | dsi 65 | .getService(shapeId) 66 | .toList 67 | .flatMap(wrapper => { 68 | HttpProtocolCompliance 69 | .serverTests( 70 | impl, 71 | wrapper.service 72 | ) 73 | }) 74 | ) 75 | 76 | def genClientAndServerTests( 77 | impl: ReverseRouter with Router, 78 | shapeIds: ShapeId* 79 | )(dsi: DynamicSchemaIndex): List[ComplianceTest[Task]] = 80 | shapeIds.toList.flatMap(shapeId => 81 | dsi 82 | .getService(shapeId) 83 | .toList 84 | .flatMap(wrapper => { 85 | HttpProtocolCompliance 86 | .clientAndServerTests( 87 | impl, 88 | wrapper.service 89 | ) 90 | }) 91 | ) 92 | 93 | def loadDynamic( 94 | doc: Document 95 | ): Either[PayloadError, DynamicSchemaIndex] = { 96 | Document.decode[Model](doc).map(load) 97 | } 98 | 99 | private[smithy4s] def fileFromEnv(key: String): Task[Path] = { 100 | 101 | ZIO 102 | .attempt(Option(System.getenv(key))) 103 | .flatMap( 104 | { 105 | ZIO 106 | .fromOption(_) 107 | .mapBoth( 108 | _ => sys.error("MODEL_DUMP env var not set"), 109 | Paths.get(_) 110 | ) 111 | } 112 | ) 113 | } 114 | 115 | def decodeDocument( 116 | bytes: Array[Byte], 117 | codecApi: BlobDecoder.Compiler 118 | ): Document = { 119 | val codec: PayloadDecoder[Document] = codecApi.fromSchema(Schema.document) 120 | codec 121 | .decode(Blob(bytes)) 122 | .fold( 123 | pe => 124 | sys.error( 125 | s"unable to decode smithy model ${new String(bytes).take(100)} into document. error $pe" 126 | ), 127 | identity 128 | ) 129 | 130 | } 131 | 132 | private def makeTest( 133 | rule: ComplianceTest[Task] => ShouldRun, 134 | tc: ComplianceTest[Task] 135 | ): Spec[Any, Throwable] = { 136 | val shouldRun = rule(tc) 137 | shouldRun match { 138 | case ShouldRun.No => Spec.empty 139 | case ShouldRun.Yes => 140 | test(tc.show)(tc.run) @@ TestAspect.withLiveClock @@ TestAspect.parallel 141 | case ShouldRun.NotSure => Spec.empty 142 | // test(tc.show)(tc.run) @@ TestAspect.withLiveClock @@ TestAspect.ignore 143 | } 144 | 145 | } 146 | } 147 | 148 | // brought over from weaver https://github.com/disneystreaming/weaver-test/blob/d5489c994ecbe84f267550fb84c25c9fba473d70/modules/core/src/weaver/Filters.scala#L5 149 | /*object Filters { 150 | 151 | def toPattern(filter: String): Pattern = { 152 | val parts = filter 153 | .split("\\*", -1) 154 | .map { // Don't discard trailing empty string, if any. 155 | case "" => "" 156 | case str => Pattern.quote(str) 157 | } 158 | Pattern.compile(parts.mkString(".*")) 159 | } 160 | 161 | private type Predicate = String => Boolean 162 | 163 | /* private object atLine { 164 | def unapply(testPath: String): Option[(String, Int)] = { 165 | // Can't use string interpolation in pattern (2.12) 166 | val members = testPath.split(".line://") 167 | if (members.size == 2) { 168 | val suiteName = members(0) 169 | // Can't use .toIntOption (2.12) 170 | val maybeLine = scala.util.Try(members(1).toInt).toOption 171 | maybeLine.map(suiteName -> _) 172 | } else None 173 | } 174 | }*/ 175 | 176 | /* def filterTests( 177 | suiteName: String 178 | )(args: List[String]): String => Boolean = { 179 | 180 | def toPredicate(filter: String): Predicate = { 181 | filter match { 182 | 183 | case atLine(`suiteName`, line) => { case TestName(_, indicator, _) => 184 | indicator.line == line 185 | } 186 | case regexStr => { case TestName(name, _, _) => 187 | val fullName = suiteName + "." + name 188 | toPattern(regexStr).matcher(fullName).matches() 189 | } 190 | } 191 | } 192 | 193 | import scala.util.Try 194 | val maybePattern = for { 195 | index <- Option(args.indexOf("-o")) 196 | .orElse(Option(args.indexOf("--only"))) 197 | .filter(_ >= 0) 198 | filter <- Try(args(index + 1)).toOption 199 | } yield toPredicate(filter) 200 | testId => maybePattern.forall(_.apply(testId)) 201 | } 202 | */ 203 | }*/ 204 | -------------------------------------------------------------------------------- /modules/prelude/src/main/scala/smithy4s/zio/prelude/SchemaVisitorHash.scala: -------------------------------------------------------------------------------- 1 | package smithy4s.zio.prelude 2 | import smithy4s.{Bijection, Hints, Lazy, Refinement, ShapeId} 3 | import smithy4s.capability.EncoderK 4 | import smithy4s.zio.prelude.instances.all.primHashPf 5 | import smithy4s.schema._ 6 | import zio.prelude.Hash.makeFrom 7 | import zio.prelude.{Equal, Hash} 8 | import zio.prelude.coherent.HashOrd.derive 9 | 10 | import scala.util.hashing.MurmurHash3.productSeed 11 | 12 | object SchemaVisitorHash extends CachedSchemaCompiler.Impl[Hash] { 13 | protected type Aux[A] = Hash[A] 14 | def fromSchema[A]( 15 | schema: Schema[A], 16 | cache: Cache 17 | ): Hash[A] = { 18 | schema.compile(new SchemaVisitorHash(cache)) 19 | } 20 | } 21 | 22 | final class SchemaVisitorHash( 23 | val cache: CompilationCache[Hash] 24 | ) extends SchemaVisitor.Cached[Hash] { self => 25 | 26 | override def primitive[P]( 27 | shapeId: ShapeId, 28 | hints: Hints, 29 | tag: Primitive[P] 30 | ): Hash[P] = primHashPf(tag) 31 | 32 | override def collection[C[_], A]( 33 | shapeId: ShapeId, 34 | hints: Hints, 35 | tag: CollectionTag[C], 36 | member: Schema[A] 37 | ): Hash[C[A]] = { 38 | implicit val memberHash: Hash[A] = self(member) 39 | tag match { 40 | case CollectionTag.ListTag => Hash[List[A]] 41 | case CollectionTag.SetTag => Hash[Set[A]] 42 | case CollectionTag.VectorTag => Hash[Vector[A]] 43 | case CollectionTag.IndexedSeqTag => 44 | makeFrom( 45 | _.map(Hash[A].hash).hashCode, 46 | Equal.ListEqual.contramap(_.toList) 47 | ) 48 | } 49 | } 50 | 51 | override def map[K, V]( 52 | shapeId: ShapeId, 53 | hints: Hints, 54 | key: Schema[K], 55 | value: Schema[V] 56 | ): Hash[Map[K, V]] = { 57 | implicit val valueHash: Hash[V] = self(value) 58 | Hash[Map[K, V]] 59 | } 60 | 61 | override def enumeration[E]( 62 | shapeId: ShapeId, 63 | hints: Hints, 64 | tag: EnumTag[E], 65 | values: List[EnumValue[E]], 66 | total: E => EnumValue[E] 67 | ): Hash[E] = { 68 | implicit val enumValueHash: Hash[EnumValue[E]] = 69 | tag match { 70 | case EnumTag.IntEnum() => 71 | Hash[Int].contramap(_.intValue) 72 | case _ => 73 | Hash[String].contramap(_.stringValue) 74 | } 75 | 76 | Hash[EnumValue[E]].contramap(total) 77 | } 78 | 79 | override def struct[S]( 80 | shapeId: ShapeId, 81 | hints: Hints, 82 | fields: Vector[Field[S, ?]], 83 | make: IndexedSeq[Any] => S 84 | ): Hash[S] = { 85 | def forField[A2](field: Field[S, A2]): Hash[S] = { 86 | field.schema.compile(self).contramap(field.get) 87 | } 88 | 89 | val hashInstances: Vector[Hash[S]] = fields.map(field => forField(field)) 90 | // similar to how productPrefix is used 91 | val nameHash = Hash[String].hash(shapeId.name) 92 | new Hash[S] { 93 | def hash(x: S): Int = { 94 | val hashCodes = hashInstances.map(_.hash(x)) 95 | val all = nameHash +: hashCodes 96 | combineHash(productSeed, all*) 97 | } 98 | 99 | def checkEqual(l: S, r: S): Boolean = { 100 | hashInstances.forall(_.equal(l, r)) 101 | } 102 | } 103 | } 104 | 105 | override def union[U]( 106 | shapeId: ShapeId, 107 | hints: Hints, 108 | alternatives: Vector[Alt[U, ?]], 109 | dispatch: Alt.Dispatcher[U] 110 | ): Hash[U] = { 111 | 112 | // A version of `Hash` that assumes for the eqv method that the RHS is "up-casted" to U. 113 | trait AltHash[A] { 114 | def checkEquals(a: A, u: U): Boolean 115 | def hash(a: A): Int 116 | } 117 | 118 | // The encoded form that Hash works against for the eqV method, is a partially-applied curried function. 119 | implicit val encoderKInstance: EncoderK[AltHash, (U => Boolean, Int)] = 120 | new EncoderK[AltHash, (U => Boolean, Int)] { 121 | def apply[A](fa: AltHash[A], a: A): (U => Boolean, Int) = { 122 | ((u: U) => fa.checkEquals(a, u), fa.hash(a)) 123 | } 124 | 125 | def absorb[A](f: A => (U => Boolean, Int)): AltHash[A] = 126 | new AltHash[A] { 127 | def checkEquals(a: A, u: U): Boolean = f(a)._1(u) 128 | def hash(a: A): Int = f(a)._2 129 | } 130 | } 131 | 132 | val precompiler = new Alt.Precompiler[AltHash] { 133 | def apply[A](label: String, instance: Schema[A]): AltHash[A] = { 134 | // Here we "cheat" to recover the `Alt` corresponding to `A`, as this information 135 | // is lost in the precompiler. 136 | val altA = 137 | alternatives.find(_.label == label).get.asInstanceOf[Alt[U, A]] 138 | 139 | val labelHash = Hash[String].hash(label) 140 | 141 | // We're using it to get a function that lets us project the `U` against `A`. 142 | // `U` is not necessarily an `A, so this function returns an `Option` 143 | val hashA: Hash[A] = instance.compile(self) 144 | new AltHash[A] { 145 | 146 | override def checkEquals(a: A, u: U): Boolean = 147 | altA.project.lift(u) match { 148 | case None => false // U is not an A. 149 | case Some(a2) => 150 | hashA.equal(a, a2) // U is an A, we delegate the comparison 151 | } 152 | override def hash(a: A): Int = { 153 | combineHash(hashA.hash(a), labelHash) 154 | } 155 | } 156 | } 157 | } 158 | 159 | val altHashU: AltHash[U] = dispatch.compile(precompiler) 160 | new Hash[U] { 161 | def checkEqual(x: U, y: U): Boolean = altHashU.checkEquals(x, y) 162 | def hash(x: U): Int = altHashU.hash(x) 163 | } 164 | } 165 | 166 | override def biject[A, B]( 167 | schema: Schema[A], 168 | bijection: Bijection[A, B] 169 | ): Hash[B] = { 170 | implicit val hashA: Hash[A] = self(schema) 171 | Hash[A].contramap(bijection.from) 172 | } 173 | 174 | override def refine[A, B]( 175 | schema: Schema[A], 176 | refinement: Refinement[A, B] 177 | ): Hash[B] = { 178 | implicit val hashA: Hash[A] = self(schema) 179 | Hash[A].contramap(refinement.from) 180 | } 181 | 182 | override def lazily[A](suspend: Lazy[Schema[A]]): Hash[A] = { 183 | implicit val hashA: Lazy[Hash[A]] = suspend.map(self(_)) 184 | new Hash[A] { 185 | override def hash(x: A): Int = hashA.value.hash(x) 186 | 187 | override protected def checkEqual(l: A, r: A): Boolean = 188 | hashA.value.equal(l, r) 189 | } 190 | } 191 | 192 | override def option[A](schema: Schema[A]): Hash[Option[A]] = { 193 | implicit val hashA: Hash[A] = self(schema) 194 | Hash[Option[A]] 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Smithy4s-ZIO 2 | 3 | ### Introduction 4 | - A few small libs based off the great [Smithy4s](https://disneystreaming.github.io/smithy4s/) to enable integration with ZIO ecosystem. 5 | #### Keep in mind this is WIP 6 | 7 | ### Credits 8 | - This project is based completely off the [http4s](https://http4s.org/) integration in Smithy4s. 9 | 10 | ### Protocol Compliant 11 | - This library is protocol compliant with the [Alloy#SimpleRestJson](https://github.com/disneystreaming/alloy) protocol 12 | - To read more about protocol in this context please see [What is a Protocol](https://disneystreaming.github.io/smithy4s/docs/protocols/definition) 13 | 14 | 15 | ### Compliance Tests - wip 16 | - This project is tested using the [Smithy Protocol Compliance Tests](https://smithy.io/2.0/additional-specs/http-protocol-compliance-tests.html) for the `alloy#simpleRestJson` protocol. 17 | - Currently all Server tests pass 18 | 19 | ### Published Modules 20 | - Http for the [ZIO Http](https://zio.dev/http/) library 21 | - ZIO Http Client and Server implementations for the [`alloy#simpleRestJsonProtocol`](https://github.com/disneystreaming/alloy) 22 | - Prelude for the [ZIO Prelude](https://zio.dev/zio-prelude/) library 23 | - Automatic derivation of the following Typeclasses for Smithy4s generated schemas 24 | - Debug 25 | - Hash 26 | - Equals 27 | - Schema for [ZIO Schema](https://zio.dev/schema/) library - WIP 28 | - a Natural Transformation from Smithy4s Schema to a ZIO Schema 29 | 30 | 31 | ### Notes 32 | - This doc assumes an understanding of Smithy and Smithy4s. For all questions related to Smithy4s and the core concepts please see the [Smithy4s](https://disneystreaming.github.io/smithy4s/) documentation and the [Smithy](https://awslabs.github.io/smithy/) documentation. 33 | 34 | 35 | ### Usage 36 | 37 | This library is currently available for Scala binary versions 2.13 and 3.1. 38 | 39 | To use the latest version, include the following in your `build.sbt`: 40 | 41 | ```scala 42 | libraryDependencies ++= Seq( 43 | "io.github.yisraelu" %% "smithy4s-zio-http" % "@VERSION@" 44 | ) 45 | ``` 46 | 47 | The snapshot version is available via the Sonatype snapshots repository: ```@SNAPSHOT_VERSION@.``` 48 | 49 | 50 | 51 | ### Http Server and Client Quickstart (borrows from Smithy4s example) 52 | 53 | 54 | Below is a quick example of smithy4s in action. 55 | This page does not provide much explanation or detail. For more information on various aspects of smithy4s, read through the other sections of this documentation site. 56 | 57 | 58 | This section will get you started with a simple `sbt` module that enables smithy4s code generation. 59 | 60 | ### project/plugins.sbt 61 | 62 | Add the `smithy4s-sbt-codegen` plugin to your build. 63 | 64 | ```scala 65 | addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % "") 66 | ``` 67 | 68 | ### build.sbt 69 | 70 | Enable the plugin in your project, add the smithy and zio-http dependencies. 71 | 72 | ```scala 73 | import smithy4s.codegen.Smithy4sCodegenPlugin 74 | 75 | val example = project 76 | .in(file("modules/example")) 77 | .enablePlugins(Smithy4sCodegenPlugin) 78 | .settings( 79 | libraryDependencies ++= Seq( 80 | "io.github.yisraelu" %% "smithy4s-zio-http" % "@VERSION@" 81 | ) 82 | ) 83 | ``` 84 | 85 | ## Smithy content 86 | 87 | Now is the time to add some Smithy shapes to see what code generation can do for you. Following the setup above, the location for the Smithy content will change depending on what build tool you used. 88 | 89 | Now let's define an API in Smithy. Create the following file: 90 | 91 | - You'll write in `modules/example/src/main/smithy/ExampleService.smithy`. 92 | 93 | And add the content below: 94 | 95 | ```kotlin 96 | namespace smithy4s.hello 97 | 98 | use alloy#simpleRestJson 99 | 100 | @simpleRestJson 101 | service HelloWorldService { 102 | version: "1.0.0", 103 | operations: [Hello] 104 | } 105 | 106 | @http(method: "POST", uri: "/{name}", code: 200) 107 | operation Hello { 108 | input: Person, 109 | output: Greeting 110 | } 111 | 112 | structure Person { 113 | @httpLabel 114 | @required 115 | name: String, 116 | 117 | @httpQuery("town") 118 | town: String 119 | } 120 | 121 | structure Greeting { 122 | @required 123 | message: String 124 | } 125 | ``` 126 | 127 | The Scala code corresponding to this smithy file will be generated the next time you compile your project. 128 | 129 | ## Using the generated code 130 | 131 | Now, let's use the generated code by the service. You need to create a scala file at the following location: 132 | 133 | - `modules/example/src/main/scala/Main.scala` 134 | 135 | Implement your service by extending the generated Service trait. Wire up routes into server. 136 | 137 | 138 | ```scala mdoc:silent 139 | import example.hello._ 140 | import zio.{Task, ZIO,ZIOAppDefault, ExitCode, URIO} 141 | import zio.http._ 142 | import com.comcast.ip4s._ 143 | import smithy4s.zio.http.SimpleRestJsonBuilder 144 | 145 | object HelloWorldImpl extends HelloWorldService[Task] { 146 | def hello(name: String, town: Option[String]): Task[Greeting] = ZIO.succeed { 147 | town match { 148 | case None => Greeting(s"Hello $name!") 149 | case Some(t) => Greeting(s"Hello $name from $t!") 150 | } 151 | } 152 | } 153 | 154 | object Main extends ZIOAppDefault { 155 | 156 | val port = Port.fromInt(9000).get 157 | val app:Task[Routes[Any,Response]] = { 158 | for { 159 | _ <- zio.Console.printLine(s"Starting server on http://localhost:$port") 160 | routes <- SimpleRestJsonBuilder 161 | .routes(HelloWorldImpl) 162 | .lift 163 | app = routes.sandbox 164 | } yield app 165 | } 166 | 167 | override def run: URIO[Any, ExitCode] = { 168 | app 169 | .flatMap(Server.serve(_).provide(Server.defaultWithPort(port.value))) 170 | .exitCode 171 | } 172 | } 173 | ``` 174 | 175 | ## Run Service 176 | 177 | - for sbt: `sbt "example/run"` 178 | 179 | 180 | ## Client Example 181 | 182 | You can also generate a client using smithy4s. 183 | 184 | ```scala mdoc:compile-only 185 | import example.hello._ 186 | import smithy4s.zio.http._ 187 | import zio.http.{Client, URL} 188 | import zio.{Scope, ZIO, ZIOAppArgs, ZIOAppDefault} 189 | 190 | object ClientImpl extends ZIOAppDefault { 191 | 192 | private val helloWorldClient: ZIO[Client, Throwable, HelloWorldService[ResourcefulTask]] = { 193 | for { 194 | url <- ZIO.fromEither(URL.decode("http://localhost:9000")) 195 | client <- ZIO.service[Client] 196 | helloClient <- SimpleRestJsonBuilder(HelloWorldService) 197 | .client(client) 198 | .url(url) 199 | .lift 200 | } yield helloClient 201 | } 202 | 203 | val program = helloWorldClient.flatMap(c => 204 | c.hello("Sam", Some("New York City")) 205 | .flatMap(greeting => zio.Console.printLine(greeting.message)) 206 | ) 207 | 208 | override def run: ZIO[Any & ZIOAppArgs & Scope, Any, Any] = 209 | program.exitCode.provide(Client.default, Scope.default) 210 | 211 | } 212 | ``` 213 | 214 | --------------------------------------------------------------------------------