Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package jsonrpclib.smithy4sinterop

import io.circe.{Decoder => CirceDecoder, _}
import smithy4s.codecs.PayloadPath
import smithy4s.schema.CachedSchemaCompiler
import smithy4s.Document
import smithy4s.Document.{Encoder => _, _}
import smithy4s.Schema

class CirceDecoderImpl extends CachedSchemaCompiler[CirceDecoder] {
val decoder: CachedSchemaCompiler.DerivingImpl[Decoder] = Document.Decoder

type Cache = decoder.Cache
def createCache(): Cache = decoder.createCache()

def fromSchema[A](schema: Schema[A], cache: Cache): CirceDecoder[A] =
c => {
c.as[Json]
.map(fromJson(_))
.flatMap { d =>
decoder
.fromSchema(schema, cache)
.decode(d)
.left
.map(e =>
DecodingFailure(DecodingFailure.Reason.CustomReason(e.getMessage), c.history ++ toCursorOps(e.path))
)
}
}

def fromSchema[A](schema: Schema[A]): CirceDecoder[A] = fromSchema(schema, createCache())

private def toCursorOps(path: PayloadPath): List[CursorOp] =
path.segments.map {
case PayloadPath.Segment.Label(name) => CursorOp.DownField(name)
case PayloadPath.Segment.Index(i) => CursorOp.DownN(i)
}

private def fromJson(json: Json): Document = json.fold(
jsonNull = DNull,
jsonBoolean = DBoolean(_),
jsonNumber = n => DNumber(n.toBigDecimal.get),
jsonString = DString(_),
jsonArray = arr => DArray(arr.map(fromJson)),
jsonObject = obj => DObject(obj.toMap.view.mapValues(fromJson).toMap)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package jsonrpclib.smithy4sinterop

import io.circe.{Encoder => CirceEncoder, _}
import smithy4s.schema.CachedSchemaCompiler
import smithy4s.Document
import smithy4s.Document._
import smithy4s.Document.Encoder
import smithy4s.Schema

class CirceEncoderImpl extends CachedSchemaCompiler[CirceEncoder] {
val encoder: CachedSchemaCompiler.DerivingImpl[Encoder] = Document.Encoder

type Cache = encoder.Cache
def createCache(): Cache = encoder.createCache()

def fromSchema[A](schema: Schema[A], cache: Cache): CirceEncoder[A] =
a => documentToJson(encoder.fromSchema(schema, cache).encode(a))

def fromSchema[A](schema: Schema[A]): CirceEncoder[A] = fromSchema(schema, createCache())

private val documentToJson: Document => Json = {
case DNull => Json.Null
case DString(value) => Json.fromString(value)
case DBoolean(value) => Json.fromBoolean(value)
case DNumber(value) => Json.fromBigDecimal(value)
case DArray(values) => Json.fromValues(values.map(documentToJson))
case DObject(entries) => Json.fromFields(entries.view.mapValues(documentToJson))
}
}
Original file line number Diff line number Diff line change
@@ -1,55 +1,18 @@
package jsonrpclib.smithy4sinterop

import io.circe._
import smithy4s.codecs.PayloadPath
import smithy4s.Document
import smithy4s.Document.{Decoder => _, _}
import smithy4s.Schema

object CirceJsonCodec {

object Encoder extends CirceEncoderImpl
object Decoder extends CirceDecoderImpl

/** Creates a Circe `Codec[A]` from a Smithy4s `Schema[A]`.
*
* This enables encoding values of type `A` to JSON and decoding JSON back into `A`, using the structure defined by
* the Smithy schema.
*/
def fromSchema[A](implicit schema: Schema[A]): Codec[A] = Codec.from(
c => {
c.as[Json]
.map(fromJson)
.flatMap { d =>
Document
.decode[A](d)
.left
.map(e =>
DecodingFailure(DecodingFailure.Reason.CustomReason(e.getMessage), c.history ++ toCursorOps(e.path))
)
}
},
a => documentToJson(Document.encode(a))
)

private def toCursorOps(path: PayloadPath): List[CursorOp] =
path.segments.map {
case PayloadPath.Segment.Label(name) => CursorOp.DownField(name)
case PayloadPath.Segment.Index(i) => CursorOp.DownN(i)
}

private val documentToJson: Document => Json = {
case DNull => Json.Null
case DString(value) => Json.fromString(value)
case DBoolean(value) => Json.fromBoolean(value)
case DNumber(value) => Json.fromBigDecimal(value)
case DArray(values) => Json.fromValues(values.map(documentToJson))
case DObject(entries) => Json.fromFields(entries.view.mapValues(documentToJson))
}

private def fromJson(json: Json): Document = json.fold(
jsonNull = DNull,
jsonBoolean = DBoolean(_),
jsonNumber = n => DNumber(n.toBigDecimal.get),
jsonString = DString(_),
jsonArray = arr => DArray(arr.map(fromJson)),
jsonObject = obj => DObject(obj.toMap.view.mapValues(fromJson).toMap)
)
def fromSchema[A](implicit schema: Schema[A]): Codec[A] =
Codec.from(Decoder.fromSchema(schema), Encoder.fromSchema(schema))
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,17 @@ private class ClientStub[Alg[_[_, _, _, _, _]], F[_]: Monadic](val service: Serv
smithy4sEndpoint: service.Endpoint[I, E, O, SI, SO],
endpointSpec: EndpointSpec
): I => F[O] = {
val decoderCache = CirceJsonCodec.Decoder.createCache()
val encoderCache = CirceJsonCodec.Encoder.createCache()

implicit val inputCodec: Codec[I] = CirceJsonCodec.fromSchema(smithy4sEndpoint.input)
implicit val outputCodec: Codec[O] = CirceJsonCodec.fromSchema(smithy4sEndpoint.output)
implicit val inputCodec: Codec[I] = Codec.from(
CirceJsonCodec.Decoder.fromSchema(smithy4sEndpoint.input, decoderCache),
CirceJsonCodec.Encoder.fromSchema(smithy4sEndpoint.input, encoderCache)
)
implicit val outputCodec: Codec[O] = Codec.from(
CirceJsonCodec.Decoder.fromSchema(smithy4sEndpoint.output, decoderCache),
CirceJsonCodec.Encoder.fromSchema(smithy4sEndpoint.output, encoderCache)
)

endpointSpec match {
case EndpointSpec.Notification(methodName) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,17 @@ object ServerEndpoints {
endpointSpec: EndpointSpec,
impl: FunctorInterpreter[Op, F]
): Endpoint[F] = {
val decoderCache = CirceJsonCodec.Decoder.createCache()
val encoderCache = CirceJsonCodec.Encoder.createCache()

implicit val inputCodec: Codec[I] = CirceJsonCodec.fromSchema(smithy4sEndpoint.input)
implicit val outputCodec: Codec[O] = CirceJsonCodec.fromSchema(smithy4sEndpoint.output)
implicit val inputCodec: Codec[I] = Codec.from(
CirceJsonCodec.Decoder.fromSchema(smithy4sEndpoint.input, decoderCache),
CirceJsonCodec.Encoder.fromSchema(smithy4sEndpoint.input, encoderCache)
)
implicit val outputCodec: Codec[O] = Codec.from(
CirceJsonCodec.Decoder.fromSchema(smithy4sEndpoint.output, decoderCache),
CirceJsonCodec.Encoder.fromSchema(smithy4sEndpoint.output, encoderCache)
)

def errorResponse(throwable: Throwable): F[E] = throwable match {
case smithy4sEndpoint.Error((_, e)) => e.pure
Expand Down