From 24011ca3fe286fedbbf80398bbfd387f8a1a02be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sat, 13 Aug 2022 21:46:36 +0200 Subject: [PATCH 1/7] Support longs, shorts, bytes --- .../scala/playground/NodeEncoderVisitor.scala | 22 +++++++--- .../main/scala/playground/QueryCompiler.scala | 44 ++++++++++++++----- .../main/scala/playground/smithyql/AST.scala | 2 +- .../main/scala/playground/smithyql/DSL.scala | 3 +- .../scala/playground/smithyql/Parser.scala | 4 +- .../smithyql/CompilationTests.scala | 16 +++++++ 6 files changed, 69 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/playground/NodeEncoderVisitor.scala b/core/src/main/scala/playground/NodeEncoderVisitor.scala index 9400f067..a82b4ba2 100644 --- a/core/src/main/scala/playground/NodeEncoderVisitor.scala +++ b/core/src/main/scala/playground/NodeEncoderVisitor.scala @@ -87,19 +87,19 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]): NodeEncoder[P] = tag match { case PInt => int - case PShort => unsupported("short") - case PLong => int.contramap(_.toInt) // todo: wraps + case PShort => short + case PLong => long case PString => string case PBigInt => unsupported("bigint") case PBoolean => boolean case PBigDecimal => bigdecimal case PBlob => string.contramap(_.toString) // todo this only works for UTF-8 text - case PDouble => int.contramap(_.toInt) // todo: wraps + case PDouble => long.contramap(_.toLong) // todo: wraps decimals case PDocument => document case PFloat => unsupported("float") case PUnit => struct(shapeId, hints, Vector.empty, _ => ()) case PUUID => string.contramap(_.toString()) - case PByte => unsupported("byte") + case PByte => byte case PTimestamp => string.contramap(_.toString) } @@ -209,7 +209,11 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => v => throw new Exception(s"Unsupported operation: $tag for value $v") val bigdecimal: NodeEncoder[BigDecimal] = unsupported("bigdecimal") - val int: NodeEncoder[Int] = IntLiteral(_) + + val long: NodeEncoder[Long] = IntLiteral(_) + val int: NodeEncoder[Int] = long.contramap(_.toLong) + val short: NodeEncoder[Short] = long.contramap(_.toLong) + val byte: NodeEncoder[Byte] = long.contramap(_.toLong) val string: NodeEncoder[String] = StringLiteral(_) @@ -229,8 +233,14 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => case DArray(value) => document.listed.toNode(value.toList) case DBoolean(value) => boolean.toNode(value) case DNumber(value) => - if (value.isValidInt) + if (value.isValidByte) + byte.toNode(value.toByte) + else if (value.isValidShort) + short.toNode(value.toShort) + else if (value.isValidInt) int.toNode(value.toInt) + else if (value.isValidLong) + long.toNode(value.toLong) else // todo other numbers bigdecimal.toNode(value) diff --git a/core/src/main/scala/playground/QueryCompiler.scala b/core/src/main/scala/playground/QueryCompiler.scala index 45f6e533..32c80391 100644 --- a/core/src/main/scala/playground/QueryCompiler.scala +++ b/core/src/main/scala/playground/QueryCompiler.scala @@ -53,7 +53,6 @@ trait PartialCompiler[A] { final def emap[B](f: A => PartialCompiler.Result[B]): PartialCompiler[B] = ast => compile(ast).flatMap(f) - // TODO: Actually use the powers of Ior. Maybe a custom monad for errors / warnings? Diagnosed[A]? Either+Writer composition? def compile(ast: WAST): PartialCompiler.Result[A] } @@ -163,6 +162,7 @@ sealed trait CompilationErrorDetails extends Product with Serializable { case Message(text) => text case DeprecatedItem(info) => "Deprecated" + CompletionItem.deprecationString(info) case InvalidUUID => "Invalid UUID" + case NumberOutOfRange(value, expectedType) => s"Number out of range for $expectedType: $value" case EnumFallback(enumName) => s"""Matching enums by value is deprecated and may be removed in the future. Use $enumName instead.""".stripMargin case DuplicateItem => "Duplicate item - some entries will be dropped to fit in a set shape." @@ -250,6 +250,9 @@ object CompilationErrorDetails { case object InvalidUUID extends CompilationErrorDetails + final case class NumberOutOfRange(numberValue: String, typeName: String) + extends CompilationErrorDetails + final case class InvalidTimestampFormat(expected: TimestampFormat) extends CompilationErrorDetails final case class MissingDiscriminator(possibleValues: NonEmptyList[String]) @@ -282,6 +285,22 @@ object CompilationErrorDetails { object QueryCompiler extends SchemaVisitor[PartialCompiler] { + private def checkRange[A, B]( + pc: PartialCompiler[A] + )( + tag: String + )( + matchToRange: PartialFunction[A, B] + ) = (pc, PartialCompiler.pos).tupled.emap { case (i, range) => + matchToRange + .lift(i) + .toRightIor( + CompilationError + .error(NumberOutOfRange(i.toString, tag), range) + ) + .toIorNec + } + def primitive[P](shapeId: ShapeId, hints: Hints, tag: Primitive[P]): PartialCompiler[P] = tag match { case PString => string @@ -289,15 +308,14 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { PartialCompiler .typeCheck(NodeKind.Bool) { case b @ BooleanLiteral(_) => b } .map(_.value.value) - case PUnit => struct(shapeId, hints, Vector.empty, _ => ()) - case PInt => - PartialCompiler - .typeCheck(NodeKind.IntLiteral) { case i @ IntLiteral(_) => i } - .map(_.value.value) + case PUnit => struct(shapeId, hints, Vector.empty, _ => ()) + case PLong => integer + case PInt => checkRange(integer)("int") { case i if i.isValidInt => i.toInt } + case PShort => checkRange(integer)("short") { case i if i.isValidShort => i.toShort } + case PByte => checkRange(integer)("byte") { case i if i.isValidByte => i.toByte } + case PFloat => unsupported("float") case PDocument => document - case PShort => unsupported("short") case PBlob => unsupported("blob") - case PByte => unsupported("byte") case PBigDecimal => unsupported("bigDecimal") case PDouble => unsupported("double") case PBigInt => unsupported("bigint") @@ -310,8 +328,6 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { .toIorNec } - case PLong => unsupported("long") - case PFloat => unsupported("float") case PTimestamp => stringLiteral.emap { s => // We don't support other formats for the simple reason that it's not necessary: @@ -331,6 +347,10 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { } } + private val integer: PartialCompiler[Long] = PartialCompiler + .typeCheck(NodeKind.IntLiteral) { case i @ IntLiteral(_) => i } + .map(_.value.value) + def collection[C[_], A]( shapeId: ShapeId, hints: Hints, @@ -570,7 +590,7 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { it.value.compile(_) } - def unsupported[A](ctx: String): PartialCompiler[A] = + private def unsupported[A](ctx: String): PartialCompiler[A] = ast => Ior.leftNec( CompilationError.error( @@ -585,7 +605,7 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { val document: PartialCompiler[Document] = _.value match { case BooleanLiteral(value) => Document.fromBoolean(value).pure[PartialCompiler.Result] - case IntLiteral(value) => Document.fromInt(value).pure[PartialCompiler.Result] + case IntLiteral(value) => Document.fromLong(value).pure[PartialCompiler.Result] case StringLiteral(value) => Document.fromString(value).pure[PartialCompiler.Result] // parTraverse in this file isn't going to work like you think it will case Listed(values) => values.value.parTraverse(document.compile(_)).map(Document.array(_)) diff --git a/core/src/main/scala/playground/smithyql/AST.scala b/core/src/main/scala/playground/smithyql/AST.scala index cc3af0b3..1e849f9b 100644 --- a/core/src/main/scala/playground/smithyql/AST.scala +++ b/core/src/main/scala/playground/smithyql/AST.scala @@ -158,7 +158,7 @@ object Struct { } -final case class IntLiteral[F[_]](value: Int) extends InputNode[F] { +final case class IntLiteral[F[_]](value: Long) extends InputNode[F] { def kind: NodeKind = NodeKind.IntLiteral def mapK[G[_]: Functor](fk: F ~> G): InputNode[G] = copy() } diff --git a/core/src/main/scala/playground/smithyql/DSL.scala b/core/src/main/scala/playground/smithyql/DSL.scala index f8beadc9..bfb97d16 100644 --- a/core/src/main/scala/playground/smithyql/DSL.scala +++ b/core/src/main/scala/playground/smithyql/DSL.scala @@ -33,7 +33,8 @@ object DSL { ): Struct[Id] = Struct[Id](Struct.Fields.fromSeq[Id](args.map(_.leftMap(Struct.Key(_))))) implicit def stringToAST(s: String): StringLiteral[Id] = StringLiteral[Id](s) - implicit def intToAST(i: Int): IntLiteral[Id] = IntLiteral[Id](i) + implicit def intToAST(i: Int): IntLiteral[Id] = IntLiteral[Id](i.toLong) + implicit def longToAAST(i: Long): IntLiteral[Id] = IntLiteral[Id](i) implicit def boolToAST(b: Boolean): BooleanLiteral[Id] = BooleanLiteral[Id](b) implicit def listToAST[A]( diff --git a/core/src/main/scala/playground/smithyql/Parser.scala b/core/src/main/scala/playground/smithyql/Parser.scala index 274b7460..e7d85075 100644 --- a/core/src/main/scala/playground/smithyql/Parser.scala +++ b/core/src/main/scala/playground/smithyql/Parser.scala @@ -97,9 +97,9 @@ object SmithyQLParser { (Rfc5234.alpha ~ Parser.charsWhile0(ch => ch.isLetterOrDigit || "_".contains(ch))) .map { case (ch, s) => s.prepended(ch) } - val number: Parser[Int] = Numbers + val number: Parser[Long] = Numbers .signedIntString - .map(_.toInt) + .map(_.toLong) val bool: Parser[Boolean] = string("true").as(true).orElse(string("false").as(false)) diff --git a/core/src/test/scala/playground/smithyql/CompilationTests.scala b/core/src/test/scala/playground/smithyql/CompilationTests.scala index 71c0ae2f..d0606c5d 100644 --- a/core/src/test/scala/playground/smithyql/CompilationTests.scala +++ b/core/src/test/scala/playground/smithyql/CompilationTests.scala @@ -141,6 +141,22 @@ object CompilationTests extends SimpleIOSuite with Checkers { ) } + pureTest("short") { + assert( + compile { + WithSource.liftId(42.mapK(WithSource.liftId)) + }(Schema.short) == Ior.right(42.toShort) + ) + } + + pureTest("short - out of range") { + assert( + compile { + WithSource.liftId((Short.MaxValue + 1).mapK(WithSource.liftId)) + }(Schema.short).isLeft + ) + } + pureTest("boolean") { assert( compile { From eae5f3f18af2f65fc34e25cc624da46d1ca24105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sat, 13 Aug 2022 22:14:09 +0200 Subject: [PATCH 2/7] Support everything in compiler --- .../scala/playground/NodeEncoderVisitor.scala | 11 +- .../main/scala/playground/QueryCompiler.scala | 66 +++++---- .../main/scala/playground/smithyql/AST.scala | 2 +- .../main/scala/playground/smithyql/DSL.scala | 8 +- .../scala/playground/smithyql/Parser.scala | 4 +- .../playground/smithyql/Arbitraries.scala | 4 +- .../smithyql/CompilationTests.scala | 131 +++++++++++++++++- .../playground/smithyql/ParserTests.scala | 10 ++ 8 files changed, 191 insertions(+), 45 deletions(-) diff --git a/core/src/main/scala/playground/NodeEncoderVisitor.scala b/core/src/main/scala/playground/NodeEncoderVisitor.scala index a82b4ba2..fc1c48de 100644 --- a/core/src/main/scala/playground/NodeEncoderVisitor.scala +++ b/core/src/main/scala/playground/NodeEncoderVisitor.scala @@ -208,12 +208,13 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => def unsupported[A](tag: String): NodeEncoder[A] = v => throw new Exception(s"Unsupported operation: $tag for value $v") - val bigdecimal: NodeEncoder[BigDecimal] = unsupported("bigdecimal") + private val number: NodeEncoder[String] = IntLiteral(_) + val bigdecimal: NodeEncoder[BigDecimal] = number.contramap(_.toString) - val long: NodeEncoder[Long] = IntLiteral(_) - val int: NodeEncoder[Int] = long.contramap(_.toLong) - val short: NodeEncoder[Short] = long.contramap(_.toLong) - val byte: NodeEncoder[Byte] = long.contramap(_.toLong) + val long: NodeEncoder[Long] = number.contramap(_.toString) + val int: NodeEncoder[Int] = number.contramap(_.toString) + val short: NodeEncoder[Short] = number.contramap(_.toString) + val byte: NodeEncoder[Byte] = number.contramap(_.toString) val string: NodeEncoder[String] = StringLiteral(_) diff --git a/core/src/main/scala/playground/QueryCompiler.scala b/core/src/main/scala/playground/QueryCompiler.scala index 32c80391..6ef74479 100644 --- a/core/src/main/scala/playground/QueryCompiler.scala +++ b/core/src/main/scala/playground/QueryCompiler.scala @@ -48,6 +48,8 @@ import java.util.UUID import types._ import util.chaining._ import PartialCompiler.WAST +import smithy4s.ByteArray +import java.util.Base64 trait PartialCompiler[A] { final def emap[B](f: A => PartialCompiler.Result[B]): PartialCompiler[B] = @@ -162,6 +164,7 @@ sealed trait CompilationErrorDetails extends Product with Serializable { case Message(text) => text case DeprecatedItem(info) => "Deprecated" + CompletionItem.deprecationString(info) case InvalidUUID => "Invalid UUID" + case InvalidBlob => "Invalid blob, expected base64-encoded string" case NumberOutOfRange(value, expectedType) => s"Number out of range for $expectedType: $value" case EnumFallback(enumName) => s"""Matching enums by value is deprecated and may be removed in the future. Use $enumName instead.""".stripMargin @@ -181,8 +184,6 @@ sealed trait CompilationErrorDetails extends Product with Serializable { case TypeMismatch(expected, actual) => s"Type mismatch: expected $expected, got $actual." - case UnsupportedNode(tag) => s"Unsupported operation: $tag" - case OperationNotFound(name, validOperations) => s"Operation ${name.text} not found. Available operations: ${validOperations.map(_.text).mkString_(", ")}." @@ -274,10 +275,10 @@ object CompilationErrorDetails { final case class RefinementFailure(msg: String) extends CompilationErrorDetails - final case class UnsupportedNode(tag: String) extends CompilationErrorDetails - case object DuplicateItem extends CompilationErrorDetails + case object InvalidBlob extends CompilationErrorDetails + case class DeprecatedItem(info: api.Deprecated) extends CompilationErrorDetails final case class EnumFallback(enumName: String) extends CompilationErrorDetails @@ -290,10 +291,9 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { )( tag: String )( - matchToRange: PartialFunction[A, B] + matchToRange: A => Option[B] ) = (pc, PartialCompiler.pos).tupled.emap { case (i, range) => - matchToRange - .lift(i) + matchToRange(i) .toRightIor( CompilationError .error(NumberOutOfRange(i.toString, tag), range) @@ -308,17 +308,31 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { PartialCompiler .typeCheck(NodeKind.Bool) { case b @ BooleanLiteral(_) => b } .map(_.value.value) - case PUnit => struct(shapeId, hints, Vector.empty, _ => ()) - case PLong => integer - case PInt => checkRange(integer)("int") { case i if i.isValidInt => i.toInt } - case PShort => checkRange(integer)("short") { case i if i.isValidShort => i.toShort } - case PByte => checkRange(integer)("byte") { case i if i.isValidByte => i.toByte } - case PFloat => unsupported("float") - case PDocument => document - case PBlob => unsupported("blob") - case PBigDecimal => unsupported("bigDecimal") - case PDouble => unsupported("double") - case PBigInt => unsupported("bigint") + case PUnit => struct(shapeId, hints, Vector.empty, _ => ()) + case PLong => checkRange(integer)("int")(_.toLongOption) + case PInt => checkRange(integer)("int")(_.toIntOption) + case PShort => checkRange(integer)("short")(_.toShortOption) + case PByte => checkRange(integer)("byte")(_.toByteOption) + case PFloat => checkRange(integer)("float")(_.toFloatOption) + case PDouble => checkRange(integer)("double")(_.toDoubleOption) + case PDocument => document + case PBlob => + (string, PartialCompiler.pos).tupled.emap { case (s, range) => + Either + .catchNonFatal(Base64.getDecoder().decode(s)) + .map(ByteArray(_)) + .leftMap(_ => CompilationError.error(CompilationErrorDetails.InvalidBlob, range)) + .toIor + .toIorNec + } + case PBigDecimal => + checkRange(integer)("bigdecimal") { s => + Either.catchNonFatal(BigDecimal(s)).toOption + } + case PBigInt => + checkRange(integer)("bigint") { s => + Either.catchNonFatal(BigInt(s)).toOption + } case PUUID => stringLiteral.emap { s => Either @@ -347,7 +361,7 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { } } - private val integer: PartialCompiler[Long] = PartialCompiler + private val integer: PartialCompiler[String] = PartialCompiler .typeCheck(NodeKind.IntLiteral) { case i @ IntLiteral(_) => i } .map(_.value.value) @@ -590,23 +604,15 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { it.value.compile(_) } - private def unsupported[A](ctx: String): PartialCompiler[A] = - ast => - Ior.leftNec( - CompilationError.error( - UnsupportedNode(ctx), - ast.range, - ) - ) - val stringLiteral = PartialCompiler.typeCheck(NodeKind.StringLiteral) { case StringLiteral(s) => s } val document: PartialCompiler[Document] = _.value match { case BooleanLiteral(value) => Document.fromBoolean(value).pure[PartialCompiler.Result] - case IntLiteral(value) => Document.fromLong(value).pure[PartialCompiler.Result] - case StringLiteral(value) => Document.fromString(value).pure[PartialCompiler.Result] + case IntLiteral(value) => + Document.fromBigDecimal(BigDecimal(value)).pure[PartialCompiler.Result] + case StringLiteral(value) => Document.fromString(value).pure[PartialCompiler.Result] // parTraverse in this file isn't going to work like you think it will case Listed(values) => values.value.parTraverse(document.compile(_)).map(Document.array(_)) case Struct(fields) => diff --git a/core/src/main/scala/playground/smithyql/AST.scala b/core/src/main/scala/playground/smithyql/AST.scala index 1e849f9b..dece4efe 100644 --- a/core/src/main/scala/playground/smithyql/AST.scala +++ b/core/src/main/scala/playground/smithyql/AST.scala @@ -158,7 +158,7 @@ object Struct { } -final case class IntLiteral[F[_]](value: Long) extends InputNode[F] { +final case class IntLiteral[F[_]](value: String) extends InputNode[F] { def kind: NodeKind = NodeKind.IntLiteral def mapK[G[_]: Functor](fk: F ~> G): InputNode[G] = copy() } diff --git a/core/src/main/scala/playground/smithyql/DSL.scala b/core/src/main/scala/playground/smithyql/DSL.scala index bfb97d16..6d2a7de3 100644 --- a/core/src/main/scala/playground/smithyql/DSL.scala +++ b/core/src/main/scala/playground/smithyql/DSL.scala @@ -33,8 +33,12 @@ object DSL { ): Struct[Id] = Struct[Id](Struct.Fields.fromSeq[Id](args.map(_.leftMap(Struct.Key(_))))) implicit def stringToAST(s: String): StringLiteral[Id] = StringLiteral[Id](s) - implicit def intToAST(i: Int): IntLiteral[Id] = IntLiteral[Id](i.toLong) - implicit def longToAAST(i: Long): IntLiteral[Id] = IntLiteral[Id](i) + implicit def intToAST(i: Int): IntLiteral[Id] = IntLiteral[Id](i.toString) + implicit def longToAST(i: Long): IntLiteral[Id] = IntLiteral[Id](i.toString) + implicit def floatToAST(i: Float): IntLiteral[Id] = IntLiteral[Id](i.toString) + implicit def doubleToAST(i: Double): IntLiteral[Id] = IntLiteral[Id](i.toString) + implicit def bigIntToAST(i: BigInt): IntLiteral[Id] = IntLiteral[Id](i.toString) + implicit def bigDecimalToAST(i: BigDecimal): IntLiteral[Id] = IntLiteral[Id](i.toString) implicit def boolToAST(b: Boolean): BooleanLiteral[Id] = BooleanLiteral[Id](b) implicit def listToAST[A]( diff --git a/core/src/main/scala/playground/smithyql/Parser.scala b/core/src/main/scala/playground/smithyql/Parser.scala index e7d85075..ee0b65af 100644 --- a/core/src/main/scala/playground/smithyql/Parser.scala +++ b/core/src/main/scala/playground/smithyql/Parser.scala @@ -97,9 +97,7 @@ object SmithyQLParser { (Rfc5234.alpha ~ Parser.charsWhile0(ch => ch.isLetterOrDigit || "_".contains(ch))) .map { case (ch, s) => s.prepended(ch) } - val number: Parser[Long] = Numbers - .signedIntString - .map(_.toLong) + val number: Parser[String] = Numbers.jsonNumber val bool: Parser[Boolean] = string("true").as(true).orElse(string("false").as(false)) diff --git a/core/src/test/scala/playground/smithyql/Arbitraries.scala b/core/src/test/scala/playground/smithyql/Arbitraries.scala index 0a1df813..d42ad20e 100644 --- a/core/src/test/scala/playground/smithyql/Arbitraries.scala +++ b/core/src/test/scala/playground/smithyql/Arbitraries.scala @@ -41,7 +41,9 @@ object Arbitraries { } implicit val arbIntLiteral: Arbitrary[IntLiteral[WithSource]] = Arbitrary { - Gen.resultOf(IntLiteral.apply[WithSource]) + Arbitrary.arbBigDecimal.arbitrary.map { bd => + IntLiteral[WithSource](bd.toString()) + } } implicit val arbStringLiteral: Arbitrary[StringLiteral[WithSource]] = Arbitrary { diff --git a/core/src/test/scala/playground/smithyql/CompilationTests.scala b/core/src/test/scala/playground/smithyql/CompilationTests.scala index d0606c5d..bbbf9157 100644 --- a/core/src/test/scala/playground/smithyql/CompilationTests.scala +++ b/core/src/test/scala/playground/smithyql/CompilationTests.scala @@ -40,6 +40,7 @@ import weaver.scalacheck.Checkers import java.util.UUID import Arbitraries._ +import smithy4s.ByteArray object CompilationTests extends SimpleIOSuite with Checkers { @@ -133,6 +134,22 @@ object CompilationTests extends SimpleIOSuite with Checkers { ) } + pureTest("long") { + assert( + compile { + WithSource.liftId(Long.MaxValue.mapK(WithSource.liftId)) + }(Schema.long) == Ior.right(Long.MaxValue) + ) + } + + pureTest("long - out of range") { + assert( + compile { + WithSource.liftId((BigInt(Long.MaxValue) + 1).mapK(WithSource.liftId)) + }(Schema.long).isLeft + ) + } + pureTest("int") { assert( compile { @@ -141,6 +158,14 @@ object CompilationTests extends SimpleIOSuite with Checkers { ) } + pureTest("int - out of range") { + assert( + compile { + WithSource.liftId((Int.MaxValue.toLong + 1L).mapK(WithSource.liftId)) + }(Schema.int).isLeft + ) + } + pureTest("short") { assert( compile { @@ -157,6 +182,90 @@ object CompilationTests extends SimpleIOSuite with Checkers { ) } + pureTest("byte") { + assert( + compile { + WithSource.liftId(Byte.MaxValue.mapK(WithSource.liftId)) + }(Schema.byte) == Ior.right(127.toByte) + ) + } + + pureTest("byte - out of range") { + assert( + compile { + WithSource.liftId((Byte.MaxValue + 1).mapK(WithSource.liftId)) + }(Schema.byte).isLeft + ) + } + + pureTest("float") { + assert( + compile { + WithSource.liftId(Float.MaxValue.mapK(WithSource.liftId)) + }(Schema.float) == Ior.right(Float.MaxValue) + ) + } + + pureTest("float - out of range") { + assert( + compile { + WithSource.liftId(Double.MaxValue.toString.mapK(WithSource.liftId)) + }(Schema.float).isLeft + ) + } + + pureTest("double") { + assert( + compile { + WithSource.liftId(Double.MaxValue.mapK(WithSource.liftId)) + }(Schema.double) == Ior.right(Double.MaxValue) + ) + } + + pureTest("double - out of range") { + assert( + compile { + WithSource.liftId((BigDecimal(Double.MaxValue) + 1).mapK(WithSource.liftId)) + }(Schema.double) == Ior.right(Double.MaxValue) + ) + } + + test("bigint - OK") { + forall { (bi: BigInt) => + assert( + compile { + WithSource.liftId(bi.mapK(WithSource.liftId)) + }(Schema.bigint) == Ior.right(bi) + ) + } + } + + pureTest("bigint - not accepting floats") { + assert( + compile { + WithSource.liftId("40.50".mapK(WithSource.liftId)) + }(Schema.bigint).isLeft + ) + } + + test("bigdecimal - OK") { + forall { (bd: BigDecimal) => + assert( + compile { + WithSource.liftId(bd.mapK(WithSource.liftId)) + }(Schema.bigdecimal) == Ior.right(bd) + ) + } + } + + pureTest("bigdecimal - not a number") { + assert( + compile { + WithSource.liftId("AAAA".mapK(WithSource.liftId)) + }(Schema.bigdecimal).isLeft + ) + } + pureTest("boolean") { assert( compile { @@ -165,6 +274,22 @@ object CompilationTests extends SimpleIOSuite with Checkers { ) } + pureTest("blob") { + assert( + compile { + WithSource.liftId("dGVzdA==".mapK(WithSource.liftId)) + }(Schema.bytes) == Ior.right(ByteArray("test".getBytes())) + ) + } + + pureTest("blob - invalid") { + assert( + compile { + WithSource.liftId("XYI519274n91lasdf/a'\'...,,".mapK(WithSource.liftId)) + }(Schema.bytes).isLeft + ) + } + pureTest("Simple struct") { assert( compile[Good] { @@ -402,9 +527,9 @@ object CompilationTests extends SimpleIOSuite with Checkers { Listed[WithSource]( WithSource.liftId( List( - WithSource.liftId(IntLiteral[WithSource](1)).withRange(range1), - WithSource.liftId(IntLiteral[WithSource](2)).withRange(range2), - WithSource.liftId(IntLiteral[WithSource](2)).withRange(range3), + WithSource.liftId(IntLiteral[WithSource]("1")).withRange(range1), + WithSource.liftId(IntLiteral[WithSource]("2")).withRange(range2), + WithSource.liftId(IntLiteral[WithSource]("2")).withRange(range3), ) ) ) diff --git a/core/src/test/scala/playground/smithyql/ParserTests.scala b/core/src/test/scala/playground/smithyql/ParserTests.scala index 0f3cf0f0..cd638765 100644 --- a/core/src/test/scala/playground/smithyql/ParserTests.scala +++ b/core/src/test/scala/playground/smithyql/ParserTests.scala @@ -29,6 +29,16 @@ object ParserTests extends FunSuite { parsingTest("simple call, sparse", " hello { } ")("hello".call()) parsingTest("simple call, sparse with underscore", " hello_world { } ")("hello_world".call()) + parsingTest("simple call with float", "hello { a = 21.37 }")("hello".call("a" -> 21.37f)) + parsingTest( + "simple call with double", + s"hello { a = ${Double.MaxValue.toString} }", + )( + "hello".call( + "a" -> Double.MaxValue + ) + ) + parsingTest("use service", "use service com.example#Demo hello {}")( "hello".call().useService("com", "example")("Demo") ) From ddf3f57e742d01d4db4b9f7a585c0d46abf93873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sat, 13 Aug 2022 22:23:06 +0200 Subject: [PATCH 3/7] Support encoding all nodes --- .../scala/playground/NodeEncoderVisitor.scala | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/core/src/main/scala/playground/NodeEncoderVisitor.scala b/core/src/main/scala/playground/NodeEncoderVisitor.scala index fc1c48de..45892723 100644 --- a/core/src/main/scala/playground/NodeEncoderVisitor.scala +++ b/core/src/main/scala/playground/NodeEncoderVisitor.scala @@ -20,7 +20,13 @@ import smithy4s.Hints import smithy4s.Lazy import smithy4s.Refinement import smithy4s.ShapeId +import smithy4s.capability.EncoderK import smithy4s.schema.Alt +import smithy4s.schema.CollectionTag +import smithy4s.schema.CollectionTag.IndexedSeqTag +import smithy4s.schema.CollectionTag.ListTag +import smithy4s.schema.CollectionTag.SetTag +import smithy4s.schema.CollectionTag.VectorTag import smithy4s.schema.EnumValue import smithy4s.schema.Field import smithy4s.schema.Primitive @@ -42,12 +48,6 @@ import smithy4s.schema.Primitive.PUnit import smithy4s.schema.Schema import smithy4s.schema.SchemaField import smithy4s.schema.SchemaVisitor -import smithy4s.schema.CollectionTag -import smithy4s.capability.EncoderK -import smithy4s.schema.CollectionTag.IndexedSeqTag -import smithy4s.schema.CollectionTag.ListTag -import smithy4s.schema.CollectionTag.SetTag -import smithy4s.schema.CollectionTag.VectorTag trait NodeEncoder[A] { def toNode(a: A): InputNode[Id] @@ -90,14 +90,14 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => case PShort => short case PLong => long case PString => string - case PBigInt => unsupported("bigint") + case PBigInt => bigint case PBoolean => boolean case PBigDecimal => bigdecimal case PBlob => string.contramap(_.toString) // todo this only works for UTF-8 text case PDouble => long.contramap(_.toLong) // todo: wraps decimals case PDocument => document - case PFloat => unsupported("float") - case PUnit => struct(shapeId, hints, Vector.empty, _ => ()) + case PFloat => float + case PUnit => _ => obj(Nil) case PUUID => string.contramap(_.toString()) case PByte => byte case PTimestamp => string.contramap(_.toString) @@ -205,16 +205,15 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => value => mapped.value.toNode(value) } - def unsupported[A](tag: String): NodeEncoder[A] = - v => throw new Exception(s"Unsupported operation: $tag for value $v") - private val number: NodeEncoder[String] = IntLiteral(_) val bigdecimal: NodeEncoder[BigDecimal] = number.contramap(_.toString) - + val bigint: NodeEncoder[BigInt] = number.contramap(_.toString) val long: NodeEncoder[Long] = number.contramap(_.toString) val int: NodeEncoder[Int] = number.contramap(_.toString) val short: NodeEncoder[Short] = number.contramap(_.toString) val byte: NodeEncoder[Byte] = number.contramap(_.toString) + val float: NodeEncoder[Float] = number.contramap(_.toString) + val double: NodeEncoder[Double] = number.contramap(_.toString) val string: NodeEncoder[String] = StringLiteral(_) @@ -233,23 +232,10 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => doc match { case DArray(value) => document.listed.toNode(value.toList) case DBoolean(value) => boolean.toNode(value) - case DNumber(value) => - if (value.isValidByte) - byte.toNode(value.toByte) - else if (value.isValidShort) - short.toNode(value.toShort) - else if (value.isValidInt) - int.toNode(value.toInt) - else if (value.isValidLong) - long.toNode(value.toLong) - else - // todo other numbers - bigdecimal.toNode(value) - case DNull => - // todo nul??? - unsupported[Null]("null").toNode(null) - case DString(value) => string.toNode(value) - case DObject(value) => obj(value.toList.map(_.map(document.toNode))) + case DNumber(value) => number.toNode(value.toString()) + case DNull => obj(List("null" -> obj(Nil))) + case DString(value) => string.toNode(value) + case DObject(value) => obj(value.toList.map(_.map(document.toNode))) } } From 53bd43541af32528b91cd2c427b0d0214dff7aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sat, 13 Aug 2022 22:26:09 +0200 Subject: [PATCH 4/7] Remove some wrapping, add some encoding tests --- .../scala/playground/NodeEncoderVisitor.scala | 5 +++-- .../test/scala/playground/NodeEncoderTests.scala | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/playground/NodeEncoderVisitor.scala b/core/src/main/scala/playground/NodeEncoderVisitor.scala index 45892723..9f208cdf 100644 --- a/core/src/main/scala/playground/NodeEncoderVisitor.scala +++ b/core/src/main/scala/playground/NodeEncoderVisitor.scala @@ -48,6 +48,7 @@ import smithy4s.schema.Primitive.PUnit import smithy4s.schema.Schema import smithy4s.schema.SchemaField import smithy4s.schema.SchemaVisitor +import smithy4s.ByteArray trait NodeEncoder[A] { def toNode(a: A): InputNode[Id] @@ -93,8 +94,8 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => case PBigInt => bigint case PBoolean => boolean case PBigDecimal => bigdecimal - case PBlob => string.contramap(_.toString) // todo this only works for UTF-8 text - case PDouble => long.contramap(_.toLong) // todo: wraps decimals + case PBlob => string.contramap((_: ByteArray).toString) + case PDouble => double case PDocument => document case PFloat => float case PUnit => _ => obj(Nil) diff --git a/core/src/test/scala/playground/NodeEncoderTests.scala b/core/src/test/scala/playground/NodeEncoderTests.scala index a914b51a..3d8497c6 100644 --- a/core/src/test/scala/playground/NodeEncoderTests.scala +++ b/core/src/test/scala/playground/NodeEncoderTests.scala @@ -10,6 +10,9 @@ import weaver._ import demo.smithy.Hero import smithy4s.Timestamp import smithy.api.TimestampFormat +import smithy4s.ByteArray +import java.util.Base64 +import playground.smithyql.StringLiteral object NodeEncoderTests extends FunSuite { @@ -37,6 +40,10 @@ object NodeEncoderTests extends FunSuite { assertEncodes(Schema.int, 42, 42) } + test("double") { + assertEncodes(Schema.double, 420.2137d, 420.2137d) + } + test("simple struct") { assertEncodes(Good.schema, Good(42), struct("howGood" -> 42)) } @@ -52,4 +59,12 @@ object NodeEncoderTests extends FunSuite { "2022-07-11T17:42:28Z", ) } + + test("blob") { + assertEncodes( + Schema.bytes, + ByteArray("foo".getBytes()), + StringLiteral("Zm9v"), + ) + } } From f4bbdf922cd0f8378c43e720fa50adfd1ecf2130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sat, 13 Aug 2022 22:27:00 +0200 Subject: [PATCH 5/7] remove unused --- core/src/main/scala/playground/run.scala | 1 - core/src/test/scala/playground/NodeEncoderTests.scala | 1 - 2 files changed, 2 deletions(-) diff --git a/core/src/main/scala/playground/run.scala b/core/src/main/scala/playground/run.scala index f8dc2b03..19d2ade9 100644 --- a/core/src/main/scala/playground/run.scala +++ b/core/src/main/scala/playground/run.scala @@ -86,7 +86,6 @@ object Compiler { dsi .allServices .map { svc => - // todo: deprecated services (here / in completions) QualifiedIdentifier .forService(svc.service) -> Compiler.fromService[svc.Alg, svc.Op](svc.service) } diff --git a/core/src/test/scala/playground/NodeEncoderTests.scala b/core/src/test/scala/playground/NodeEncoderTests.scala index 3d8497c6..aeefd7c8 100644 --- a/core/src/test/scala/playground/NodeEncoderTests.scala +++ b/core/src/test/scala/playground/NodeEncoderTests.scala @@ -11,7 +11,6 @@ import demo.smithy.Hero import smithy4s.Timestamp import smithy.api.TimestampFormat import smithy4s.ByteArray -import java.util.Base64 import playground.smithyql.StringLiteral object NodeEncoderTests extends FunSuite { From 9dde7d1d0945041b57acc5128c62f11309bbcd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sat, 13 Aug 2022 22:37:24 +0200 Subject: [PATCH 6/7] =?UTF-8?q?Add=20null=20=F0=9F=92=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scala/playground/DocumentSymbolProvider.scala | 1 + .../main/scala/playground/NodeEncoderVisitor.scala | 3 ++- core/src/main/scala/playground/QueryCompiler.scala | 1 + core/src/main/scala/playground/smithyql/AST.scala | 8 ++++++++ .../main/scala/playground/smithyql/Formatter.scala | 1 + core/src/main/scala/playground/smithyql/Parser.scala | 4 ++++ .../main/scala/playground/smithyql/WithSource.scala | 4 +++- .../src/test/scala/playground/NodeEncoderTests.scala | 10 ++++++++++ .../test/scala/playground/smithyql/Assertions.scala | 1 + .../scala/playground/smithyql/CompilationTests.scala | 7 +++++++ .../test/scala/playground/smithyql/PrettyPrint.scala | 1 + vscode-extension/grammar.tmLanguage.json | 12 ++++++++++++ 12 files changed, 51 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/playground/DocumentSymbolProvider.scala b/core/src/main/scala/playground/DocumentSymbolProvider.scala index 3b488ffb..5d3d17b8 100644 --- a/core/src/main/scala/playground/DocumentSymbolProvider.scala +++ b/core/src/main/scala/playground/DocumentSymbolProvider.scala @@ -67,6 +67,7 @@ object DocumentSymbolProvider { int = _ => Nil, listed = list => findInList(node.copy(value = list)), bool = _ => Nil, + nul = _ => Nil, ) private def findInList( diff --git a/core/src/main/scala/playground/NodeEncoderVisitor.scala b/core/src/main/scala/playground/NodeEncoderVisitor.scala index 9f208cdf..b727fa07 100644 --- a/core/src/main/scala/playground/NodeEncoderVisitor.scala +++ b/core/src/main/scala/playground/NodeEncoderVisitor.scala @@ -49,6 +49,7 @@ import smithy4s.schema.Schema import smithy4s.schema.SchemaField import smithy4s.schema.SchemaVisitor import smithy4s.ByteArray +import playground.smithyql.NullLiteral trait NodeEncoder[A] { def toNode(a: A): InputNode[Id] @@ -234,7 +235,7 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => case DArray(value) => document.listed.toNode(value.toList) case DBoolean(value) => boolean.toNode(value) case DNumber(value) => number.toNode(value.toString()) - case DNull => obj(List("null" -> obj(Nil))) + case DNull => NullLiteral() case DString(value) => string.toNode(value) case DObject(value) => obj(value.toList.map(_.map(document.toNode))) } diff --git a/core/src/main/scala/playground/QueryCompiler.scala b/core/src/main/scala/playground/QueryCompiler.scala index 6ef74479..159edd57 100644 --- a/core/src/main/scala/playground/QueryCompiler.scala +++ b/core/src/main/scala/playground/QueryCompiler.scala @@ -621,6 +621,7 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] { .value .parTraverse { case (key, value) => document.compile(value).tupleLeft(key.value.text) } .map(Document.obj(_: _*)) + case NullLiteral() => Document.nullDoc.rightIor } val string = stringLiteral.map(_.value) diff --git a/core/src/main/scala/playground/smithyql/AST.scala b/core/src/main/scala/playground/smithyql/AST.scala index dece4efe..c82b2ed4 100644 --- a/core/src/main/scala/playground/smithyql/AST.scala +++ b/core/src/main/scala/playground/smithyql/AST.scala @@ -30,6 +30,7 @@ sealed trait InputNode[F[_]] extends AST[F] { int: IntLiteral[F] => A, listed: Listed[F] => A, bool: BooleanLiteral[F] => A, + nul: NullLiteral[F] => A, ): A = this match { case s @ Struct(_) => struct(s) @@ -37,6 +38,7 @@ sealed trait InputNode[F[_]] extends AST[F] { case b @ BooleanLiteral(_) => bool(b) case s @ StringLiteral(_) => string(s) case l @ Listed(_) => listed(l) + case n @ NullLiteral() => nul(n) } def mapK[G[_]: Functor](fk: F ~> G): InputNode[G] @@ -158,6 +160,11 @@ object Struct { } +final case class NullLiteral[F[_]]() extends InputNode[F] { + def kind: NodeKind = NodeKind.NullLiteral + def mapK[G[_]: Functor](fk: F ~> G): InputNode[G] = copy() +} + final case class IntLiteral[F[_]](value: String) extends InputNode[F] { def kind: NodeKind = NodeKind.IntLiteral def mapK[G[_]: Functor](fk: F ~> G): InputNode[G] = copy() @@ -188,6 +195,7 @@ sealed trait NodeKind extends Product with Serializable object NodeKind { case object Struct extends NodeKind case object IntLiteral extends NodeKind + case object NullLiteral extends NodeKind case object StringLiteral extends NodeKind case object Query extends NodeKind case object Listed extends NodeKind diff --git a/core/src/main/scala/playground/smithyql/Formatter.scala b/core/src/main/scala/playground/smithyql/Formatter.scala index 311ac0d3..c1d7cd7e 100644 --- a/core/src/main/scala/playground/smithyql/Formatter.scala +++ b/core/src/main/scala/playground/smithyql/Formatter.scala @@ -25,6 +25,7 @@ object Formatter { case BooleanLiteral(b) => Doc.text(b.toString()) case StringLiteral(s) => Doc.text(renderStringLiteral(s)) case l @ Listed(_) => renderSequence(l) + case NullLiteral() => Doc.text("null") } def renderOperationName(o: OperationName[WithSource]): Doc = Doc.text(o.text) diff --git a/core/src/main/scala/playground/smithyql/Parser.scala b/core/src/main/scala/playground/smithyql/Parser.scala index ee0b65af..a3e7127e 100644 --- a/core/src/main/scala/playground/smithyql/Parser.scala +++ b/core/src/main/scala/playground/smithyql/Parser.scala @@ -108,6 +108,8 @@ object SmithyQLParser { .with1 .surroundedBy(char('"')) + val nullLiteral: Parser[Unit] = string("null") + def punctuation(c: Char): Parser[Unit] = char(c) val equalsSign = punctuation('=') @@ -150,11 +152,13 @@ object SmithyQLParser { val boolLiteral = tokens.bool.map(BooleanLiteral[T](_)) val stringLiteral = tokens.stringLiteral.map(StringLiteral[T](_)) + val nullLiteral = tokens.nullLiteral.map(_ => NullLiteral[T]()) lazy val node: Parser[InputNode[T]] = Parser.defer { intLiteral | boolLiteral | stringLiteral | + nullLiteral | struct | listed } diff --git a/core/src/main/scala/playground/smithyql/WithSource.scala b/core/src/main/scala/playground/smithyql/WithSource.scala index 901b94bf..f944fb14 100644 --- a/core/src/main/scala/playground/smithyql/WithSource.scala +++ b/core/src/main/scala/playground/smithyql/WithSource.scala @@ -84,7 +84,7 @@ object WithSource { _.value .flatMap { case (k, v) => k.allComments(_ => Nil) ++ v.allComments( - _.fold(comments, comments, comments, comments, comments) + _.fold(comments, comments, comments, comments, comments, comments) ) } .toList @@ -93,6 +93,7 @@ object WithSource { int = _ => Nil, bool = _ => Nil, listed = _.values.allComments(_.flatMap(_.allComments(comments))), + nul = _ => Nil, ) q.useClause.commentsLeft ++ q @@ -110,6 +111,7 @@ object WithSource { comments, comments, comments, + comments, ) ) } diff --git a/core/src/test/scala/playground/NodeEncoderTests.scala b/core/src/test/scala/playground/NodeEncoderTests.scala index aeefd7c8..39e6bd2c 100644 --- a/core/src/test/scala/playground/NodeEncoderTests.scala +++ b/core/src/test/scala/playground/NodeEncoderTests.scala @@ -12,6 +12,8 @@ import smithy4s.Timestamp import smithy.api.TimestampFormat import smithy4s.ByteArray import playground.smithyql.StringLiteral +import smithy4s.Document +import playground.smithyql.NullLiteral object NodeEncoderTests extends FunSuite { @@ -66,4 +68,12 @@ object NodeEncoderTests extends FunSuite { StringLiteral("Zm9v"), ) } + + test("null document") { + assertEncodes( + Schema.document, + Document.nullDoc, + NullLiteral(), + ) + } } diff --git a/core/src/test/scala/playground/smithyql/Assertions.scala b/core/src/test/scala/playground/smithyql/Assertions.scala index 4ef1b52a..8efea6bb 100644 --- a/core/src/test/scala/playground/smithyql/Assertions.scala +++ b/core/src/test/scala/playground/smithyql/Assertions.scala @@ -30,6 +30,7 @@ object Assertions extends Expectations.Helpers { string = _ => "string", listed = _ => "list", bool = _ => "bool", + nul = _ => "null", ) ensureEqual(tpe(a), tpe(b))(ctx) diff --git a/core/src/test/scala/playground/smithyql/CompilationTests.scala b/core/src/test/scala/playground/smithyql/CompilationTests.scala index bbbf9157..0d03f904 100644 --- a/core/src/test/scala/playground/smithyql/CompilationTests.scala +++ b/core/src/test/scala/playground/smithyql/CompilationTests.scala @@ -274,6 +274,13 @@ object CompilationTests extends SimpleIOSuite with Checkers { ) } + pureTest("null document") { + assert( + compile { + WithSource.liftId(NullLiteral[WithSource]()) + }(Schema.document) == Ior.right(Document.nullDoc) + ) + } pureTest("blob") { assert( compile { diff --git a/core/src/test/scala/playground/smithyql/PrettyPrint.scala b/core/src/test/scala/playground/smithyql/PrettyPrint.scala index 76a9d231..955806d8 100644 --- a/core/src/test/scala/playground/smithyql/PrettyPrint.scala +++ b/core/src/test/scala/playground/smithyql/PrettyPrint.scala @@ -88,6 +88,7 @@ object PrettyPrint { int = i => Structure(Map("int" -> just(i.value.toString))), listed = list => ???, /* todo */ bool = b => Structure(Map("bool" -> just(b.value.toString))), + nul = _ => Structure(Map("null" -> empty)), ) } diff --git a/vscode-extension/grammar.tmLanguage.json b/vscode-extension/grammar.tmLanguage.json index f9cdecdc..bb11bc64 100644 --- a/vscode-extension/grammar.tmLanguage.json +++ b/vscode-extension/grammar.tmLanguage.json @@ -17,6 +17,9 @@ { "include": "#booleans" }, + { + "include": "#nulls" + }, { "include": "#keywords" } @@ -60,6 +63,15 @@ } ] }, + "nulls": { + "name": "constant.null", + "patterns": [ + { + "name": "constant.null", + "match": "null" + } + ] + }, "operators": { "name": "keyword.operator", "patterns": [ From 9913b3ffd771cb1f08a5952e3dc078e273ebb8e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sat, 13 Aug 2022 22:39:05 +0200 Subject: [PATCH 7/7] anti-null test --- .../scala/playground/smithyql/CompilationTests.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/test/scala/playground/smithyql/CompilationTests.scala b/core/src/test/scala/playground/smithyql/CompilationTests.scala index 0d03f904..4f7f076a 100644 --- a/core/src/test/scala/playground/smithyql/CompilationTests.scala +++ b/core/src/test/scala/playground/smithyql/CompilationTests.scala @@ -281,6 +281,15 @@ object CompilationTests extends SimpleIOSuite with Checkers { }(Schema.document) == Ior.right(Document.nullDoc) ) } + + pureTest("null doesn't work as anything like a string") { + assert( + compile { + WithSource.liftId(NullLiteral[WithSource]()) + }(Schema.string).isLeft + ) + } + pureTest("blob") { assert( compile {