Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a simple SchemaDescription visitor #295

Merged
merged 13 commits into from
Jul 11, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import smithy4s.http.internals.MetaDecode.{
StructureMetaDecode
}
import smithy4s.schema._
import smithy4s.internals.SchemaDescription

import java.{util => ju}
import scala.collection.mutable.{Map => MMap}
Expand All @@ -44,25 +45,28 @@ private[http] class SchemaVisitorMetadataReader()
hints: Hints,
tag: Primitive[P]
): MetaDecode[P] = {
val desc = tag.schema(shapeId).compile(SchemaDescription)
def withDesc[A](f: String => Option[A]) =
MetaDecode.from[A](desc)(f)
def withDescUnsafe[A](f: String => A) =
MetaDecode.fromUnsafe[A](desc)(f)
tag match {
case Primitive.PShort => MetaDecode.from("Short")(_.toShortOption)
case Primitive.PInt => MetaDecode.from("Int")(_.toIntOption)
case Primitive.PFloat => MetaDecode.from("Float")(_.toFloatOption)
case Primitive.PLong => MetaDecode.from("Long")(_.toLongOption)
case Primitive.PDouble => MetaDecode.from("Double")(_.toDoubleOption)
case Primitive.PBigInt => MetaDecode.fromUnsafe("BigInt")(BigInt(_))
case Primitive.PBigDecimal =>
MetaDecode.fromUnsafe("BigDecimal")(BigDecimal(_))
case Primitive.PBoolean => MetaDecode.from("Boolean")(_.toBooleanOption)
case Primitive.PString => MetaDecode.fromUnsafe("String")(identity)
case Primitive.PUUID =>
MetaDecode.fromUnsafe[ju.UUID]("UUID")(ju.UUID.fromString)
case Primitive.PByte => EmptyMetaDecode
case Primitive.PShort => withDesc(_.toShortOption)
case Primitive.PInt => withDesc(_.toIntOption)
case Primitive.PFloat => withDesc(_.toFloatOption)
case Primitive.PLong => withDesc(_.toLongOption)
case Primitive.PDouble => withDesc(_.toDoubleOption)
case Primitive.PBoolean => withDesc(_.toBooleanOption)
case Primitive.PBigInt => withDescUnsafe(BigInt(_))
case Primitive.PBigDecimal => withDescUnsafe(BigDecimal(_))
case Primitive.PString => withDescUnsafe(identity)
case Primitive.PUUID => withDescUnsafe(ju.UUID.fromString)
case Primitive.PBlob =>
MetaDecode.fromUnsafe("Bytes")(string =>
withDescUnsafe(string =>
ByteArray(ju.Base64.getDecoder().decode(string))
)
case Primitive.PDocument => EmptyMetaDecode
case Primitive.PByte => EmptyMetaDecode
case Primitive.PTimestamp =>
(
hints.get(HttpBinding).map(_.tpe),
Expand Down
75 changes: 75 additions & 0 deletions modules/core/src/smithy4s/internals/SchemaDescription.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2021-2022 Disney Streaming
*
* Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://disneystreaming.github.io/TOST-1.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smithy4s
package internals

import smithy4s.schema.{
Primitive,
EnumValue,
SchemaField,
SchemaAlt,
Alt,
SchemaVisitor,
CollectionTag
}

object SchemaDescription extends SchemaVisitor[SchemaDescription] {
// format: off

def of[A](value: String): SchemaDescription[A] = value
override def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]): SchemaDescription[P] = {
val value = tag match {
case Primitive.PShort => "Short"
case Primitive.PInt => "Int"
case Primitive.PFloat => "Float"
case Primitive.PLong => "Long"
case Primitive.PDouble => "Double"
case Primitive.PBigInt => "BigInt"
case Primitive.PBigDecimal => "BigDecimal"
case Primitive.PBoolean => "Boolean"
case Primitive.PString => "String"
case Primitive.PUUID => "UUID"
case Primitive.PByte => "Byte"
case Primitive.PBlob => "Bytes"
case Primitive.PDocument => "Document"
case Primitive.PTimestamp => "Timestamp"
case Primitive.PUnit => "Unit"
}
SchemaDescription.of(value)
}
override def collection[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C], member: Schema[A]): SchemaDescription[C[A]] =
SchemaDescription.of(tag.name)
override def map[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]): SchemaDescription[Map[K,V]] =
SchemaDescription.of("Map")

override def enumeration[E](shapeId: ShapeId, hints: Hints, values: List[EnumValue[E]], total: E => EnumValue[E]): SchemaDescription[E] =
SchemaDescription.of("Enumeration")

override def struct[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S): SchemaDescription[S] =
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
SchemaDescription.of("Structure")

override def union[U](shapeId: ShapeId, hints: Hints, alternatives: Vector[SchemaAlt[U, _]], dispatch: U => Alt.SchemaAndValue[U, _]): SchemaDescription[U] =
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
SchemaDescription.of("Union")

override def biject[A, B](schema: Schema[A], to: A => B, from: B => A): SchemaDescription[B] =
SchemaDescription.of(apply(schema))
override def surject[A, B](schema: Schema[A], to: Refinement[A,B], from: B => A): SchemaDescription[B] =
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
SchemaDescription.of(apply(schema))
override def lazily[A](suspend: Lazy[Schema[A]]): SchemaDescription[A] =
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
suspend.map(s => SchemaDescription.of(apply(s))).value
// format: on
}
170 changes: 170 additions & 0 deletions modules/core/src/smithy4s/internals/SchemaDescriptionDetailed.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2021-2022 Disney Streaming
*
* Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://disneystreaming.github.io/TOST-1.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smithy4s
package internals

