From 469cd86c86492278ac5cdb216ba29b8b2454c56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Thu, 5 Oct 2023 13:20:24 +0200 Subject: [PATCH] Update to smithy4s 0.18 (#255) --- README.md | 5 +- build.sbt | 2 +- .../playground/DynamicServiceProxy.scala | 47 +----- .../scala/playground/NodeEncoderVisitor.scala | 55 +++---- .../scala/playground/OperationCompiler.scala | 5 +- .../scala/playground/OperationRunner.scala | 19 ++- .../playground/QueryCompilerVisitor.scala | 145 ++++++++++-------- .../smithyutil/AddDynamicRefinements.scala | 3 +- .../smithyutil/TransitiveCompiler.scala | 20 ++- .../src/test/scala/playground/Diffs.scala | 4 +- .../test/scala/playground/DynamicModel.scala | 2 +- .../scala/playground/NodeEncoderTests.scala | 31 +++- .../smithyql/CompilationTests.scala | 36 ++++- modules/core/src/test/smithy/demo.smithy | 21 ++- .../test/scala/playground/e2e/E2ETests.scala | 4 +- .../language/CompletionProvider.scala | 3 +- .../language/CompletionVisitor.scala | 111 ++++++++++---- .../language/CompletionProviderTests.scala | 22 +-- .../playground/language/CompletionTests.scala | 73 ++++++++- .../language/DiagnosticProviderTests.scala | 8 + .../scala/playground/lsp/BuildLoader.scala | 19 +-- .../scala/playground/lsp/LanguageServer.scala | 2 +- .../scala/playground/lsp/MainServer.scala | 3 +- .../scala/playground/lsp/ServerBuilder.scala | 12 +- .../lsp/util/SerializedSmithyModel.scala | 21 --- project/plugins.sbt | 3 +- smithy-build.json | 4 +- 27 files changed, 426 insertions(+), 254 deletions(-) delete mode 100644 modules/lsp/src/main/scala/playground/lsp/util/SerializedSmithyModel.scala diff --git a/README.md b/README.md index 111c10fd..12e285d7 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,9 @@ We have: - booleans: `true`, `false` - lists: `[ true, false, ]` - structures: `{ k: "value", k2: true, k3: [ 10, 20, ], }` - note that keys aren't quoted, unlike in JSON -- null, but it's not what you think: the `null` literal only typechecks as a Smithy `Document`, - and corresponds to the [Null document](https://awslabs.github.io/smithy/2.0/spec/simple-types.html?highlight=null#document). +- null, but it's not what you think: the `null` literal only typechecks as two things: + - a Smithy `Document`: corresponds to the [Null document](https://awslabs.github.io/smithy/2.0/spec/simple-types.html?highlight=null#document) + - an element of a sparse collection. ### Type representation diff --git a/build.sbt b/build.sbt index 6248f1ee..ce3c7270 100644 --- a/build.sbt +++ b/build.sbt @@ -131,7 +131,7 @@ lazy val core = module("core") "com.disneystreaming.smithy4s" %% "smithy4s-aws-http4s" % smithy4sVersion.value, "com.disneystreaming.smithy4s" % "smithy4s-protocol" % smithy4sVersion.value % Test, "com.disneystreaming.alloy" % "alloy-core" % "0.2.7" % Test, - "software.amazon.smithy" % "smithy-aws-traits" % "1.34.0" % Test, + "software.amazon.smithy" % "smithy-aws-traits" % "1.39.1" % Test, ), Smithy4sCodegenPlugin.defaultSettings(Test), ) diff --git a/modules/core/src/main/scala/playground/DynamicServiceProxy.scala b/modules/core/src/main/scala/playground/DynamicServiceProxy.scala index d03bb6fc..b5e15479 100644 --- a/modules/core/src/main/scala/playground/DynamicServiceProxy.scala +++ b/modules/core/src/main/scala/playground/DynamicServiceProxy.scala @@ -50,11 +50,11 @@ class DynamicServiceProxy[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]]( val mapOutput = makeProxy(endpointStatic.output, endpoint.output) def errorMapper[A]: Throwable => F[A] = - endpointStatic.errorable match { + endpointStatic.error match { case None => _.raiseError[F, A] case Some(errorableStatic) => - val errorable = endpoint.errorable.get // should be there at this point - val mapError = makeProxy(errorableStatic.error, errorable.error) + val errorable = endpoint.error.get // should be there at this point + val mapError = makeProxy(errorableStatic.schema, errorable.schema) e => errorableStatic.liftError(e) match { @@ -77,47 +77,8 @@ class DynamicServiceProxy[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]]( endpoint: Endpoint[Op, I, E, O, SI, SO] ): I => F[O] = applyWithStatic(endpoint, grp(endpoint.id)) } - .precomputeBy(service.endpoints, _.name) - - new FunctorInterpreter[Op, F] { - def apply[I, E, O, SI, SO]( - op: Op[I, E, O, SI, SO] - ): F[O] = { - val (input, endpoint) = service.endpoint(op) - endpointMapping(endpoint)(input) - } - } - } - - private final implicit class PolyFunction5Ops[F[_, _, _, _, _], G[_, _, _, _, _]]( - self: PolyFunction5[F, G] - ) { - - // copied from smithy4s PolyFunction5's unsafeCacheBy - final def precomputeBy[K]( - allPossibleInputs: Seq[Kind5.Existential[F]], - getKey: Kind5.Existential[F] => K, - ): PolyFunction5[F, G] = - new PolyFunction5[F, G] { - - private val map: Map[K, Any] = { - val builder = Map.newBuilder[K, Any] - allPossibleInputs.foreach(input => - builder += getKey(input) -> self - .apply(input.asInstanceOf[F[Any, Any, Any, Any, Any]]) - .asInstanceOf[Any] - ) - builder.result() - } - - def apply[A0, A1, A2, A3, A4]( - input: F[A0, A1, A2, A3, A4] - ): G[A0, A1, A2, A3, A4] = map( - getKey(Kind5.existential(input)) - ).asInstanceOf[G[A0, A1, A2, A3, A4]] - - } + service.functorInterpreter(endpointMapping) } } diff --git a/modules/core/src/main/scala/playground/NodeEncoderVisitor.scala b/modules/core/src/main/scala/playground/NodeEncoderVisitor.scala index 44c855f5..37a346ce 100644 --- a/modules/core/src/main/scala/playground/NodeEncoderVisitor.scala +++ b/modules/core/src/main/scala/playground/NodeEncoderVisitor.scala @@ -13,7 +13,6 @@ import playground.smithyql.NullLiteral import playground.smithyql.StringLiteral import playground.smithyql.Struct import smithy4s.Bijection -import smithy4s.ByteArray import smithy4s.Document import smithy4s.Document.DArray import smithy4s.Document.DBoolean @@ -32,12 +31,12 @@ import smithy4s.schema.CollectionTag.IndexedSeqTag import smithy4s.schema.CollectionTag.ListTag import smithy4s.schema.CollectionTag.SetTag import smithy4s.schema.CollectionTag.VectorTag +import smithy4s.schema.EnumTag import smithy4s.schema.EnumValue import smithy4s.schema.Field import smithy4s.schema.Primitive import smithy4s.schema.Primitive._ import smithy4s.schema.Schema -import smithy4s.schema.SchemaField import smithy4s.schema.SchemaVisitor trait NodeEncoder[A] { @@ -111,16 +110,24 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => case PBigInt => bigint case PBoolean => boolean case PBigDecimal => bigdecimal - case PBlob => string.contramap((_: ByteArray).toString) + case PBlob => string.contramap(_.toBase64String) case PDouble => double case PDocument => document case PFloat => float - case PUnit => _ => obj(Nil) case PUUID => string.contramap(_.toString()) case PByte => byte case PTimestamp => string.contramap(_.toString) } + def option[A]( + schema: Schema[A] + ): NodeEncoder[Option[A]] = { + val base = schema.compile(this) + val nullDoc = document.toNode(Document.nullDoc) + + _.fold(nullDoc)(base.toNode) + } + def collection[C[_], A]( shapeId: ShapeId, hints: Hints, @@ -162,6 +169,7 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => def enumeration[E]( shapeId: ShapeId, hints: Hints, + tag: EnumTag[E], values: List[EnumValue[E]], total: E => EnumValue[E], ): NodeEncoder[E] = string.contramap(total(_).name) @@ -169,39 +177,32 @@ object NodeEncoderVisitor extends SchemaVisitor[NodeEncoder] { self => def struct[S]( shapeId: ShapeId, hints: Hints, - fieldsRaw: Vector[SchemaField[S, _]], + fieldsRaw: Vector[Field[S, _]], make: IndexedSeq[Any] => S, ): NodeEncoder[S] = { - val fields = fieldsRaw.map(_.mapK(this)) def go[A]( - f: Field[NodeEncoder, S, A], - s: S, - ): Option[Binding[Id]] = f.fold( - new Field.Folder[NodeEncoder, S, Option[Binding[Id]]] { - def onRequired[F]( - label: String, - instance: NodeEncoder[F], - get: S => F, - ): Option[Binding[Id]] = Binding[Id](Identifier(label), instance.toNode(get(s))).some - - def onOptional[F]( - label: String, - instance: NodeEncoder[F], - get: S => Option[F], - ): Option[Binding[Id]] = get(s).map(f => Binding[Id](Identifier(label), instance.toNode(f))) - } - ) + f: Field[S, A] + ): S => Option[Binding[Id]] = { + val instance = f.schema.compile(this) + + s => + f.getUnlessDefault(s).map(instance.toNode(_)).map { v => + Binding[Id](Identifier(f.label), v) + } + } + + val fields = fieldsRaw.map(go(_)) - s => obj(fields.flatMap(go(_, s)).toList) + s => obj(fields.mapFilter(_.apply(s)).toList) } def union[U]( shapeId: ShapeId, hints: Hints, - alternatives: Vector[Alt[Schema, U, _]], - dispatcher: Alt.Dispatcher[Schema, U], - ): NodeEncoder[U] = dispatcher.compile(new Alt.Precompiler[Schema, NodeEncoder] { + alternatives: Vector[Alt[U, _]], + dispatcher: Alt.Dispatcher[U], + ): NodeEncoder[U] = dispatcher.compile(new Alt.Precompiler[NodeEncoder] { def apply[A]( label: String, diff --git a/modules/core/src/main/scala/playground/OperationCompiler.scala b/modules/core/src/main/scala/playground/OperationCompiler.scala index 4048d388..631d34a8 100644 --- a/modules/core/src/main/scala/playground/OperationCompiler.scala +++ b/modules/core/src/main/scala/playground/OperationCompiler.scala @@ -83,7 +83,7 @@ object OperationCompiler { def fromSchemaIndex( dsi: DynamicSchemaIndex - ): OperationCompiler[Eff] = fromServices(dsi.allServices) + ): OperationCompiler[Eff] = fromServices(dsi.allServices.toList) def fromServices( services: List[DynamicSchemaIndex.ServiceWrapper] @@ -154,7 +154,7 @@ private class ServiceCompiler[Alg[_[_, _, _, _, _]]]( ): QueryCompiler[CompiledInput] = { val inputCompiler = e.input.compile(QueryCompilerVisitor.full) val outputEncoder = NodeEncoder.derive(e.output) - val errorEncoder = e.errorable.map(e => NodeEncoder.derive(e.error)) + val errorEncoder = e.error.map(e => NodeEncoder.derive(e.schema)) ast => inputCompiler @@ -177,6 +177,7 @@ private class ServiceCompiler[Alg[_[_, _, _, _, _]]]( // map of endpoint names to (endpoint, input compiler) private val endpoints = service .endpoints + .toList .groupByNel(_.name) .map(_.map(_.head).map(e => (e, compileEndpoint(e)))) diff --git a/modules/core/src/main/scala/playground/OperationRunner.scala b/modules/core/src/main/scala/playground/OperationRunner.scala index f9b69a53..ed018e0c 100644 --- a/modules/core/src/main/scala/playground/OperationRunner.scala +++ b/modules/core/src/main/scala/playground/OperationRunner.scala @@ -2,16 +2,21 @@ package playground import aws.protocols.AwsJson1_0 import aws.protocols.AwsJson1_1 +import aws.protocols.AwsQuery +import aws.protocols.Ec2Query +import aws.protocols.RestJson1 +import aws.protocols.RestXml import cats.Defer import cats.Id import cats.data.IorNel import cats.data.NonEmptyList -import cats.effect.Concurrent +import cats.effect.Async import cats.effect.MonadCancelThrow import cats.effect.Resource import cats.effect.implicits._ import cats.effect.std import cats.implicits._ +import fs2.compression.Compression import org.http4s.Uri import org.http4s.client.Client import playground._ @@ -135,14 +140,14 @@ object OperationRunner { } } - def forSchemaIndex[F[_]: StdlibRuntime: Concurrent: Defer: std.Console]( + def forSchemaIndex[F[_]: StdlibRuntime: Async: Compression: std.Console]( dsi: DynamicSchemaIndex, client: Client[F], baseUri: F[Uri], awsEnv: Resource[F, AwsEnvironment[F]], plugins: List[PlaygroundPlugin], ): Map[QualifiedIdentifier, Resolver[F]] = forServices( - services = dsi.allServices, + services = dsi.allServices.toList, getSchema = dsi.getSchema, client = client, baseUri = baseUri, @@ -150,7 +155,7 @@ object OperationRunner { plugins = plugins, ) - def forServices[F[_]: StdlibRuntime: Concurrent: Defer: std.Console]( + def forServices[F[_]: StdlibRuntime: Async: Compression: std.Console]( services: List[DynamicSchemaIndex.ServiceWrapper], getSchema: ShapeId => Option[Schema[_]], client: Client[F], @@ -194,7 +199,7 @@ object OperationRunner { } - def forService[Alg[_[_, _, _, _, _]], F[_]: StdlibRuntime: Concurrent: Defer: std.Console]( + def forService[Alg[_[_, _, _, _, _]], F[_]: StdlibRuntime: Async: Compression: std.Console]( service: Service[Alg], client: Client[F], baseUri: F[Uri], @@ -256,14 +261,14 @@ object OperationRunner { .prepare(service) .map { builder => awsEnv - .map(builder.buildSimple(_)) + .map(builder.build(_)) .map(service.toPolyFunction(_)) } .map(liftFunctorInterpreterResource(_)) .toIor .leftMap(_ => NonEmptyList - .of(AwsJson1_0.id, AwsJson1_1.id) + .of(AwsJson1_0.id, AwsJson1_1.id, RestJson1.id, AwsQuery.id, RestXml.id, Ec2Query.id) .map(Issue.InvalidProtocol(_, serviceProtocols)) ) diff --git a/modules/core/src/main/scala/playground/QueryCompilerVisitor.scala b/modules/core/src/main/scala/playground/QueryCompilerVisitor.scala index 97756614..e8f90cc6 100644 --- a/modules/core/src/main/scala/playground/QueryCompilerVisitor.scala +++ b/modules/core/src/main/scala/playground/QueryCompilerVisitor.scala @@ -11,7 +11,7 @@ import playground.smithyutil._ import smithy.api import smithy.api.TimestampFormat import smithy4s.Bijection -import smithy4s.ByteArray +import smithy4s.Blob import smithy4s.Document import smithy4s.Hints import smithy4s.Lazy @@ -24,7 +24,9 @@ import smithy4s.schema.CollectionTag.IndexedSeqTag import smithy4s.schema.CollectionTag.ListTag import smithy4s.schema.CollectionTag.SetTag import smithy4s.schema.CollectionTag.VectorTag +import smithy4s.schema.EnumTag import smithy4s.schema.EnumValue +import smithy4s.schema.Field import smithy4s.schema.Primitive import smithy4s.schema.Primitive.PBigDecimal import smithy4s.schema.Primitive.PBigInt @@ -40,9 +42,7 @@ import smithy4s.schema.Primitive.PShort import smithy4s.schema.Primitive.PString import smithy4s.schema.Primitive.PTimestamp import smithy4s.schema.Primitive.PUUID -import smithy4s.schema.Primitive.PUnit import smithy4s.schema.Schema -import smithy4s.schema.SchemaField import smithy4s.schema.SchemaVisitor import smithy4s.~> import types._ @@ -50,6 +50,7 @@ import util.chaining._ import java.util.Base64 import java.util.UUID +import scala.collection.immutable.ListMap object QueryCompilerVisitor { val full: Schema ~> QueryCompiler = @@ -88,7 +89,6 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { QueryCompiler .typeCheck(NodeKind.Bool) { case b @ BooleanLiteral(_) => b } .map(_.value.value) - case PUnit => struct(shapeId, hints, Vector.empty, _ => ()) case PLong => checkRange(number)("int")(_.toLongExact) case PInt => checkRange(number)("int")(_.toIntExact) case PShort => checkRange(number)("short")(_.toShortExact) @@ -100,7 +100,7 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { (string, QueryCompiler.pos).tupled.emap { case (s, range) => Either .catchNonFatal(Base64.getDecoder().decode(s)) - .map(ByteArray(_)) + .map(Blob(_)) .leftMap(_ => CompilationError.error(CompilationErrorDetails.InvalidBlob, range)) .toIor .toIorNec @@ -223,34 +223,31 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { def default: Option[A] } - private val compileField: Schema ~> FieldCompiler = - new (Schema ~> FieldCompiler) { + private object FieldCompiler { - def apply[A]( - schema: Schema[A] - ): FieldCompiler[A] = - new FieldCompiler[A] { - def compiler: QueryCompiler[A] = schema.compile(QueryCompilerVisitorInternal) + def compile[A]( + field: Field[_, A] + ): FieldCompiler[A] = + new FieldCompiler[A] { + override val compiler: QueryCompiler[A] = field.schema.compile(QueryCompilerVisitorInternal) - def default: Option[A] = schema - .hints - .get(api.Default) - // Ignoring precise error, as this should generally be a Right _always_ due to smithy-level validation - .flatMap(v => Document.Decoder.fromSchema(schema).decode(v.value).toOption) + override val default: Option[A] = field.schema.getDefaultValue - } + } - } + } def struct[S]( shapeId: ShapeId, hints: Hints, - fieldsRaw: Vector[SchemaField[S, _]], + fieldsRaw: Vector[Field[S, _]], make: IndexedSeq[Any] => S, ): QueryCompiler[S] = { - val fields = fieldsRaw.map(_.mapK(compileField)) + val fields = fieldsRaw + .map(f => f.label -> FieldCompiler.compile(f)) + .to(ListMap) - val validFields = fields.map(_.label) + val validFields = fields val deprecatedFields = fieldsRaw.flatMap { f => f.hints.get(api.Deprecated).tupleLeft(f.label) @@ -259,21 +256,16 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { QueryCompiler .typeCheck(NodeKind.Struct) { case s @ Struct(_) => s } .emap { struct => - // this is a list to keep the original type's ordering - val remainingValidFields = - validFields - .filterNot( - struct.value.fields.value.keys.map(_.value.text).toSet - ) - .toList - val presentKeys = struct.value.fields.value.keys + // this is a list to keep the original type's ordering + val remainingValidFields = validFields -- presentKeys.map(_.value.text).toSet + val extraFieldErrors: QueryCompiler.Result[Unit] = presentKeys - .filterNot(field => validFields.contains_(field.value.text)) + .filterNot(field => validFields.keySet.contains_(field.value.text)) .map { unexpectedKey => CompilationError.error( - UnexpectedField(remainingValidFields), + UnexpectedField(remainingValidFields.keys.toList), unexpectedKey.range, ) } @@ -294,44 +286,60 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { .toBothLeft(()) .combine(Ior.right(())) - val buildStruct = fields - .parTraverse { field => - val fieldOpt = struct + def handleField[T]( + label: String, + field: FieldCompiler[T], + ): QueryCompiler.Result[T] = { + val fieldByName = + struct .value .fields .value - .byName(field.label)(_.value) - .parTraverse(field.instance.compiler.compile) - - if (field.isOptional) - fieldOpt - else - // Note: defaults get no special handling in dynamic schemas (in which a field with a default is considered optional). - // There's no real need to provide the default value in a dynamic client, as it can just omit the field in the request being sent. - // The server shall provide the default value on its own. - // This `orElse` fallback will arguably never be hit in practice, but it's here for completeness - just in case the compiler ends up being used with static services. - fieldOpt.map(_.orElse(field.instance.default)).flatMap { - _.toRightIor( - CompilationError.error( - MissingField(field.label), - struct.value.fields.range, - ) - ).toIorNec - } - } + .byName(label)(_.value) + + // Note: defaults get no special handling in dynamic schemas (in which a field with a default is considered optional). + // There's no real need to provide the default value in a dynamic client, as it can just omit the field in the request being sent. + // The server shall provide the default value on its own. + // This `orElse` fallback will arguably never be hit in practice, but it's here for completeness - just in case the compiler ends up being used with static + fieldByName + .parTraverse(field.compiler.compile) + .map(_.orElse(field.default)) + .flatMap { + _.toRightIor( + CompilationError.error( + MissingField(label), + struct.value.fields.range, + ) + ).toIorNec + } + } + + val buildStruct = fields + .toVector + .parTraverse { case (label, instance) => handleField(label, instance) } buildStruct.map(make) <& extraFieldErrors <& deprecatedFieldWarnings } - } def union[U]( shapeId: ShapeId, hints: Hints, - alternatives: Vector[Alt[Schema, U, _]], - dispatcher: Alt.Dispatcher[Schema, U], + alternatives: Vector[Alt[U, _]], + dispatcher: Alt.Dispatcher[U], ): QueryCompiler[U] = { - val alternativesCompiled = alternatives.map(_.mapK(this)).groupBy(_.label).map(_.map(_.head)) + def handleAlt[A]( + alt: Alt[U, A] + ): QueryCompiler[U] = alt + .schema + .compile(QueryCompilerVisitorInternal.this) + .map(alt.inject) + + val alternativesCompiled = alternatives + .groupByNev(_.label) + .fmap(_.head) + .fmap(handleAlt(_)) + val deprecatedAlternativeLabels = alternatives.flatMap(alt => alt.hints.get(api.Deprecated).tupleLeft(alt.label)).toMap @@ -345,10 +353,6 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { val definition = s.value.fields.value.head val key = definition.identifier - def go[A]( - alt: Alt[QueryCompiler, U, A] - ): QueryCompiler[U] = alt.instance.map(alt.inject) - val op = alternativesCompiled .get(key.value.text) @@ -372,7 +376,7 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { .toBothLeft(()) .combine(Ior.right(())) - op.flatMap(go(_).compile(definition.value)) <& deprecationWarning + op.flatMap(_.compile(definition.value)) <& deprecationWarning case s if s.value.fields.value.isEmpty => CompilationError @@ -454,9 +458,22 @@ object QueryCompilerVisitorInternal extends SchemaVisitor[QueryCompiler] { val string: QueryCompiler[String] = stringLiteral.map(_.value) - def enumeration[E]( + def option[A]( + schema: Schema[A] + ): QueryCompiler[Option[A]] = { + val underlying = schema.compile(this) + + in => + in.value match { + case NullLiteral() => None.rightIor + case _ => underlying.compile(in).map(Some(_)) + } + } + + override def enumeration[E]( shapeId: ShapeId, hints: Hints, + tag: EnumTag[E], values: List[EnumValue[E]], total: E => EnumValue[E], ): QueryCompiler[E] = (string, QueryCompiler.pos).tupled.emap { case (name, range) => diff --git a/modules/core/src/main/scala/playground/smithyutil/AddDynamicRefinements.scala b/modules/core/src/main/scala/playground/smithyutil/AddDynamicRefinements.scala index fb5de0bc..e5817182 100644 --- a/modules/core/src/main/scala/playground/smithyutil/AddDynamicRefinements.scala +++ b/modules/core/src/main/scala/playground/smithyutil/AddDynamicRefinements.scala @@ -64,7 +64,7 @@ object AddDynamicRefinements extends (Schema ~> Schema) { case PBigInt => schema.reifyHint[api.Range] case PBigDecimal => schema.reifyHint[api.Range] case PBlob => schema.reifyHint[api.Length] - case PUnit | PTimestamp | PDocument | PBoolean | PUUID => schema + case PTimestamp | PDocument | PBoolean | PUUID => schema } case c: CollectionSchema[_, _] => collection(c) @@ -76,6 +76,7 @@ object AddDynamicRefinements extends (Schema ~> Schema) { case s: StructSchema[_] => s case l: LazySchema[_] => l case u: UnionSchema[_] => u + case n: OptionSchema[_] => n } } diff --git a/modules/core/src/main/scala/playground/smithyutil/TransitiveCompiler.scala b/modules/core/src/main/scala/playground/smithyutil/TransitiveCompiler.scala index 50ce4dff..f61baf0f 100644 --- a/modules/core/src/main/scala/playground/smithyutil/TransitiveCompiler.scala +++ b/modules/core/src/main/scala/playground/smithyutil/TransitiveCompiler.scala @@ -1,11 +1,14 @@ package playground.smithyutil +import smithy4s.schema.Alt +import smithy4s.schema.Field import smithy4s.schema.Schema import smithy4s.schema.Schema.BijectionSchema import smithy4s.schema.Schema.CollectionSchema import smithy4s.schema.Schema.EnumerationSchema import smithy4s.schema.Schema.LazySchema import smithy4s.schema.Schema.MapSchema +import smithy4s.schema.Schema.OptionSchema import smithy4s.schema.Schema.PrimitiveSchema import smithy4s.schema.Schema.RefinementSchema import smithy4s.schema.Schema.StructSchema @@ -21,16 +24,25 @@ final class TransitiveCompiler( fa: Schema[A] ): Schema[A] = fa match { - case e @ EnumerationSchema(_, _, _, _) => underlying(e) - case p @ PrimitiveSchema(_, _, _) => underlying(p) + case e @ EnumerationSchema(_, _, _, _, _) => underlying(e) + case p @ PrimitiveSchema(_, _, _) => underlying(p) case u @ UnionSchema(_, _, _, _) => - underlying(u.copy(alternatives = u.alternatives.map(_.mapK(this)))) + underlying(u.copy(alternatives = u.alternatives.map(handleAlt(_)))) case BijectionSchema(s, bijection) => underlying(BijectionSchema(this(s), bijection)) case LazySchema(suspend) => underlying(LazySchema(suspend.map(this.apply))) case RefinementSchema(s, refinement) => underlying(RefinementSchema(this(s), refinement)) case c @ CollectionSchema(_, _, _, _) => underlying(c.copy(member = this(c.member))) case m @ MapSchema(_, _, _, _) => underlying(m.copy(key = this(m.key), value = this(m.value))) - case s @ StructSchema(_, _, _, _) => underlying(s.copy(fields = s.fields.map(_.mapK(this)))) + case s @ StructSchema(_, _, _, _) => underlying(s.copy(fields = s.fields.map(handleField(_)))) + case n @ OptionSchema(_) => underlying(n.copy(underlying = this(n.underlying))) } + private def handleField[S, A]( + field: Field[S, A] + ): Field[S, A] = field.copy(schema = this(field.schema)) + + private def handleAlt[S, A]( + alt: Alt[S, A] + ): Alt[S, A] = alt.copy(schema = this(alt.schema)) + } diff --git a/modules/core/src/test/scala/playground/Diffs.scala b/modules/core/src/test/scala/playground/Diffs.scala index af51a0d6..da60e236 100644 --- a/modules/core/src/test/scala/playground/Diffs.scala +++ b/modules/core/src/test/scala/playground/Diffs.scala @@ -3,7 +3,7 @@ package playground import cats.data.Ior import playground.smithyql.ContextRange import playground.smithyql.NodeContext -import smithy4s.ByteArray +import smithy4s.Blob import scala.annotation.nowarn @@ -27,7 +27,7 @@ object Diffs { @nowarn("cat=unused") implicit def diffForIor[E: Diff, A: Diff]: Diff[Ior[E, A]] = Diff.derivedDiff - implicit val diffByteArray: Diff[ByteArray] = Diff[String].contramap(_.toString()) + implicit val diffByteArray: Diff[Blob] = Diff[String].contramap(_.toString()) implicit val diffDocument: Diff[smithy4s.Document] = Diff.derivedDiff implicit val diffTimestamp: Diff[smithy4s.Timestamp] = Diff[String].contramap(_.toString()) } diff --git a/modules/core/src/test/scala/playground/DynamicModel.scala b/modules/core/src/test/scala/playground/DynamicModel.scala index 926e7967..543bb2f9 100644 --- a/modules/core/src/test/scala/playground/DynamicModel.scala +++ b/modules/core/src/test/scala/playground/DynamicModel.scala @@ -13,7 +13,7 @@ object DynamicModel { .assemble() .unwrap() - DynamicSchemaIndex.loadModel(model).toTry.get + DynamicSchemaIndex.loadModel(model) } } diff --git a/modules/core/src/test/scala/playground/NodeEncoderTests.scala b/modules/core/src/test/scala/playground/NodeEncoderTests.scala index adbbc554..aa14e71f 100644 --- a/modules/core/src/test/scala/playground/NodeEncoderTests.scala +++ b/modules/core/src/test/scala/playground/NodeEncoderTests.scala @@ -3,14 +3,17 @@ package playground import cats.Id import demo.smithy.Good import demo.smithy.Hero +import demo.smithy.Person import demo.smithy.Power +import demo.smithy.SampleSparseList import playground.NodeEncoder import playground.smithyql.AST import playground.smithyql.DSL._ +import playground.smithyql.Listed import playground.smithyql.NullLiteral import playground.smithyql.StringLiteral import smithy.api.TimestampFormat -import smithy4s.ByteArray +import smithy4s.Blob import smithy4s.Document import smithy4s.Timestamp import smithy4s.schema.Schema @@ -50,6 +53,22 @@ object NodeEncoderTests extends FunSuite { assertEncodes(Good.schema, Good(42), struct("howGood" -> 42)) } + test("struct with optionals: defined") { + assertEncodes( + Person.schema, + Person("My name", age = Some(42)), + struct("name" -> "My name", "age" -> 42), + ) + } + + test("struct with optionals: empty") { + assertEncodes( + Person.schema, + Person("My name", age = None), + struct("name" -> "My name"), + ) + } + test("enum") { assertEncodes(Power.schema, Power.ICE, "ICE") } @@ -69,7 +88,7 @@ object NodeEncoderTests extends FunSuite { test("blob") { assertEncodes( Schema.bytes, - ByteArray("foo".getBytes()), + Blob("foo"), StringLiteral("Zm9v"), ) } @@ -81,4 +100,12 @@ object NodeEncoderTests extends FunSuite { NullLiteral(), ) } + + test("sparse list") { + assertEncodes( + SampleSparseList.schema, + SampleSparseList(List(Some(1), None, Some(3))), + Listed[Id](List(1, NullLiteral(), 3)), + ) + } } diff --git a/modules/core/src/test/scala/playground/smithyql/CompilationTests.scala b/modules/core/src/test/scala/playground/smithyql/CompilationTests.scala index b0dc813e..3a1d880a 100644 --- a/modules/core/src/test/scala/playground/smithyql/CompilationTests.scala +++ b/modules/core/src/test/scala/playground/smithyql/CompilationTests.scala @@ -1,5 +1,6 @@ package playground.smithyql +import cats.Id import cats.Show import cats.data.Chain import cats.data.Ior @@ -22,6 +23,7 @@ import demo.smithy.Ints import demo.smithy.MyInstant import demo.smithy.Person import demo.smithy.Power +import demo.smithy.SampleSparseList import demo.smithy.StringWithLength import org.scalacheck.Arbitrary import playground.Assertions._ @@ -46,7 +48,7 @@ import playground.std.ClockGen import playground.std.RandomGen import playground.types.IorThrow import smithy.api.TimestampFormat -import smithy4s.ByteArray +import smithy4s.Blob import smithy4s.Document import smithy4s.Refinement import smithy4s.Service @@ -474,7 +476,7 @@ object CompilationTests extends SimpleIOSuite with Checkers { compile { WithSource.liftId("dGVzdA==".mapK(WithSource.liftId)) }(Schema.bytes), - Ior.right(ByteArray("test".getBytes())), + Ior.right(Blob("test".getBytes())), ) } @@ -780,6 +782,36 @@ object CompilationTests extends SimpleIOSuite with Checkers { ) } + pureTest("sparse list of ints") { + implicit val diffSSL: Diff[SampleSparseList] = Diff[List[Option[Int]]].contramap(_.value) + + assertNoDiff( + compile[SampleSparseList]( + WithSource.liftId(List[InputNode[Id]](1, NullLiteral(), 3).mapK(WithSource.liftId)) + ).leftMap(_.map(_.err)), + Ior.right( + SampleSparseList(List(Some(1), None, Some(3))) + ), + ) + } + + pureTest("sparse list of ints - dynamic") { + assert.same( + compile( + WithSource.liftId(List[InputNode[Id]](1, NullLiteral(), 3).mapK(WithSource.liftId)) + )(dynamicSchemaFor[SampleSparseList]).leftMap(_.map(_.err)), + Ior.right( + Document.array( + List( + Document.fromInt(1), + Document.nullDoc, + Document.fromInt(3), + ) + ) + ), + ) + } + pureTest("set of ints") { assert( compile[IntSet](WithSource.liftId(List(1, 2, 3).mapK(WithSource.liftId))) == Ior.right( diff --git a/modules/core/src/test/smithy/demo.smithy b/modules/core/src/test/smithy/demo.smithy index b0799872..af8c7695 100644 --- a/modules/core/src/test/smithy/demo.smithy +++ b/modules/core/src/test/smithy/demo.smithy @@ -67,7 +67,9 @@ structure CreateHeroInput { friendSet: FriendSet hasNewtypes: HasNewtypes hasDeprecations: HasDeprecations - doc: Document + doc: Document, + sparse: SampleSparseList, + sparseMap: SampleSparseMap } @uniqueItems @@ -146,6 +148,11 @@ enum Power { WIND = "Wind" } +intEnum PrivacyTier { + PUBLIC = 0 + PRIVATE = 1 +} + @http(method: "PUT", uri: "/subscriptions") @idempotent @documentation(""" @@ -257,3 +264,15 @@ structure HasMixin with [SampleMixin] { @required name: String } + + +@sparse +list SampleSparseList { + member: Integer +} + +@sparse +map SampleSparseMap { + key: String + value: Integer +} diff --git a/modules/e2e/src/test/scala/playground/e2e/E2ETests.scala b/modules/e2e/src/test/scala/playground/e2e/E2ETests.scala index d3dc01a5..7ca8f8c7 100644 --- a/modules/e2e/src/test/scala/playground/e2e/E2ETests.scala +++ b/modules/e2e/src/test/scala/playground/e2e/E2ETests.scala @@ -21,6 +21,7 @@ import weaver._ import java.io.PrintWriter import java.lang.ProcessBuilder.Redirect import java.util.concurrent.CompletableFuture +import scala.concurrent.duration._ import scala.jdk.CollectionConverters._ import scala.util.chaining._ @@ -92,7 +93,7 @@ object E2ETests extends SimpleIOSuite { .create() Resource - .make(IO(launcher.startListening()))(f => IO(f.cancel(true): Unit)) + .make(IO(launcher.startListening()).timeout(5.seconds))(f => IO(f.cancel(true): Unit)) .as(new LanguageServerAdapter(launcher.getRemoteProxy())) } } @@ -123,5 +124,6 @@ object E2ETests extends SimpleIOSuite { } } + .timeout(20.seconds) } } diff --git a/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala b/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala index 56e1890c..8740b3d2 100644 --- a/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala +++ b/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala @@ -31,7 +31,7 @@ object CompletionProvider { def forSchemaIndex( dsi: DynamicSchemaIndex - ): CompletionProvider = forServices(dsi.allServices) + ): CompletionProvider = forServices(dsi.allServices.toList) def forServices( allServices: List[DynamicSchemaIndex.ServiceWrapper] @@ -70,6 +70,7 @@ object CompletionProvider { insertBodyStruct = insertBodyStruct, ) } + .toList } def completeRootOperationName( diff --git a/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala b/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala index 4cc15da1..b8262c32 100644 --- a/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala +++ b/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala @@ -32,13 +32,13 @@ import smithy4s.Timestamp import smithy4s.dynamic.DynamicSchemaIndex import smithy4s.schema.Alt import smithy4s.schema.CollectionTag +import smithy4s.schema.EnumTag +import smithy4s.schema.EnumTag.IntEnum import smithy4s.schema.EnumValue import smithy4s.schema.Field import smithy4s.schema.Primitive import smithy4s.schema.Schema import smithy4s.schema.Schema._ -import smithy4s.schema.SchemaAlt -import smithy4s.schema.SchemaField import smithy4s.schema.SchemaVisitor import java.util.UUID @@ -102,6 +102,18 @@ object InsertText { object CompletionItem { + def forNull: CompletionItem = CompletionItem( + kind = CompletionItemKind.Constant, + label = "null", + insertText = InsertText.JustString("null"), + detail = ": null", + description = None, + deprecated = false, + docs = None, + extraTextEdits = Nil, + sortText = None, + ) + def useServiceClause( ident: QualifiedIdentifier, service: DynamicSchemaIndex.ServiceWrapper, @@ -115,29 +127,29 @@ object CompletionItem { ).copy(detail = describeService(service)) def fromField( - field: Field[Schema, _, _] + field: Field[_, _] ): CompletionItem = fromHints( kind = CompletionItemKind.Field, label = field.label, insertText = InsertText.JustString(s"${field.label}: "), - schema = field.instance, + schema = field.schema, ) def fromAlt( - alt: Alt[Schema, _, _] + alt: Alt[_, _] ): CompletionItem = fromHints( kind = CompletionItemKind.UnionMember, label = alt.label, // needs proper completions for the inner schema // https://github.com/kubukoz/smithy-playground/pull/120 insertText = - if (describeSchema(alt.instance).apply().startsWith("structure ")) + if (describeSchema(alt.schema).apply().startsWith("structure ")) InsertText.SnippetString(s"""${alt.label}: { | $$0 |},""".stripMargin) else InsertText.JustString(s"${alt.label}: "), - alt.instance, + alt.schema, ) def fromHints( @@ -200,7 +212,6 @@ object CompletionItem { case PByte => "byte" case PDouble => "double" case PShort => "short" - case PUnit => "unit" case PBigInt => "bigInteger" case PInt => "integer" case PUUID => "uuid" @@ -214,15 +225,21 @@ object CompletionItem { } } - private def describeCollection[C[_]]: CollectionTag[C] => String = { + private def describeCollection[C[_]]( + tag: CollectionTag[C], + hints: Hints, + ): String = { import smithy4s.schema.CollectionTag._ - { - case ListTag => "list" - case SetTag => "set" - case IndexedSeqTag => "@indexedSeq list" - case VectorTag => "@vector list" - } + val base = + tag match { + case ListTag => "list" + case SetTag => "set" + case IndexedSeqTag => "@indexedSeq list" + case VectorTag => "@vector list" + } + + sparseTraitDescription(hints).foldMap(_ + " ") + base } def describeService( @@ -237,18 +254,31 @@ object CompletionItem { schema match { case PrimitiveSchema(shapeId, _, tag) => now(s"${describePrimitive(tag)} ${shapeId.name}") - case Schema.CollectionSchema(shapeId, _, tag, member) => - now(s"${describeCollection(tag)} ${shapeId.name} { member: ${describeSchema(member)()} }") + case Schema.CollectionSchema(shapeId, hints, tag, member) => + now( + s"${describeCollection(tag, hints)} ${shapeId.name} { member: ${describeSchema(member)()} }" + ) - case EnumerationSchema(shapeId, _, _, _) => now(s"enum ${shapeId.name}") + case e @ EnumerationSchema(_, _, _, _, _) => + e.tag match { + case IntEnum() => now(s"intEnum ${e.shapeId.name}") + case _ => now(s"enum ${e.shapeId.name}") + } case MapSchema(shapeId, _, key, value) => - now(s"map ${shapeId.name} { key: ${key.shapeId.name}, value: ${value.shapeId.name} }") + now( + sparseTraitDescription(schema.hints).foldMap(_ + " ") + + s"map ${shapeId.name} { key: ${describeSchema(key)()}, value: ${describeSchema(value)()} }" + ) case StructSchema(shapeId, _, _, _) => now(s"structure ${shapeId.name}") case UnionSchema(shapeId, _, _, _) => now(s"union ${shapeId.name}") + case OptionSchema(underlying) => + // ignore the fact that it's nullable, just describe the underlying schema + describeSchema(underlying) + case LazySchema(suspend) => // we don't look at fields or whatnot, // so we can immediately evaluate the schema and compile it as usual. @@ -259,6 +289,10 @@ object CompletionItem { case BijectionSchema(underlying, _) => describeSchema(underlying) } + private def sparseTraitDescription( + hints: Hints + ): Option[String] = hints.get(api.Sparse).as("@sparse") + private def now( s: String ): ( @@ -495,9 +529,21 @@ object CompletionVisitor extends SchemaVisitor[CompletionResolver] { ) } + override def option[A]( + schema: Schema[A] + ): CompletionResolver[Option[A]] = { + val underlying = schema.compile(this) + + { + case p @ EmptyPath => underlying.getCompletions(p).appended(CompletionItem.forNull) + case more => underlying.getCompletions(more) + } + } + override def enumeration[E]( shapeId: ShapeId, hints: Hints, + tag: EnumTag[E], values: List[EnumValue[E]], total: E => EnumValue[E], ): CompletionResolver[E] = quoteAware { transformString => @@ -507,7 +553,7 @@ object CompletionVisitor extends SchemaVisitor[CompletionResolver] { CompletionItemKind.EnumMember, enumValue.name, InsertText.JustString(transformString(enumValue.name)), - Schema.enumeration(total, values).addHints(hints).withId(shapeId), + Schema.enumeration(total, tag, values).addHints(hints).withId(shapeId), ) } } @@ -527,16 +573,16 @@ object CompletionVisitor extends SchemaVisitor[CompletionResolver] { override def struct[S]( shapeId: ShapeId, hints: Hints, - fields: Vector[SchemaField[S, _]], + fields: Vector[Field[S, _]], make: IndexedSeq[Any] => S, ): CompletionResolver[S] = { - val compiledFields = fields.map(field => (field.mapK(this), field.instance)) + val compiledFields = fields.map(field => (field, field.schema.compile(this))) structLike( inBody = fields // todo: filter out present fields - .sortBy(field => (field.isRequired, field.label)) + .sortBy(field => (field.isStrictlyRequired, field.label)) .map(CompletionItem.fromField) .toList, inValue = @@ -546,20 +592,20 @@ object CompletionVisitor extends SchemaVisitor[CompletionResolver] { ) => compiledFields .collectFirst { - case (field, _) if field.label === h => field + case (field, instance) if field.label === h => instance } - .foldMap(_.instance.getCompletions(rest)), + .foldMap(_.getCompletions(rest)), ) } override def union[U]( shapeId: ShapeId, hints: Hints, - alternatives: Vector[SchemaAlt[U, _]], - dispatcher: Alt.Dispatcher[Schema, U], + alternatives: Vector[Alt[U, _]], + dispatcher: Alt.Dispatcher[U], ): CompletionResolver[U] = { - val allWithIds = alternatives.map { alt => - (alt.mapK(this), alt.instance) + val compiledAlts = alternatives.map { alt => + (alt, alt.schema.compile(this)) } structLike( @@ -568,7 +614,12 @@ object CompletionVisitor extends SchemaVisitor[CompletionResolver] { ( head, tail, - ) => allWithIds.find(_._1.label === head).toList.flatMap(_._1.instance.getCompletions(tail)), + ) => + compiledAlts + .collect { + case (alt, instance) if alt.label === head => instance + } + .foldMap(_.getCompletions(tail)), ) } diff --git a/modules/language-support/src/test/scala/playground/language/CompletionProviderTests.scala b/modules/language-support/src/test/scala/playground/language/CompletionProviderTests.scala index 6f639bb2..53c4fdc7 100644 --- a/modules/language-support/src/test/scala/playground/language/CompletionProviderTests.scala +++ b/modules/language-support/src/test/scala/playground/language/CompletionProviderTests.scala @@ -26,16 +26,18 @@ object CompletionProviderTests extends SimpleIOSuite { Position(0), ) - val expected = RandomGen - .endpoints - .map(endpoint => - CompletionItem.forOperation( - insertUseClause = CompletionItem.InsertUseClause.Required, - endpoint, - QualifiedIdentifier.forService(RandomGen), - CompletionItem.InsertBodyStruct.Yes, - ) - ) + val expected = + RandomGen + .endpoints + .map { endpoint => + CompletionItem.forOperation( + insertUseClause = CompletionItem.InsertUseClause.Required, + endpoint, + QualifiedIdentifier.forService(RandomGen), + CompletionItem.InsertBodyStruct.Yes, + ) + } + .toList assertNoDiff(result, expected) } diff --git a/modules/language-support/src/test/scala/playground/language/CompletionTests.scala b/modules/language-support/src/test/scala/playground/language/CompletionTests.scala index b87da126..2a4b5d84 100644 --- a/modules/language-support/src/test/scala/playground/language/CompletionTests.scala +++ b/modules/language-support/src/test/scala/playground/language/CompletionTests.scala @@ -12,6 +12,11 @@ import demo.smithy.MyInt import demo.smithy.MyString import demo.smithy.Power import demo.smithy.PowerMap +import demo.smithy.PrivacyTier +import demo.smithy.SampleSparseList +import demo.smithy.SampleSparseMap +import playground.Assertions._ +import playground.language.Diffs._ import playground.smithyql.NodeContext import playground.smithyql.NodeContext.PathEntry._ import smithy.api.TimestampFormat @@ -115,6 +120,34 @@ object CompletionTests extends FunSuite { assert(completions.isEmpty) } + test("completions in sparse collection root contain null") { + val completions = getCompletions( + Schema.list(Schema.int.option), + NodeContext.EmptyPath.inCollectionEntry(None), + ) + + assertNoDiff( + completions, + List( + CompletionItem.forNull + ), + ) + } + + test("completions in sparse map root contain null") { + val completions = getCompletions( + Schema.map(Schema.string, Schema.int.option), + NodeContext.EmptyPath.inStructBody.inStructValue("test"), + ) + + assertNoDiff( + completions, + List( + CompletionItem.forNull + ), + ) + } + test("completions on struct in list are available") { val completions = getCompletions( Schema.list(Good.schema), @@ -126,6 +159,17 @@ object CompletionTests extends FunSuite { assert.eql(fieldNames, List("howGood")) } + test("completions on struct in sparse list are available") { + val completions = getCompletions( + Schema.list(Good.schema.option), + NodeContext.EmptyPath.inCollectionEntry(0.some).inStructBody, + ) + + val fieldNames = completions.map(_.label) + + assert.eql(fieldNames, List("howGood")) + } + test("completions on enum without quotes have quotes") { val completions = getCompletions(Power.schema, NodeContext.EmptyPath) @@ -288,10 +332,17 @@ object CompletionTests extends FunSuite { ) } + test("describe int enum") { + assert.eql( + CompletionItem.describeSchema(PrivacyTier.schema)(), + "intEnum PrivacyTier", + ) + } + test("describe map") { assert.eql( CompletionItem.describeSchema(PowerMap.schema)(), - "map PowerMap { key: Power, value: Hero }", + "map PowerMap { key: enum Power, value: union Hero }", ) } @@ -330,6 +381,26 @@ object CompletionTests extends FunSuite { ) } + test("describe sparse collection: sparse trait present") { + assert.eql( + CompletionItem.describeType( + isField = false, + SampleSparseList.schema, + ), + ": @sparse list SampleSparseList { member: integer Integer }", + ) + } + + test("describe sparse map: sparse trait present") { + assert.eql( + CompletionItem.describeType( + isField = false, + SampleSparseMap.schema, + ), + ": @sparse map SampleSparseMap { key: string String, value: integer Integer }", + ) + } + test("buildDocumentation: deprecation note goes before optionality note") { val doc = CompletionItem.buildDocumentation( isField = true, diff --git a/modules/language-support/src/test/scala/playground/language/DiagnosticProviderTests.scala b/modules/language-support/src/test/scala/playground/language/DiagnosticProviderTests.scala index 23a0c3cc..d3e791f2 100644 --- a/modules/language-support/src/test/scala/playground/language/DiagnosticProviderTests.scala +++ b/modules/language-support/src/test/scala/playground/language/DiagnosticProviderTests.scala @@ -3,6 +3,10 @@ package playground.language import alloy.SimpleRestJson import aws.protocols.AwsJson1_0 import aws.protocols.AwsJson1_1 +import aws.protocols.AwsQuery +import aws.protocols.Ec2Query +import aws.protocols.RestJson1 +import aws.protocols.RestXml import cats.data.NonEmptyList import cats.effect.IO import cats.effect.kernel.Resource @@ -60,6 +64,10 @@ object DiagnosticProviderTests extends SimpleIOSuite { SimpleRestJson, AwsJson1_0, AwsJson1_1, + RestJson1, + AwsQuery, + RestXml, + Ec2Query, Stdlib, ) .map(_.id) diff --git a/modules/lsp/src/main/scala/playground/lsp/BuildLoader.scala b/modules/lsp/src/main/scala/playground/lsp/BuildLoader.scala index f27e216b..e58f8f65 100644 --- a/modules/lsp/src/main/scala/playground/lsp/BuildLoader.scala +++ b/modules/lsp/src/main/scala/playground/lsp/BuildLoader.scala @@ -7,7 +7,6 @@ import fs2.io.file.Path import playground.PlaygroundConfig import playground.language.TextDocumentProvider import playground.language.Uri -import playground.lsp.util.SerializedSmithyModel import smithy4s.dynamic.DynamicSchemaIndex trait BuildLoader[F[_]] { @@ -101,7 +100,7 @@ object BuildLoader { for { specs <- filterImports(rawImportPaths) model <- loadModel(specs, loaded.config) - dsi <- DynamicSchemaIndex.loadModel(model).liftTo[F] + dsi = DynamicSchemaIndex.loadModel(model) } yield dsi } @@ -123,25 +122,9 @@ object BuildLoader { .emits(specs.toSeq) .flatMap(Files[F].walk(_)) .evalFilterNot(Files[F].isDirectory) - .evalFilter { file => - val isSmithyFile = file.extName === ".smithy" - - if (isSmithyFile) - true.pure[F] - else - isSerializedSmithyModelF(file) - } .compile .to(Set) - private def isSerializedSmithyModelF( - file: Path - ): F[Boolean] = Files[F] - .readAll(file) - .compile - .to(Array) - .map(SerializedSmithyModel.decode(_).isRight) - } } diff --git a/modules/lsp/src/main/scala/playground/lsp/LanguageServer.scala b/modules/lsp/src/main/scala/playground/lsp/LanguageServer.scala index 4a5b7d37..b2e2881f 100644 --- a/modules/lsp/src/main/scala/playground/lsp/LanguageServer.scala +++ b/modules/lsp/src/main/scala/playground/lsp/LanguageServer.scala @@ -127,7 +127,7 @@ object LanguageServer { // see if we can pass this everywhere // https://github.com/kubukoz/smithy-playground/issues/164 - val serviceIndex: ServiceIndex = ServiceIndex.fromServices(dsi.allServices) + val serviceIndex: ServiceIndex = ServiceIndex.fromServices(dsi.allServices.toList) val compiler: FileCompiler[IorThrow] = FileCompiler .instance( diff --git a/modules/lsp/src/main/scala/playground/lsp/MainServer.scala b/modules/lsp/src/main/scala/playground/lsp/MainServer.scala index 36f33a45..a122d857 100644 --- a/modules/lsp/src/main/scala/playground/lsp/MainServer.scala +++ b/modules/lsp/src/main/scala/playground/lsp/MainServer.scala @@ -4,6 +4,7 @@ import cats.effect.implicits._ import cats.effect.kernel.Async import cats.effect.kernel.Resource import cats.effect.std +import fs2.compression.Compression import fs2.io.file.Files import fs2.io.net.Network import playground.TextDocumentManager @@ -14,7 +15,7 @@ import playground.TextDocumentManager */ object MainServer { - def makeServer[F[_]: LanguageClient: Async: Files: Network: std.Console] + def makeServer[F[_]: LanguageClient: Async: Files: Network: Compression: std.Console] : Resource[F, LanguageServer[F]] = TextDocumentManager .instance[F] .toResource diff --git a/modules/lsp/src/main/scala/playground/lsp/ServerBuilder.scala b/modules/lsp/src/main/scala/playground/lsp/ServerBuilder.scala index 5b623650..914a15ae 100644 --- a/modules/lsp/src/main/scala/playground/lsp/ServerBuilder.scala +++ b/modules/lsp/src/main/scala/playground/lsp/ServerBuilder.scala @@ -5,6 +5,7 @@ import cats.effect.kernel.Async import cats.effect.kernel.Resource import cats.effect.std import cats.implicits._ +import fs2.compression.Compression import fs2.io.file.Files import fs2.io.net.Network import org.http4s.client.Client @@ -18,11 +19,8 @@ import playground.TextDocumentManager import playground.language.CommandResultReporter import playground.std.StdlibRuntime import smithy4s.aws.AwsEnvironment -import smithy4s.aws.http4s.AwsHttp4sBackend import smithy4s.aws.kernel.AwsRegion -import scala.concurrent.duration._ - trait ServerBuilder[F[_]] { def build( @@ -38,7 +36,7 @@ object ServerBuilder { implicit F: ServerBuilder[F] ): ServerBuilder[F] = F - def instance[F[_]: Async: LanguageClient: BuildLoader: Files: Network: std.Console] + def instance[F[_]: Async: LanguageClient: BuildLoader: Files: Network: Compression: std.Console] : Resource[F, ServerBuilder[F]] = { implicit val pluginResolver: PluginResolver[F] = PluginResolver.instance[F] @@ -62,9 +60,7 @@ object ServerBuilder { client <- makeClient awsEnv <- AwsEnvironment - .default(AwsHttp4sBackend(client), AwsRegion.US_EAST_1) - // workaround for https://github.com/disneystreaming/smithy4s/issues/1075... which doesn't actually work - .timeout(1.second) + .default(client, AwsRegion.US_EAST_1) .memoize tdm <- TextDocumentManager.instance[F].toResource } yield new ServerBuilder[F] { @@ -88,7 +84,7 @@ object ServerBuilder { plugins = plugins, ) - val serviceIndex = ServiceIndex.fromServices(dsi.allServices) + val serviceIndex = ServiceIndex.fromServices(dsi.allServices.toList) implicit val sl: ServerLoader[F] = loader diff --git a/modules/lsp/src/main/scala/playground/lsp/util/SerializedSmithyModel.scala b/modules/lsp/src/main/scala/playground/lsp/util/SerializedSmithyModel.scala deleted file mode 100644 index 0f12f988..00000000 --- a/modules/lsp/src/main/scala/playground/lsp/util/SerializedSmithyModel.scala +++ /dev/null @@ -1,21 +0,0 @@ -package playground.lsp.util - -import cats.implicits._ - -case class SerializedSmithyModel( - smithy: String -) - -object SerializedSmithyModel { - import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec - import com.github.plokhotnyuk.jsoniter_scala.core._ - import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker - - private implicit val c: JsonValueCodec[SerializedSmithyModel] = JsonCodecMaker.make - - val decode: Array[Byte] => Either[Throwable, SerializedSmithyModel] = - bytes => - Either - .catchNonFatal(readFromArray[SerializedSmithyModel](bytes)) - -} diff --git a/project/plugins.sbt b/project/plugins.sbt index b6cbc9ac..43876a26 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,7 +3,8 @@ ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.11") addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.3") -addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % "0.17.11") +// try to keep in sync with smithy-build.json +addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % "0.18.0") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.1") diff --git a/smithy-build.json b/smithy-build.json index 1bdb8712..5b86edb3 100644 --- a/smithy-build.json +++ b/smithy-build.json @@ -2,7 +2,7 @@ "imports": ["modules/core/src/test/smithy"], "mavenDependencies": [ "com.disneystreaming.alloy:alloy-core:0.2.7", - "com.disneystreaming.smithy4s:smithy4s-protocol:0.17.6", - "software.amazon.smithy:smithy-aws-traits:1.34.0" + "com.disneystreaming.smithy4s:smithy4s-protocol:0.18.0", + "software.amazon.smithy:smithy-aws-traits:1.39.1" ] }