Skip to content

Commit

Permalink
Merge pull request #80 from kubukoz/deprecated-use-operaetion
Browse files Browse the repository at this point in the history
  • Loading branch information
kubukoz committed Aug 13, 2022
2 parents 1a36e44 + e312784 commit a3ccbdd
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 84 deletions.
52 changes: 21 additions & 31 deletions core/src/main/scala/playground/QueryCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import cats.data.NonEmptyList
import cats.implicits._
import playground.CompilationErrorDetails._
import playground.smithyql._
import smithy.api
import smithy.api.TimestampFormat
import smithy4s.Document
import smithy4s.Hints
Expand Down Expand Up @@ -43,10 +44,10 @@ import smithy4s.schema.Schema
import smithy4s.schema.SchemaField
import smithy4s.schema.SchemaVisitor

import java.util.UUID
import types._
import util.chaining._
import PartialCompiler.WAST
import java.util.UUID
import smithy.api

trait PartialCompiler[A] {
final def emap[B](f: A => PartialCompiler.Result[B]): PartialCompiler[B] =
Expand All @@ -55,20 +56,6 @@ trait PartialCompiler[A] {
// 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]

/** Makes all error-level diagnostics fatal on the top level of this compiler instance.
*/
def seal: PartialCompiler[A] =
ast =>
compile(ast).fold(
Ior.left(_),
Ior.right(_),
(e, a) =>
if (e.exists(_.isError))
Ior.left(e)
else
Ior.both(e, a),
)

}

object PartialCompiler {
Expand Down Expand Up @@ -148,6 +135,14 @@ object CompilationError {
range: SourceRange,
): CompilationError = default(err, range, DiagnosticSeverity.Warning)

def deprecation(
info: api.Deprecated,
range: SourceRange,
): CompilationError =
CompilationError
.warning(CompilationErrorDetails.DeprecatedItem(info), range)
.deprecated

def default(
err: CompilationErrorDetails,
range: SourceRange,
Expand All @@ -165,11 +160,9 @@ sealed trait CompilationErrorDetails extends Product with Serializable {

def render: String =
this match {
case Message(text) => text
case DeprecatedMember(info) =>
s"Deprecated union member${CompletionItem.deprecationString(info)}"
case DeprecatedField(info) => s"Deprecated field${CompletionItem.deprecationString(info)}"
case InvalidUUID => "Invalid UUID"
case Message(text) => text
case DeprecatedItem(info) => "Deprecated" + CompletionItem.deprecationString(info)
case InvalidUUID => "Invalid UUID"
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."
Expand Down Expand Up @@ -281,8 +274,8 @@ object CompilationErrorDetails {
final case class UnsupportedNode(tag: String) extends CompilationErrorDetails

case object DuplicateItem extends CompilationErrorDetails
case class DeprecatedField(info: api.Deprecated) extends CompilationErrorDetails
case class DeprecatedMember(info: api.Deprecated) extends CompilationErrorDetails

case class DeprecatedItem(info: api.Deprecated) extends CompilationErrorDetails

final case class EnumFallback(enumName: String) extends CompilationErrorDetails
}
Expand Down Expand Up @@ -439,21 +432,19 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] {
.toList
.toNel
.map(NonEmptyChain.fromNonEmptyList)
.toLeftIor(())
.toBothLeft(())
.combine(Ior.right(()))

val deprecatedFieldWarnings: PartialCompiler.Result[Unit] = presentKeys
.flatMap { key =>
deprecatedFields.get(key.value.text).map { info =>
CompilationError
.warning(CompilationErrorDetails.DeprecatedField(info), key.range)
.deprecated
CompilationError.deprecation(info, key.range)
}
}
.toList
.toNel
.map(NonEmptyChain.fromNonEmptyList)
.toLeftIor(())
.toBothLeft(())
.combine(Ior.right(()))

val buildStruct = fields
Expand Down Expand Up @@ -521,13 +512,12 @@ object QueryCompiler extends SchemaVisitor[PartialCompiler] {
.get(k.value.text)
.map { info =>
CompilationError
.warning(CompilationErrorDetails.DeprecatedMember(info), k.range)
.deprecated
.deprecation(info, k.range)
}
.toList
.toNel
.map(NonEmptyChain.fromNonEmptyList)
.toLeftIor(())
.toBothLeft(())
.combine(Ior.right(()))

op.flatMap(go(_).compile(v)) <& deprecationWarning
Expand Down
97 changes: 72 additions & 25 deletions core/src/main/scala/playground/run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import playground.smithyql.OperationName
import playground.smithyql.QualifiedIdentifier
import playground.smithyql.Query
import playground.smithyql.WithSource
import playground.std.Stdlib
import playground.std.StdlibRuntime
import smithy.api
import smithy.api.ProtocolDefinition
import smithy4s.Endpoint
import smithy4s.Service
Expand All @@ -36,8 +39,8 @@ import smithy4s.dynamic.DynamicSchemaIndex
import smithy4s.http4s.SimpleProtocolBuilder
import smithy4s.http4s.SimpleRestJsonBuilder
import smithy4s.schema.Schema
import playground.std.Stdlib
import playground.std.StdlibRuntime

import types._

trait CompiledInput {
type _Op[_, _, _, _, _]
Expand All @@ -49,7 +52,7 @@ trait CompiledInput {
def writeError: Option[NodeEncoder[E]]
def writeOutput: NodeEncoder[O]
def serviceId: QualifiedIdentifier
def endpoint: Endpoint[_Op, I, E, O, _, _]
def wrap(i: I): _Op[I, E, O, _, _]
}

object CompiledInput {
Expand Down Expand Up @@ -110,16 +113,15 @@ private class ServiceCompiler[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](

private def compileEndpoint[In, Err, Out](
e: Endpoint[Op, In, Err, Out, _, _]
): WithSource[InputNode[WithSource]] => Ior[Throwable, CompiledInput] = {
val inputCompiler = e.input.compile(QueryCompiler).seal
): WithSource[InputNode[WithSource]] => IorNel[CompilationError, CompiledInput] = {
val inputCompiler = e.input.compile(QueryCompiler)
val outputEncoder = NodeEncoder.derive(e.output)
val errorEncoder = e.errorable.map(e => NodeEncoder.derive(e.error))

ast =>
inputCompiler
.compile(ast)
.leftMap(_.toNonEmptyList)
.leftMap(CompilationFailed(_))
.map { compiled =>
new CompiledInput {
type _Op[_I, _E, _O, _SE, _SO] = Op[_I, _E, _O, _SE, _SO]
Expand All @@ -128,7 +130,8 @@ private class ServiceCompiler[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](
type O = Out
val input: I = compiled
val serviceId: QualifiedIdentifier = QualifiedIdentifier.forService(service)
val endpoint: Endpoint[Op, I, E, O, _, _] = e

def wrap(i: In): Op[In, Err, Out, _, _] = e.wrap(i)
val writeOutput: NodeEncoder[Out] = outputEncoder
val writeError: Option[NodeEncoder[Err]] = errorEncoder
val catchError: Throwable => Option[Err] = err => e.errorable.flatMap(_.liftError(err))
Expand All @@ -139,24 +142,68 @@ private class ServiceCompiler[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](
private val endpoints = service
.endpoints
.groupByNel(_.name)
.map(_.map(_.head).map(compileEndpoint(_)))
.map(_.map(_.head).map(e => (e, compileEndpoint(e))))

private def operationNotFound(q: Query[WithSource]): CompilationError = CompilationError.error(
CompilationErrorDetails
.OperationNotFound(
q.operationName.value.mapK(WithSource.unwrap),
endpoints.keys.map(OperationName[Id](_)).toList,
),
q.operationName.range,
)

private def deprecationWarnings(q: Query[WithSource]) =
q.useClause.value match {
// If the use clause is present, in normal flow it's 100% safe to assume that it matches this compiler's service.
case Some(useClause) =>
service
.hints
.get(api.Deprecated)
.map { info =>
CompilationError.deprecation(info, useClause.identifier.range)
}
.toBothLeft(())
.toIorNel

// todo: deprecated operations (here / in completions)
def compile(q: Query[WithSource]): Ior[Throwable, CompiledInput] = endpoints
.get(q.operationName.value.text)
.toRight(
CompilationFailed.one(
CompilationError.error(
CompilationErrorDetails
.OperationNotFound(
q.operationName.value.mapK(WithSource.unwrap),
endpoints.keys.map(OperationName[Id](_)).toList,
),
q.operationName.range,
)
)
)
.fold(_.leftIor, _.apply(q.input))
case None => Ior.right(())
}

private def deprecatedOperationCheck(
q: Query[WithSource],
endpoint: Endpoint[Op, _, _, _, _, _],
) =
endpoint
.hints
.get(api.Deprecated)
.map { info =>
CompilationError.deprecation(info, q.operationName.range)
}
.toBothLeft(())
.toIorNel

private def seal[A](
result: IorNel[CompilationError, A]
): IorNel[CompilationError, A] = result.fold(
Ior.left(_),
Ior.right(_),
(e, a) =>
if (e.exists(_.isError))
Ior.left(e)
else
Ior.both(e, a),
)

def compile(q: Query[WithSource]): Ior[Throwable, CompiledInput] = {
val compiled =
endpoints
.get(q.operationName.value.text)
.toRightIor(NonEmptyList.one(operationNotFound(q)))
.flatTap { case (e, _) => deprecatedOperationCheck(q, e) }
.flatMap(_._2.apply(q.input)) <& deprecationWarnings(q)

seal(compiled).leftMap(CompilationFailed(_))
}

}

Expand Down Expand Up @@ -379,7 +426,7 @@ object Runner {
private def perform[I, E, O](
interpreter: smithy4s.Interpreter[Op, F],
q: CompiledInput.Aux[I, E, O, Op],
) = Defer[F].defer(interpreter(q.endpoint.wrap(q.input))).map { response =>
) = Defer[F].defer(interpreter(q.wrap(q.input))).map { response =>
q.writeOutput.toNode(response)
}

Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/playground/types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,11 @@ import cats.data.Ior
object types {

type IorThrow[+A] = Ior[Throwable, A]

implicit final class OptionOps[E](private val opt: Option[E]) extends AnyVal {

def toBothLeft[A](a: A): Ior[E, A] = opt.fold(Ior.right[E, A](a))(Ior.both(_, a))

}

}
Loading

0 comments on commit a3ccbdd

Please sign in to comment.