import smithy4s.schema.{
Primitive,
EnumValue,
SchemaField,
SchemaAlt,
Alt,
SchemaVisitor,
CollectionTag
}

private[internals] trait SchemaDescriptionDetailedImpl[A]
extends (Set[ShapeId] => (Set[ShapeId], String)) {
def mapResult[B](f: String => String): SchemaDescriptionDetailedImpl[B] = {
seen =>
val (s1, desc) = apply(seen)
(s1 ++ seen, f(desc))
}
def flatMapResult[B](
f: String => SchemaDescriptionDetailedImpl[B]
): SchemaDescriptionDetailedImpl[B] = { seen =>
val (s1, desc) = apply(seen)
f(desc)(s1 ++ seen)
}
}

private[internals] object SchemaDescriptionDetailedImpl
extends SchemaVisitor[SchemaDescriptionDetailedImpl] { self =>

def of[A](shapeId: ShapeId, value: String): SchemaDescriptionDetailedImpl[A] =
s => (s + shapeId, value)

override def primitive[P](
shapeId: ShapeId,
hints: Hints,
tag: Primitive[P]
): SchemaDescriptionDetailedImpl[P] = {
SchemaDescriptionDetailedImpl.of(
shapeId,
SchemaDescription.apply(tag.schema(shapeId))
)
}
override def collection[C[_], A](
shapeId: ShapeId,
hints: Hints,
tag: CollectionTag[C],
member: Schema[A]
): SchemaDescriptionDetailedImpl[C[A]] = {
apply(member).mapResult(s => s"${tag.name}[$s]")
}
override def map[K, V](
shapeId: ShapeId,
hints: Hints,
key: Schema[K],
value: Schema[V]
): SchemaDescriptionDetailedImpl[Map[K, V]] = {
apply(key).flatMapResult { kDesc =>
apply(value).mapResult { vDesc =>
s"Map[$kDesc, $vDesc]"
}
}
}
override def enumeration[E](
shapeId: ShapeId,
hints: Hints,
values: List[EnumValue[E]],
total: E => EnumValue[E]
): SchemaDescriptionDetailedImpl[E] = {
val vDesc = values.map(e => e.stringValue).mkString(", ")
SchemaDescriptionDetailedImpl.of(shapeId, s"Enumeration{ $vDesc }")
}

override def struct[S](
shapeId: ShapeId,
hints: Hints,
fields: Vector[SchemaField[S, _]],
make: IndexedSeq[Any] => S
): SchemaDescriptionDetailedImpl[S] = { seen =>
def forField[T](sf: SchemaField[S, T]): (String, (Set[ShapeId], String)) = {
apply(sf.instance)(seen)
sf.label -> apply(sf.instance)(seen)
}
val (sFinal, res) = fields
.foldLeft((Set.empty[ShapeId], Seq.empty[(String, String)])) {
case ((shapes, fieldDesc), field) =>
val (label, (s2, desc)) = forField(field)
(shapes ++ s2, fieldDesc :+ (label -> desc))
}
val fieldDesc =
res.map { case (label, desc) => s"$label: $desc" }.mkString(", ")
sFinal -> s"Structure ${shapeId.name}{ $fieldDesc }"
}

override def union[U](
shapeId: ShapeId,
hints: Hints,
alternatives: Vector[SchemaAlt[U, _]],
dispatch: U => Alt.SchemaAndValue[U, _]
): SchemaDescriptionDetailedImpl[U] = { seen =>
def forAlt[T](alt: SchemaAlt[U, T]): (String, (Set[ShapeId], String)) = {
val desc = apply(alt.instance)(seen)
alt.label -> desc
}
val (sFinal, res) = alternatives
.foldLeft((Set.empty[ShapeId], Seq.empty[(String, String)])) {
case ((shapes, fieldDesc), alt) =>
val (label, (s2, desc)) = forAlt(alt)
(shapes ++ s2, fieldDesc :+ (label -> desc))
}
val fieldDesc =
res.map { case (label, desc) => s"$label: $desc" }.mkString(", ")
sFinal -> s"Union{ $fieldDesc }"
}

override def biject[A, B](
schema: Schema[A],
to: A => B,
from: B => A
): SchemaDescriptionDetailedImpl[B] = {
apply(schema).mapResult { desc => s"Bijection{ $desc }" }
}

override def surject[A, B](
schema: Schema[A],
to: Refinement[A, B],
from: B => A
): SchemaDescriptionDetailedImpl[B] = {
apply(schema).mapResult { desc => s"Surjection{ $desc }" }
}
override def lazily[A](
suspend: Lazy[Schema[A]]
): SchemaDescriptionDetailedImpl[A] = {
new SchemaDescriptionDetailedImpl[A] {
val rec = suspend.map(s => s -> self.apply(s))
override def apply(seen: Set[ShapeId]): (Set[ShapeId], String) = {
val (schema, f) = rec.value
if (seen(schema.shapeId)) {
seen -> s"Recursive{ ${schema.shapeId.name} }"
} else {
f(seen + schema.shapeId)
}
}
}
}

val conversion: SchemaDescriptionDetailedImpl ~> SchemaDescription =
new PolyFunction[SchemaDescriptionDetailedImpl, SchemaDescription] {
def apply[A](
fa: SchemaDescriptionDetailedImpl[A]
): SchemaDescription[A] = fa(Set.empty)._2
}
}
25 changes: 25 additions & 0 deletions modules/core/src/smithy4s/internals/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2021-2022 Disney Streaming
*
* Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://disneystreaming.github.io/TOST-1.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smithy4s

