From 269c68ad595024f2a17ecf3cb68deb26fbd10870 Mon Sep 17 00:00:00 2001 From: Dima Date: Thu, 29 Dec 2022 20:03:57 +0300 Subject: [PATCH] Add errors to API results, fix AquaConfig, fix types (#620) --- api/aqua-api-npm/aqua-api.d.ts | 31 ++- .../src/main/scala/aqua/api/AquaAPI.scala | 202 +++++++++--------- .../src/main/scala/aqua/api/types/Types.scala | 103 +++++++++ .../scala/aqua/backend/api/APIBackend.scala | 2 +- .../scala/aqua/definitions/Definitions.scala | 4 +- .../scala/aqua/backend/OutputService.scala | 2 +- build.sbt | 2 +- .../src/main/scala/aqua/builder/Service.scala | 3 +- .../main/scala/aqua/run/plugin/Plugin.scala | 2 +- 9 files changed, 237 insertions(+), 114 deletions(-) create mode 100644 api/aqua-api/src/main/scala/aqua/api/types/Types.scala diff --git a/api/aqua-api-npm/aqua-api.d.ts b/api/aqua-api-npm/aqua-api.d.ts index fe5d534a..a32b26e1 100644 --- a/api/aqua-api-npm/aqua-api.d.ts +++ b/api/aqua-api-npm/aqua-api.d.ts @@ -2,6 +2,7 @@ import type {FunctionCallDef, ServiceDef} from "@fluencelabs/fluence/dist/intern export class AquaConfig { constructor(logLevel: string, constants: string[], noXor: boolean, noRelay: boolean); + logLevel?: string constants?: string[] noXor?: boolean @@ -14,14 +15,36 @@ export class AquaFunction { } export class CompilationResult { - services: ServiceDef[] + services: Record functions: Record + functionCall?: AquaFunction + errors: string[] +} + +export class Input { + constructor(input: string); + + input: string +} + +export class Path { + constructor(path: string); + + path: string +} + +export class Call { + constructor(functionCall: string, + arguments: any, + input: Input | Path); + + functionCall: string + arguments: any + input: Input | Path } export class Compiler { - compileRun(functionStr: string, arguments: any, path: string, imports: string[], config?: AquaConfig): Promise; - compile(path: string, imports: string[], config?: AquaConfig): Promise; - compileString(input: string, imports: string[], config?: AquaConfig): Promise; + compile(input: Input | Path | Call, imports: string[], config?: AquaConfig): Promise; } export var Aqua: Compiler; diff --git a/api/aqua-api/src/main/scala/aqua/api/AquaAPI.scala b/api/aqua-api/src/main/scala/aqua/api/AquaAPI.scala index 43de1a79..328484d1 100644 --- a/api/aqua-api/src/main/scala/aqua/api/AquaAPI.scala +++ b/api/aqua-api/src/main/scala/aqua/api/AquaAPI.scala @@ -1,6 +1,7 @@ package aqua.api import aqua.ErrorRendering.showError +import aqua.api.types.{AquaAPIConfig, AquaConfig, AquaFunction, CompilationResult, Input} import aqua.backend.{AirFunction, Backend, Generated} import aqua.compiler.* import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId} @@ -18,7 +19,7 @@ import aqua.{AquaIO, SpanParser} import aqua.model.transform.{Transform, TransformConfig} import aqua.backend.api.APIBackend import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} -import cats.data.Validated.{invalidNec, validNec, Invalid, Valid} +import cats.data.Validated.{Invalid, Valid, invalidNec, validNec} import cats.syntax.applicative.* import cats.syntax.apply.* import cats.syntax.flatMap.* @@ -32,10 +33,11 @@ import scribe.Logging import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future +import scala.scalajs.js.| import scala.scalajs.js import scala.scalajs.js.JSConverters.* import scala.scalajs.js.annotation.* -import scala.scalajs.js.{undefined, UndefOr} +import scala.scalajs.js.{UndefOr, undefined} import aqua.js.{FunctionDefJs, ServiceDefJs, VarJson} import aqua.model.AquaContext import aqua.raw.ops.CallArrowRawTag @@ -43,53 +45,6 @@ import aqua.raw.value.{LiteralRaw, VarRaw} import aqua.res.AquaRes import cats.Applicative -@JSExportTopLevel("AquaFunction") -case class AquaFunction( - @JSExport - funcDef: FunctionDefJs, - @JSExport - script: String -) - -case class AquaAPIConfig( - logLevel: String = "info", - constants: List[String] = Nil, - noXor: Boolean = false, - noRelay: Boolean = false -) - -object AquaAPIConfig { - - def fromJS(cjs: AquaConfig): AquaAPIConfig = { - AquaAPIConfig( - cjs.logLevel.getOrElse("info"), - cjs.constants.map(_.toList).getOrElse(Nil), - cjs.noXor.getOrElse(false), - cjs.noRelay.getOrElse(false) - ) - } -} - -@JSExportTopLevel("AquaConfig") -case class AquaConfig( - @JSExport - logLevel: js.UndefOr[String], - @JSExport - constants: js.UndefOr[js.Array[String]], - @JSExport - noXor: js.UndefOr[Boolean], - @JSExport - noRelay: js.UndefOr[Boolean] -) - -@JSExportTopLevel("CompilationResult") -case class CompilationResult( - @JSExport - services: js.Array[ServiceDefJs], - @JSExport - functions: js.Dictionary[AquaFunction] -) - @JSExportTopLevel("Aqua") object AquaAPI extends App with Logging { @@ -102,86 +57,121 @@ object AquaAPI extends App with Logging { } @JSExport - def compileRun( + def compile( + input: types.Input | types.Path | types.Call, + imports: js.Array[String], + aquaConfigJS: js.UndefOr[AquaConfig] + ) = { + val aquaConfig: AquaAPIConfig = + aquaConfigJS.toOption.map(cjs => AquaAPIConfig.fromJS(cjs)).getOrElse(AquaAPIConfig()) + + val importsList = imports.toList + + input match { + case i: types.Input => compileString(i.input, importsList, aquaConfig) + case p: types.Path => compilePath(p.path, importsList, aquaConfig) + case c: types.Call => + val path = c.input match { + case i: types.Input => i.input + case p: types.Path => p.path + } + compileCall(c.functionCall, c.arguments, path, importsList, aquaConfig) + + } + } + + def compileCall( functionStr: String, arguments: js.Dynamic, pathStr: String, - imports: js.Array[String], - aquaConfigJS: js.UndefOr[AquaConfig] - ): js.Promise[AquaFunction] = { + imports: List[String], + aquaConfig: AquaAPIConfig + ): js.Promise[CompilationResult] = { implicit val aio: AquaIO[IO] = new AquaFilesIO[IO] - val aquaConfig: AquaAPIConfig = - aquaConfigJS.toOption.map(cjs => AquaAPIConfig.fromJS(cjs)).getOrElse(AquaAPIConfig()) - LogFormatter.initLogger(Some(LogLevels.levelFromString(aquaConfig.logLevel).toOption.get)) - val transformConfig = TransformConfig() + ( + LogLevels.levelFromString(aquaConfig.logLevel), + Constants.parse(aquaConfig.constants) + ).mapN { (level, constants) => - new FuncCompiler[IO]( - Some(RelativePath(Path(pathStr))), - imports.toList.map(Path.apply), - transformConfig - ).compile().map { contextV => - contextV.andThen { context => - CliFunc.fromString(functionStr).leftMap(errs => NonEmptyChain.fromNonEmptyList(errs)).andThen { cliFunc => - FuncCompiler.findFunction(context, cliFunc).andThen { arrow => - VarJson.checkDataGetServices(cliFunc.args, Some(arguments)).andThen { - case (argsWithTypes, _) => - val func = cliFunc.copy(args = argsWithTypes) - val preparer = new RunPreparer( - func, - arrow, - transformConfig - ) - preparer.prepare().map { ci => - AquaFunction(FunctionDefJs(ci.definitions), ci.air) + val transformConfig = aquaConfig.getTransformConfig.copy(constants = constants) + + LogFormatter.initLogger(Some(level)) + + new FuncCompiler[IO]( + Some(RelativePath(Path(pathStr))), + imports.toList.map(Path.apply), + transformConfig + ).compile() + .map { contextV => + contextV.andThen { context => + CliFunc + .fromString(functionStr) + .leftMap(errs => NonEmptyChain.fromNonEmptyList(errs)) + .andThen { cliFunc => + FuncCompiler.findFunction(context, cliFunc).andThen { arrow => + VarJson.checkDataGetServices(cliFunc.args, Some(arguments)).andThen { + case (argsWithTypes, _) => + val func = cliFunc.copy(args = argsWithTypes) + val preparer = new RunPreparer( + func, + arrow, + transformConfig + ) + preparer.prepare().map { ci => + AquaFunction(FunctionDefJs(ci.definitions), ci.air) + } + } } - } + } } } - } - }.flatMap { - case Valid(result) => IO.pure(result) - case Invalid(err) => - err.map(_.show).distinct.map(OutputPrinter.errorF[IO]).sequence - IO.raiseError[AquaFunction](new Error("Compilation failed.")) - }.unsafeToFuture().toJSPromise + .flatMap { + case Valid(result) => IO.pure(CompilationResult.result(js.Dictionary(), js.Dictionary(), Some(result))) + case Invalid(err) => + val errs = err.map(_.show).distinct.toChain.toList + IO.pure(CompilationResult.errs(errs)) + } + .unsafeToFuture() + .toJSPromise + } match { + case Valid(pr) => pr + case Invalid(err) => js.Promise.resolve(CompilationResult.errs(err.toList)) + } } - @JSExport - def compile( + def compilePath( pathStr: String, - imports: js.Array[String], - aquaConfigJS: js.UndefOr[AquaConfig] + imports: List[String], + aquaConfig: AquaAPIConfig ): js.Promise[CompilationResult] = { implicit val aio: AquaIO[IO] = new AquaFilesIO[IO] val path = Path(pathStr) - val sources = new AquaFileSources[IO](path, imports.toList.map(Path.apply)) - compileRaw(aquaConfigJS, sources) + val sources = new AquaFileSources[IO](path, imports.map(Path.apply)) + compileRaw(aquaConfig, sources) } - @JSExport def compileString( input: String, - imports: js.Array[String], - aquaConfigJS: js.UndefOr[AquaConfig] + imports: List[String], + aquaConfig: AquaAPIConfig ): js.Promise[CompilationResult] = { implicit val aio: AquaIO[IO] = new AquaFilesIO[IO] val path = Path("") + val strSources: AquaFileSources[IO] = - new AquaFileSources[IO](path, imports.toList.map(Path.apply)) { + new AquaFileSources[IO](path, imports.map(Path.apply)) { override def sources: IO[ValidatedNec[AquaFileError, Chain[(FileModuleId, String)]]] = { IO.pure(Valid(Chain.one((FileModuleId(path), input)))) } } - compileRaw(aquaConfigJS, strSources) + compileRaw(aquaConfig, strSources) } def compileRaw( - aquaConfigJS: js.UndefOr[AquaConfig], + aquaConfig: AquaAPIConfig, sources: AquaSources[IO, AquaFileError, FileModuleId] ): js.Promise[CompilationResult] = { - val aquaConfig = - aquaConfigJS.toOption.map(cjs => AquaAPIConfig.fromJS(cjs)).getOrElse(AquaAPIConfig()) ( LogLevels.levelFromString(aquaConfig.logLevel), @@ -191,7 +181,7 @@ object AquaAPI extends App with Logging { LogFormatter.initLogger(Some(level)) val config = AquaCompilerConf(constants) - val transformConfig = TransformConfig() + val transformConfig = aquaConfig.getTransformConfig val proc = for { res <- CompilerAPI @@ -214,15 +204,21 @@ object AquaAPI extends App with Logging { jsResult <- res match { case Valid(compiled) => val allGenerated: List[Generated] = compiled.toList.flatMap(_.compiled) - val serviceDefs = allGenerated.flatMap(_.services).map(s => ServiceDefJs(s)).toJSArray + val serviceDefs = allGenerated.flatMap(_.services).map(s => s.name -> ServiceDefJs(s)) val functions = allGenerated.flatMap( _.air.map(as => (as.name, AquaFunction(FunctionDefJs(as.funcDef), as.air))) ) - IO.pure(CompilationResult(serviceDefs, js.Dictionary.apply(functions: _*))) + IO.pure( + CompilationResult.result( + js.Dictionary.apply(serviceDefs: _*), + js.Dictionary.apply(functions: _*), + None + ) + ) case Invalid(errChain) => - errChain.map(_.show).distinct.map(OutputPrinter.errorF[IO]).sequence - IO.raiseError[CompilationResult](new Error("Compilation failed.")) + val errors = errChain.map(_.show).distinct.toChain.toList + IO.pure[CompilationResult](CompilationResult.errs(errors)) } } yield { jsResult @@ -231,7 +227,7 @@ object AquaAPI extends App with Logging { proc.unsafeToFuture().toJSPromise } match { case Valid(pr) => pr - case Invalid(err) => js.Promise.reject(err) + case Invalid(err) => js.Promise.resolve(CompilationResult.errs(err.toList)) } } } diff --git a/api/aqua-api/src/main/scala/aqua/api/types/Types.scala b/api/aqua-api/src/main/scala/aqua/api/types/Types.scala new file mode 100644 index 00000000..743eaba4 --- /dev/null +++ b/api/aqua-api/src/main/scala/aqua/api/types/Types.scala @@ -0,0 +1,103 @@ +package aqua.api.types + +import aqua.api.* +import aqua.js.{FunctionDefJs, ServiceDefJs} +import aqua.model.transform.TransformConfig + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel} +import scala.scalajs.js.| +import scala.scalajs.js.JSConverters.* + +@JSExportTopLevel("AquaFunction") +case class AquaFunction( + @JSExport + funcDef: FunctionDefJs, + @JSExport + script: String +) + +@JSExportTopLevel("Input") +class Input( + @JSExport + val input: String +) + +@JSExportTopLevel("Path") +class Path( + @JSExport + val path: String +) + +@JSExportTopLevel("Call") +class Call( + @JSExport + val functionCall: String, + @JSExport + val arguments: js.Dynamic, + @JSExport + val input: Path | Input +) + +@JSExportTopLevel("AquaConfig") +class AquaConfig( + @JSExport + val logLevel: js.UndefOr[String], + @JSExport + val constants: js.UndefOr[js.Array[String]], + @JSExport + val noXor: js.UndefOr[Boolean], + @JSExport + val noRelay: js.UndefOr[Boolean] +) + +@JSExportTopLevel("CompilationResult") +class CompilationResult( + @JSExport + val services: js.Dictionary[ServiceDefJs], + @JSExport + val functions: js.Dictionary[AquaFunction], + @JSExport + val functionCall: js.UndefOr[AquaFunction], + @JSExport + val errors: js.Array[String] +) + +object CompilationResult { + + def result( + services: js.Dictionary[ServiceDefJs], + functions: js.Dictionary[AquaFunction], + call: Option[AquaFunction] + ): CompilationResult = + new CompilationResult(services, functions, call.orNull, js.Array()) + + def errs( + errors: List[String] + ): CompilationResult = + CompilationResult(js.Dictionary(), js.Dictionary(), null, errors.toJSArray) +} + +case class AquaAPIConfig( + logLevel: String = "info", + constants: List[String] = Nil, + noXor: Boolean = false, + noRelay: Boolean = false +) { + + def getTransformConfig: TransformConfig = + if (noRelay) TransformConfig(relayVarName = None, wrapWithXor = !noXor) + else TransformConfig(wrapWithXor = !noXor) +} + +object AquaAPIConfig { + + def fromJS(cjs: AquaConfig): AquaAPIConfig = { + AquaAPIConfig( + cjs.logLevel.getOrElse("info"), + cjs.constants.map(_.toList).getOrElse(Nil), + cjs.noXor.getOrElse(false), + cjs.noRelay.getOrElse(false) + ) + } +} diff --git a/backend/api/src/main/scala/aqua/backend/api/APIBackend.scala b/backend/api/src/main/scala/aqua/backend/api/APIBackend.scala index 61d993cb..a8d683e8 100644 --- a/backend/api/src/main/scala/aqua/backend/api/APIBackend.scala +++ b/backend/api/src/main/scala/aqua/backend/api/APIBackend.scala @@ -17,7 +17,7 @@ object APIBackend extends Backend { srv.members.map { case (n, a) => (n, ArrowTypeDef(a)) } ) - ServiceDef(srv.defaultId.map(s => s.replace("\"", "")), functions) + ServiceDef(srv.defaultId.map(s => s.replace("\"", "")), functions, srv.name) }.toList Generated("", "", airGenerated.flatMap(_.air).toList, services) :: Nil diff --git a/backend/definitions/src/main/scala/aqua/definitions/Definitions.scala b/backend/definitions/src/main/scala/aqua/definitions/Definitions.scala index e4e65858..3cd16e27 100644 --- a/backend/definitions/src/main/scala/aqua/definitions/Definitions.scala +++ b/backend/definitions/src/main/scala/aqua/definitions/Definitions.scala @@ -68,7 +68,7 @@ object TypeDefinition { ) } - implicit val encodeServiceDefType: Encoder[ServiceDef] = { case ServiceDef(sId, functions) => + implicit val encodeServiceDefType: Encoder[ServiceDef] = { case ServiceDef(sId, functions, name) => Json.obj( ("defaultServiceId", sId.asJson), ("functions", encodeProdDefType(functions)) @@ -195,7 +195,7 @@ case class NamesConfig( ) // Describes service -case class ServiceDef(defaultServiceId: Option[String], functions: LabeledProductTypeDef) +case class ServiceDef(defaultServiceId: Option[String], functions: LabeledProductTypeDef, name: String) // Describes top-level function case class FunctionDef( diff --git a/backend/ts/src/main/scala/aqua/backend/OutputService.scala b/backend/ts/src/main/scala/aqua/backend/OutputService.scala index a52d8b23..e996cdcd 100644 --- a/backend/ts/src/main/scala/aqua/backend/OutputService.scala +++ b/backend/ts/src/main/scala/aqua/backend/OutputService.scala @@ -22,7 +22,7 @@ case class OutputService(srv: ServiceRes, types: Types) { srv.members.map { case (n, a) => (n, ArrowTypeDef(a)) } ) - val serviceDef = ServiceDef(srv.defaultId.map(s => s.replace("\"", "")), functions) + val serviceDef = ServiceDef(srv.defaultId.map(s => s.replace("\"", "")), functions, srv.name) s""" |${serviceTypes.generate} diff --git a/build.sbt b/build.sbt index 0618bec1..0c4aa131 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ val scribeV = "3.7.1" name := "aqua-hll" val commons = Seq( - baseAquaVersion := "0.9.0", + baseAquaVersion := "0.9.1", version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"), scalaVersion := dottyVersion, libraryDependencies ++= Seq( diff --git a/cli/cli/.js/src/main/scala/aqua/builder/Service.scala b/cli/cli/.js/src/main/scala/aqua/builder/Service.scala index 1ee01277..30dbc72b 100644 --- a/cli/cli/.js/src/main/scala/aqua/builder/Service.scala +++ b/cli/cli/.js/src/main/scala/aqua/builder/Service.scala @@ -27,7 +27,8 @@ class Service(serviceId: String, functions: NonEmptyList[AquaFunction]) extends handlers.toList, ServiceDef( None, - defs + defs, + "" ) ) } diff --git a/cli/cli/.js/src/main/scala/aqua/run/plugin/Plugin.scala b/cli/cli/.js/src/main/scala/aqua/run/plugin/Plugin.scala index 1892f325..5da4091c 100644 --- a/cli/cli/.js/src/main/scala/aqua/run/plugin/Plugin.scala +++ b/cli/cli/.js/src/main/scala/aqua/run/plugin/Plugin.scala @@ -49,7 +49,7 @@ case class Plugin(name: String, functions: List[Function]) { peer, name, handlers, - ServiceDef(Some(name), LabeledProductTypeDef(funcTypes)) + ServiceDef(Some(name), LabeledProductTypeDef(funcTypes), "") ) } }