import smithy4s.schema.SchemaVisitor

package object internals {
type SchemaDescription[A] = String
val SchemaDescriptionDetailed: SchemaVisitor[SchemaDescription] =
SchemaDescriptionDetailedImpl.mapK(SchemaDescriptionDetailedImpl.conversion)
}
25 changes: 24 additions & 1 deletion modules/core/src/smithy4s/schema/SchemaVisitor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package schema
import Schema._

// format: off
trait SchemaVisitor[F[_]] extends (Schema ~> F) {
trait SchemaVisitor[F[_]] extends (Schema ~> F) { self =>
def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]) : F[P]
def collection[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C], member: Schema[A]): F[C[A]]
def map[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]): F[Map[K, V]]
Expand All @@ -42,4 +42,27 @@ trait SchemaVisitor[F[_]] extends (Schema ~> F) {
case SurjectionSchema(schema, to, from) => surject(schema, to, from)
case LazySchema(make) => lazily(make)
}

def mapK[G[_]](f: F ~> G): SchemaVisitor[G] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove. Schema ~> F already has andThen which achieves exactly the same thing.

https://github.com/disneystreaming/smithy4s/blob/main/modules/core/src/smithy4s/PolyFunction.scala#L28-L31

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 73ec53e

new SchemaVisitor[G] {
def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]) : G[P] =
f(self.primitive(shapeId, hints, tag))
def collection[C[_], A](shapeId: ShapeId, hints: Hints, tag: CollectionTag[C], member: Schema[A]): G[C[A]] =
f(self.collection(shapeId, hints, tag, member))
def map[K, V](shapeId: ShapeId, hints: Hints, key: Schema[K], value: Schema[V]): G[Map[K, V]] =
f(self.map(shapeId, hints, key, value))
def enumeration[E](shapeId: ShapeId, hints: Hints, values: List[EnumValue[E]], total: E => EnumValue[E]) : G[E] =
f(self.enumeration(shapeId, hints, values, total))
def struct[S](shapeId: ShapeId, hints: Hints, fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S) : G[S] =
f(self.struct(shapeId, hints, fields, make))
def union[U](shapeId: ShapeId, hints: Hints, alternatives: Vector[SchemaAlt[U, _]], dispatch: U => Alt.SchemaAndValue[U, _]) : G[U] =
f(self.union(shapeId, hints, alternatives, dispatch))
def biject[A, B](schema: Schema[A], to: A => B, from: B => A) : G[B] =
f(self.biject(schema, to, from))
def surject[A, B](schema: Schema[A], to: Refinement[A, B], from: B => A) : G[B] =
f(self.surject(schema, to, from))
def lazily[A](suspend: Lazy[Schema[A]]) : G[A] =
f(self.lazily(suspend))
}
}
}
Loading