From 40f44be858584014969de6dec2fae6ddf76eac5f Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 3 Mar 2022 16:28:04 +0300 Subject: [PATCH] Update the Language Server API (#3308) --- .../Standard/Searcher/0.0.0-dev/package.yaml | 2 +- .../protocol-language-server.md | 104 +++-------- .../libraries/ComponentGroupsResolver.scala | 29 +-- .../languageserver/libraries/LibraryApi.scala | 6 +- .../libraries/LibraryComponentGroup.scala | 154 +++++++++++++++- .../libraries/LocalLibraryManager.scala | 12 +- .../LocalLibraryManagerFailureMapper.scala | 18 ++ .../LocalLibraryManagerProtocol.scala | 1 + .../EditionsListAvailableHandler.scala | 8 +- ...EditionsListDefinedComponentsHandler.scala | 8 +- .../EditionsListDefinedLibrariesHandler.scala | 7 +- .../handler/LibraryGetMetadataHandler.scala | 6 +- .../handler/LibraryGetPackageHandler.scala | 21 ++- .../ComponentGroupsResolverSpec.scala | 2 +- .../websocket/json/LibrariesTest.scala | 61 +++++- .../updater/UpdatingEditionProvider.scala | 4 +- .../provider/EditionLoadingError.scala | 2 +- .../provider/FileSystemEditionProvider.scala | 6 +- .../enso/editions/EditionResolverSpec.scala | 2 +- .../scala/org/enso/pkg/ComponentGroup.scala | 174 +++++++++++------- .../scala/org/enso/pkg/ConfigCodecs.scala | 66 ++----- .../test/scala/org/enso/pkg/ConfigSpec.scala | 89 ++++----- 22 files changed, 480 insertions(+), 302 deletions(-) diff --git a/distribution/lib/Standard/Searcher/0.0.0-dev/package.yaml b/distribution/lib/Standard/Searcher/0.0.0-dev/package.yaml index 6f3c3d6279d..7815a288f7a 100644 --- a/distribution/lib/Standard/Searcher/0.0.0-dev/package.yaml +++ b/distribution/lib/Standard/Searcher/0.0.0-dev/package.yaml @@ -1,4 +1,4 @@ -name: Base +name: Searcher namespace: Standard version: 0.0.0-dev license: APLv2 diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 79a147d76a7..9cf93ac8b7f 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -62,12 +62,9 @@ transport formats, please look [here](./protocol-architecture). - [`LibraryVersion`](#libraryversion) - [`Contact`](#contact) - [`EditionReference`](#editionreference) - - [`ComponentGroups`](#componentgroups) - - [`ComponentGroup`](#componentgroup) - - [`ExtendedComponentGroup`](#extendedcomponentgroup) - - [`ModuleReference`](#modulereference) - - [`Component`](#component) + - [`LibraryComponentGroups`](#librarycomponentgroups) - [`LibraryComponentGroup`](#librarycomponentgroup) + - [`LibraryComponent`](#librarycomponent) - [Connection Management](#connection-management) - [`session/initProtocolConnection`](#sessioninitprotocolconnection) - [`session/initBinaryConnection`](#sessioninitbinaryconnection) @@ -1427,84 +1424,18 @@ interface NamedEdition { } ``` -### `ComponentGroups` +### `LibraryComponentGroups` The description of component groups provided by the package. Object fields can be omitted if the corresponding list is empty. ```typescript -interface ComponentGroups { +interface LibraryComponentGroups { /** The list of component groups provided by the package. */ - newGroups?: ComponentGroup[]; + newGroups?: LibraryComponentGroup[]; /** The list of component groups that this package extends.*/ - extendedGroups?: ExtendedComponentGroup[]; -} -``` - -### `ComponentGroup` - -The definition of a single component group. - -```typescript -interface ComponentGroup { - /** The module name containing the declared componennts. */ - module: string; - - color?: string; - - icon?: string; - - /** The list of components provided by this component group. */ - exports: Component[]; -} -``` - -### `ExtendedComponentGroup` - -The definition of a component group that extends an existing one. - -```typescript -interface ExtendedComponentGroup { - /** The reference to the component group module being extended. */ - module: ModuleReference; - - /** The list of components provided by this component group. */ - exports: Component[]; -} -``` - -### `ModuleReference` - -The reference to a module. - -```typescript -interface ModuleReference { - /** - * A string consisting of a namespace and a lirary name separated by the dot - * ., i.e. `Standard.Base`. - */ - libraryName: string; - - /** The module name without the library name prefix. - * E.g. given the `Standard.Base.Data.Vector` module reference, - * the `moduleName` field contains `Data.Vector`. - */ - moduleName: string; -} -``` - -### `Component` - -A single component of a component group. - -```typescript -interface Component { - /** The component name. */ - name: string; - - /** The component shortcut. */ - shortcut?: string; + extendedGroups?: LibraryComponentGroup[]; } ``` @@ -1515,8 +1446,9 @@ The component group provided by a library. ```typescript interface LibraryComponentGroup { /** - * A string consisting of a namespace and a lirary name separated by the dot - * ., i.e. `Standard.Base`. + * Thf fully qualified module name. A string consisting of a namespace and + * a library name separated by the dot ., + * i.e. `Standard.Base`. */ library: string; @@ -1531,7 +1463,21 @@ interface LibraryComponentGroup { icon?: string; /** The list of components provided by this component group. */ - exports: Component[]; + exports: LibraryComponent[]; +} +``` + +### `LibraryComponent` + +A single component of a component group. + +```typescript +interface LibraryComponent { + /** The component name. */ + name: string; + + /** The component shortcut. */ + shortcut?: string; } ``` @@ -4620,7 +4566,7 @@ All returned fields are optional, as they may be missing. ```typescript { license?: String; - componentGroups?: ComponentGroups; + componentGroups?: LibraryComponentGroups; } ``` diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ComponentGroupsResolver.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ComponentGroupsResolver.scala index 42b0219a331..d26251d5a07 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ComponentGroupsResolver.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ComponentGroupsResolver.scala @@ -2,7 +2,6 @@ package org.enso.languageserver.libraries import org.enso.editions.LibraryName import org.enso.pkg.{ - ComponentGroup, ComponentGroups, Config, ExtendedComponentGroup, @@ -53,7 +52,9 @@ final class ComponentGroupsResolver { val newLibraryComponentGroups: View[LibraryComponentGroup] = libraryComponents.view .flatMap { case (libraryName, componentGroups) => - componentGroups.newGroups.map(toLibraryComponentGroup(libraryName, _)) + componentGroups.newGroups.map( + LibraryComponentGroup.fromComponentGroup(libraryName, _) + ) } val newLibraryComponentGroupsMap : Map[ModuleReference, LibraryComponentGroup] = @@ -114,29 +115,9 @@ final class ComponentGroupsResolver { extendedComponentGroup: ExtendedComponentGroup ): LibraryComponentGroup = libraryComponentGroup.copy( - exports = libraryComponentGroup.exports :++ extendedComponentGroup.exports + exports = libraryComponentGroup.exports :++ + extendedComponentGroup.exports.map(LibraryComponent.fromComponent) ) - - /** Convert [[ComponentGroup]] to [[LibraryComponentGroup]] representation. - * - * @param libraryName the library name defining this component group - * @param componentGroup the component group to convert - * @return the [[LibraryComponentGroup]] representation of this component - * group - */ - private def toLibraryComponentGroup( - libraryName: LibraryName, - componentGroup: ComponentGroup - ): LibraryComponentGroup = { - LibraryComponentGroup( - libraryName, - componentGroup.module, - componentGroup.color, - componentGroup.icon, - componentGroup.exports - ) - } - } object ComponentGroupsResolver { diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala index 1ed43825c09..555e2e0636d 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala @@ -1,10 +1,10 @@ package org.enso.languageserver.libraries -import io.circe.{Json, JsonObject} import io.circe.literal.JsonStringContext +import io.circe.{Json, JsonObject} import org.enso.editions.{LibraryName, LibraryVersion} import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused} -import org.enso.pkg.{ComponentGroups, Contact} +import org.enso.pkg.Contact object LibraryApi { case object EditionsListAvailable extends Method("editions/listAvailable") { @@ -190,7 +190,7 @@ object LibraryApi { // should be removed when the integration with IDE is finished case class Result( license: Option[String], - componentGroups: Option[ComponentGroups], + componentGroups: Option[LibraryComponentGroups], raw: JsonObject ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryComponentGroup.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryComponentGroup.scala index 9eba8be2637..7c18f3d325d 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryComponentGroup.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryComponentGroup.scala @@ -3,9 +3,77 @@ package org.enso.languageserver.libraries import io.circe._ import io.circe.syntax._ import org.enso.editions.LibraryName -import org.enso.pkg.{Component, ModuleName} +import org.enso.pkg.{ + Component, + ComponentGroup, + ComponentGroups, + ExtendedComponentGroup, + ModuleName, + Shortcut +} -/** The component group definition of a library. +/** The description of component groups provided by the package. This + * representation is used in the JSONRPC API. + * + * @param newGroups the list of component groups provided by the package + * @param extendedGroups the list of component groups that this package extends + */ +case class LibraryComponentGroups( + newGroups: List[LibraryComponentGroup], + extendedGroups: List[LibraryComponentGroup] +) +object LibraryComponentGroups { + + /** Create a [[LibraryComponentGroups]] from the provided [[ComponentGroups]]. + * + * @param libraryName the library name defining these component groups + * @param componentGroups the component groups to convert + * @return the [[LibraryComponentGroups]] representation of the provided + * component groups + */ + def fromComponentGroups( + libraryName: LibraryName, + componentGroups: ComponentGroups + ): LibraryComponentGroups = + LibraryComponentGroups( + componentGroups.newGroups.map( + LibraryComponentGroup.fromComponentGroup(libraryName, _) + ), + componentGroups.extendedGroups.map( + LibraryComponentGroup.fromExtendedComponentGroup + ) + ) + + /** Fields for use when serializing the [[LibraryComponentGroups]]. */ + private object Fields { + val newGroups = "newGroups" + val extendedGroups = "extendedGroups" + } + + /** [[Encoder]] instance for the [[LibraryComponentGroups]]. */ + implicit val encoder: Encoder[LibraryComponentGroups] = { componentGroups => + val newGroups = Option.unless(componentGroups.newGroups.isEmpty)( + Fields.newGroups -> componentGroups.newGroups.asJson + ) + val extendedGroups = Option.unless(componentGroups.extendedGroups.isEmpty)( + Fields.extendedGroups -> componentGroups.extendedGroups.asJson + ) + Json.obj(newGroups.toSeq ++ extendedGroups.toSeq: _*) + } + + /** [[Decoder]] instance for the [[LibraryComponentGroups]]. */ + implicit val decoder: Decoder[LibraryComponentGroups] = { json => + for { + newGroups <- json + .getOrElse[List[LibraryComponentGroup]](Fields.newGroups)(List()) + extendedGroups <- json + .getOrElse[List[LibraryComponentGroup]](Fields.extendedGroups)(List()) + } yield LibraryComponentGroups(newGroups, extendedGroups) + } +} + +/** The component group definition of a library. This representation is used in + * the JSONRPC API. * * @param library the library name * @param module the module name @@ -18,10 +86,47 @@ case class LibraryComponentGroup( module: ModuleName, color: Option[String], icon: Option[String], - exports: Seq[Component] + exports: Seq[LibraryComponent] ) object LibraryComponentGroup { + /** create a [[LibraryComponentGroup]] from the provided [[ComponentGroup]]. + * + * @param libraryName the library name defining this component group + * @param componentGroup the component group to convert + * @return the [[LibraryComponentGroup]] representation of this component + * group + */ + def fromComponentGroup( + libraryName: LibraryName, + componentGroup: ComponentGroup + ): LibraryComponentGroup = + LibraryComponentGroup( + library = libraryName, + module = componentGroup.module, + color = componentGroup.color, + icon = componentGroup.icon, + exports = componentGroup.exports.map(LibraryComponent.fromComponent) + ) + + /** Create a [[LibraryComponentGroup]] from the [[ExtendedComponentGroup]]. + * + * @param extendedComponentGroup the extended component group to convert + * @return the [[LibraryComponentGroup]] representation of the provided + * extended component group + */ + def fromExtendedComponentGroup( + extendedComponentGroup: ExtendedComponentGroup + ): LibraryComponentGroup = + LibraryComponentGroup( + library = extendedComponentGroup.module.libraryName, + module = extendedComponentGroup.module.moduleName, + color = None, + icon = None, + exports = + extendedComponentGroup.exports.map(LibraryComponent.fromComponent) + ) + /** Fields for use when serializing the [[LibraryComponentGroup]]. */ private object Fields { val Library = "library" @@ -52,8 +157,47 @@ object LibraryComponentGroup { module <- json.get[ModuleName](Fields.Module) color <- json.get[Option[String]](Fields.Color) icon <- json.get[Option[String]](Fields.Icon) - exports <- json.getOrElse[List[Component]](Fields.Exports)(List()) + exports <- json.getOrElse[List[LibraryComponent]](Fields.Exports)(List()) } yield LibraryComponentGroup(library, module, color, icon, exports) } - +} + +/** A single component of a component group. This representation is used in + * the JSONRPC API. + * + * @param name the component name + * @param shortcut the component shortcut + */ +case class LibraryComponent(name: String, shortcut: Option[Shortcut]) +object LibraryComponent { + + /** Create a [[LibraryComponent]] from the provided [[Component]]. + * + * @param component the component to convert + * @return the [[LibraryComponent]] representation of this component + */ + def fromComponent(component: Component): LibraryComponent = + LibraryComponent(component.name, component.shortcut) + + object Fields { + val Name = "name" + val Shortcut = "shortcut" + } + + /** [[Encoder]] instance for the [[LibraryComponent]]. */ + implicit val encoder: Encoder[LibraryComponent] = { component => + val shortcut = component.shortcut.map(Fields.Shortcut -> _.asJson) + Json.obj( + Seq(Fields.Name -> component.name.asJson) ++ + shortcut.toSeq: _* + ) + } + + /** [[Decoder]] instance for the [[LibraryComponent]]. */ + implicit val decoder: Decoder[LibraryComponent] = { json => + for { + name <- json.get[String](Fields.Name) + shortcut <- json.getOrElse[Option[Shortcut]](Fields.Shortcut)(None) + } yield LibraryComponent(name, shortcut) + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala index 18311442a58..ae0ec3c21a8 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -202,12 +202,16 @@ class LocalLibraryManager( configPath = libraryRootPath / Package.configFileName config <- loadPackageConfig(configPath) } yield { - if (config.componentGroups.isLeft) { - logger.error( - s"Failed to parse library [$libraryName] component groups." - ) + config.componentGroups match { + case Left(error) => + logger.error( + s"Failed to parse library [$libraryName] component groups " + + s"(reason: ${error.message})." + ) + case _ => } GetPackageResponse( + libraryName = LibraryName(config.namespace, config.name), license = config.license, componentGroups = config.componentGroups.toOption, rawPackage = config.originalJson diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerFailureMapper.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerFailureMapper.scala index 5bb0e696c2c..c113426eaac 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerFailureMapper.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerFailureMapper.scala @@ -1,6 +1,8 @@ package org.enso.languageserver.libraries +import org.enso.editions.provider.EditionNotFound import org.enso.jsonrpc +import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError /** The object mapping the [[LocalLibraryManagerProtocol]] failures into the * corresponding JSONRPC error messages. @@ -18,4 +20,20 @@ object LocalLibraryManagerFailureMapper { case LocalLibraryManagerProtocol.InvalidSemverVersionError(version) => LibraryApi.InvalidSemverVersion(version) } + + /** Convert the exceptions raised in the library management api to the + * corresponding JSONRPC errors. + * + * @param error the raised exception + * @return the JSONRPC error message + */ + def mapException(error: Throwable): jsonrpc.Error = + error match { + case ex: LocalLibraryManagerProtocol.LocalLibraryNotFoundError => + LibraryApi.LocalLibraryNotFound(ex.libraryName) + case ex: EditionNotFound => + LibraryApi.EditionNotFoundError(ex.editionName) + case _ => + FileSystemError(error.getMessage) + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala index 050ad52a80f..e7e110682af 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala @@ -31,6 +31,7 @@ object LocalLibraryManagerProtocol { /** A response to the [[GetPackage]] request. */ case class GetPackageResponse( + libraryName: LibraryName, license: String, componentGroups: Option[ComponentGroups], rawPackage: JsonObject diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala index af6db4c13c1..c1104bb83bb 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala @@ -5,8 +5,10 @@ import akka.pattern.pipe import com.typesafe.scalalogging.LazyLogging import org.enso.editions.updater.EditionManager import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult} -import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError -import org.enso.languageserver.libraries.BlockingOperation +import org.enso.languageserver.libraries.{ + BlockingOperation, + LocalLibraryManagerFailureMapper +} import org.enso.languageserver.libraries.LibraryApi._ import org.enso.languageserver.util.UnhandledLogging @@ -49,7 +51,7 @@ class EditionsListAvailableHandler(editionManager: EditionManager) case Status.Failure(exception) => replyTo ! ResponseError( Some(id), - FileSystemError(exception.toString) + LocalLibraryManagerFailureMapper.mapException(exception) ) context.stop(self) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedComponentsHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedComponentsHandler.scala index 4bfdbf196b9..569d0455f69 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedComponentsHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedComponentsHandler.scala @@ -5,14 +5,14 @@ import akka.pattern.pipe import com.typesafe.scalalogging.LazyLogging import org.enso.editions.{LibraryName, LibraryVersion} import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult} -import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError import org.enso.languageserver.libraries.LibraryApi._ import org.enso.languageserver.libraries.{ BlockingOperation, ComponentGroupsResolver, ComponentGroupsValidator, EditionReferenceResolver, - LibraryComponentGroup + LibraryComponentGroup, + LocalLibraryManagerFailureMapper } import org.enso.languageserver.util.UnhandledLogging import org.enso.librarymanager.local.LocalLibraryProvider @@ -50,7 +50,7 @@ class EditionsListDefinedComponentsHandler( BlockingOperation .run { val edition = editionReferenceResolver.resolveEdition(reference).get - val definedLibraries = edition.getAllDefinedLibraries.view + val definedLibraries = edition.getAllDefinedLibraries.toSeq .map { case (name, version) => readLocalPackage(name, version) } @@ -109,7 +109,7 @@ class EditionsListDefinedComponentsHandler( case Status.Failure(exception) => replyTo ! ResponseError( Some(id), - FileSystemError(exception.getMessage) + LocalLibraryManagerFailureMapper.mapException(exception) ) context.stop(self) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala index aff79edb911..dccce560c9a 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala @@ -5,12 +5,12 @@ import akka.pattern.pipe import com.typesafe.scalalogging.LazyLogging import org.enso.editions.LibraryVersion import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult} -import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError import org.enso.languageserver.libraries.LibraryApi._ import org.enso.languageserver.libraries.{ BlockingOperation, EditionReferenceResolver, - LibraryEntry + LibraryEntry, + LocalLibraryManagerFailureMapper } import org.enso.languageserver.util.UnhandledLogging import org.enso.librarymanager.local.LocalLibraryProvider @@ -74,10 +74,9 @@ class EditionsListDefinedLibrariesHandler( context.stop(self) case Status.Failure(exception) => - // TODO [RW] more detailed errors replyTo ! ResponseError( Some(id), - FileSystemError(exception.getMessage) + LocalLibraryManagerFailureMapper.mapException(exception) ) context.stop(self) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala index 1ce49c9e815..0abf8ff9831 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala @@ -9,7 +9,6 @@ import nl.gn0s1s.bump.SemVer import org.enso.editions.Editions.Repository import org.enso.editions.LibraryName import org.enso.jsonrpc._ -import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError import org.enso.languageserver.libraries.LibraryApi._ import org.enso.languageserver.libraries.{ LibraryEntry, @@ -101,7 +100,10 @@ class LibraryGetMetadataHandler( context.stop(self) case Status.Failure(exception) => - replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage)) + replyTo ! ResponseError( + Some(id), + LocalLibraryManagerFailureMapper.mapException(exception) + ) cancellable.cancel() context.stop(self) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetPackageHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetPackageHandler.scala index f410046a3e7..99805088d59 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetPackageHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetPackageHandler.scala @@ -7,13 +7,8 @@ import nl.gn0s1s.bump.SemVer import org.enso.editions.Editions.Repository import org.enso.editions.LibraryName import org.enso.jsonrpc._ -import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError import org.enso.languageserver.libraries.LibraryApi._ -import org.enso.languageserver.libraries.{ - LibraryEntry, - LocalLibraryManagerFailureMapper, - LocalLibraryManagerProtocol -} +import org.enso.languageserver.libraries._ import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.util.UnhandledLogging import org.enso.librarymanager.published.PublishedLibraryCache @@ -83,6 +78,7 @@ class LibraryGetPackageHandler( context.stop(self) case LocalLibraryManagerProtocol.GetPackageResponse( + libraryName, license, componentGroups, rawPackage @@ -92,7 +88,11 @@ class LibraryGetPackageHandler( id, LibraryGetPackage.Result( Option.unless(license.isEmpty)(license), - componentGroups, + componentGroups.flatMap { groups => + Option.unless( + groups.newGroups.isEmpty && groups.extendedGroups.isEmpty + )(LibraryComponentGroups.fromComponentGroups(libraryName, groups)) + }, rawPackage ) ) @@ -105,7 +105,10 @@ class LibraryGetPackageHandler( context.stop(self) case Status.Failure(exception) => - replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage)) + replyTo ! ResponseError( + Some(id), + LocalLibraryManagerFailureMapper.mapException(exception) + ) cancellable.cancel() context.stop(self) } @@ -133,6 +136,7 @@ class LibraryGetPackageHandler( .readPackage() .map(config => LocalLibraryManagerProtocol.GetPackageResponse( + LibraryName(config.namespace, config.name), config.license, config.componentGroups.toOption, config.originalJson @@ -150,6 +154,7 @@ class LibraryGetPackageHandler( .fetchPackageConfig() .toFuture } yield LocalLibraryManagerProtocol.GetPackageResponse( + libraryName = LibraryName(config.namespace, config.name), license = config.license, componentGroups = config.componentGroups.toOption, rawPackage = config.originalJson diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/libraries/ComponentGroupsResolverSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/libraries/ComponentGroupsResolverSpec.scala index 4b0cd5017c3..575cebb96dd 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/libraries/ComponentGroupsResolverSpec.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/libraries/ComponentGroupsResolverSpec.scala @@ -311,6 +311,6 @@ object ComponentGroupsResolverSpec { module = ModuleName(module), color = None, icon = None, - exports = exports.map(Component(_, None)) + exports = exports.map(LibraryComponent(_, None)) ) } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala index cdfa98b7826..6d543714e72 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala @@ -5,7 +5,7 @@ import io.circe.{Json, JsonObject} import nl.gn0s1s.bump.SemVer import org.enso.distribution.FileSystem import org.enso.editions.{Editions, LibraryName} -import org.enso.languageserver.libraries.LibraryEntry +import org.enso.languageserver.libraries.{LibraryComponentGroups, LibraryEntry} import org.enso.languageserver.libraries.LibraryEntry.PublishedLibraryVersion import org.enso.librarymanager.published.bundles.LocalReadOnlyRepository import org.enso.librarymanager.published.repository.{ @@ -227,6 +227,32 @@ class LibrariesTest extends BaseServerTest { """) } + "return LibraryNotFound error when getting the metadata of unknown library" in { + val client = getInitialisedWsClient() + client.send(json""" + { "jsonrpc": "2.0", + "method": "library/getMetadata", + "id": 0, + "params": { + "namespace": "user", + "name": "Get_Package_Unknown", + "version": { + "type": "LocalLibraryVersion" + } + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 0, + "error": { + "code": 8007, + "message": "Local library [user.Get_Package_Unknown] has not been found." + } + } + """) + } + "get the package config" in { val client = getInitialisedWsClient() val testComponentGroups = ComponentGroups( @@ -313,8 +339,37 @@ class LibrariesTest extends BaseServerTest { response.hcursor .downField("result") .downField("componentGroups") - .as[ComponentGroups] - .rightValue shouldEqual testComponentGroups + .as[LibraryComponentGroups] + .rightValue shouldEqual LibraryComponentGroups.fromComponentGroups( + LibraryName("user", "Get_Package_Test_Lib"), + testComponentGroups + ) + } + + "return LibraryNotFound error when getting the package of unknown library" in { + val client = getInitialisedWsClient() + client.send(json""" + { "jsonrpc": "2.0", + "method": "library/getPackage", + "id": 0, + "params": { + "namespace": "user", + "name": "Get_Package_Unknown", + "version": { + "type": "LocalLibraryVersion" + } + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 0, + "error": { + "code": 8007, + "message": "Local library [user.Get_Package_Unknown] has not been found." + } + } + """) } "create, publish a library and fetch its manifest from the server" in { diff --git a/lib/scala/edition-updater/src/main/scala/org/enso/editions/updater/UpdatingEditionProvider.scala b/lib/scala/edition-updater/src/main/scala/org/enso/editions/updater/UpdatingEditionProvider.scala index 8bb74ecea73..d06a34835aa 100644 --- a/lib/scala/edition-updater/src/main/scala/org/enso/editions/updater/UpdatingEditionProvider.scala +++ b/lib/scala/edition-updater/src/main/scala/org/enso/editions/updater/UpdatingEditionProvider.scala @@ -33,12 +33,12 @@ class UpdatingEditionProvider( name: String ): Either[EditionLoadingError, Editions.Raw.Edition] = { provider.findEditionForName(name) match { - case Left(EditionNotFound()) => + case Left(EditionNotFound(_)) => if (!wasUpdateTried) { wasUpdateTried = true updater.updateEditions() provider.findEditionForName(name) - } else Left(EditionNotFound()) + } else Left(EditionNotFound(name)) case Left(otherError) => Left(otherError) case Right(value) => Right(value) } diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/provider/EditionLoadingError.scala b/lib/scala/editions/src/main/scala/org/enso/editions/provider/EditionLoadingError.scala index 85c28dd0cbb..a1c258bcea2 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/provider/EditionLoadingError.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/provider/EditionLoadingError.scala @@ -5,7 +5,7 @@ sealed class EditionLoadingError(message: String, cause: Throwable = null) extends RuntimeException(message, cause) /** Indicates that the requested edition was not found in the caches. */ -case class EditionNotFound() +case class EditionNotFound(editionName: String) extends EditionLoadingError("The edition was not found.") /** Indicates that the edition was found but could not be read, for example due diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala index 6ca67808ae1..dce23a1a179 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala @@ -28,11 +28,11 @@ class FileSystemEditionProvider(searchPaths: List[Path]) case head :: tail => val headResult = loadEdition(name, head) headResult match { - case Left(EditionNotFound()) => + case Left(EditionNotFound(_)) => findEdition(name, tail) case _ => headResult } - case Nil => Left(EditionNotFound()) + case Nil => Left(EditionNotFound(name)) } private def loadEdition( @@ -47,7 +47,7 @@ class FileSystemEditionProvider(searchPaths: List[Path]) .toEither .left .map(EditionReadError) - } else Left(EditionNotFound()) + } else Left(EditionNotFound(name)) } /** Finds all editions available on the [[searchPaths]]. */ diff --git a/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala b/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala index e0c6bc9b614..17eb8ff89d9 100644 --- a/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala +++ b/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala @@ -52,7 +52,7 @@ class EditionResolverSpec override def findEditionForName( name: String ): Either[EditionLoadingError, Editions.Raw.Edition] = - editions.get(name).toRight(EditionNotFound()) + editions.get(name).toRight(EditionNotFound(name)) override def findAvailableEditions(): Seq[String] = editions.keys.toSeq } diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/ComponentGroup.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/ComponentGroup.scala index 8331f659871..6f5b14e04e8 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/ComponentGroup.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/ComponentGroup.scala @@ -63,7 +63,6 @@ object ComponentGroup { /** Fields for use when serializing the [[ComponentGroup]]. */ private object Fields { - val Module = "module" val Color = "color" val Icon = "icon" val Exports = "exports" @@ -77,26 +76,45 @@ object ComponentGroup { Fields.Exports -> componentGroup.exports.asJson ) Json.obj( - (Fields.Module -> componentGroup.module.asJson) +: - (color.toSeq ++ icon.toSeq ++ exports.toSeq): _* + componentGroup.module.name -> Json.obj( + color.toSeq ++ icon.toSeq ++ exports.toSeq: _* + ) ) } /** [[Decoder]] instance for the [[ComponentGroup]]. */ implicit val decoder: Decoder[ComponentGroup] = { json => for { - name <- ConfigCodecs - .getFromObject[ModuleName]( - "component group name", - Fields.Module, - json - ) - color <- json.get[Option[String]](Fields.Color) - icon <- json.get[Option[String]](Fields.Icon) - exports <- json.getOrElse[List[Component]](Fields.Exports)(List()) - } yield ComponentGroup(name, color, icon, exports) + name <- decodeName(json) + componentGroup <- decodeComponentGroup( + ModuleName(name), + json.downField(name) + ) + } yield componentGroup } + private def decodeName(cursor: ACursor): Decoder.Result[String] = + ConfigCodecs + .getNameFromKey(cursor) + .toRight(decodingFailure(cursor.history)) + + private def decodeComponentGroup( + name: ModuleName, + cursor: ACursor + ): Decoder.Result[ComponentGroup] = { + if (cursor.keys.nonEmpty) { + for { + color <- cursor.get[Option[String]](Fields.Color) + icon <- cursor.get[Option[String]](Fields.Icon) + exports <- cursor.getOrElse[List[Component]](Fields.Exports)(List()) + } yield ComponentGroup(name, color, icon, exports) + } else { + Left(decodingFailure(cursor.history)) + } + } + + private def decodingFailure(history: List[CursorOp]): DecodingFailure = + DecodingFailure("Failed to decode component group", history) } /** The definition of a component group that extends an existing one. @@ -112,7 +130,6 @@ object ExtendedComponentGroup { /** Fields for use when serializing the [[ExtendedComponentGroup]]. */ private object Fields { - val Module = "module" val Exports = "exports" } @@ -123,22 +140,54 @@ object ExtendedComponentGroup { Fields.Exports -> extendedComponentGroup.exports.asJson ) Json.obj( - (Fields.Module -> extendedComponentGroup.module.asJson) +: exports.toSeq: _* + extendedComponentGroup.module.qualifiedName -> Json.obj( + exports.toSeq: _* + ) ) } /** [[Decoder]] instance for the [[ExtendedComponentGroup]]. */ implicit val decoder: Decoder[ExtendedComponentGroup] = { json => for { - reference <- ConfigCodecs - .getFromObject[ModuleReference]( - "extended component group reference", - Fields.Module, - json + moduleName <- decodeModuleName(json) + moduleReference <- ModuleReference + .fromModuleName(moduleName) + .toRight( + DecodingFailure( + s"Failed to decode '$moduleName' as a module reference. " + + s"Module reference should consist of a namespace (author), " + + s"library name and a module name (e.g. Standard.Base.Data).", + json.history + ) ) - exports <- json.getOrElse[List[Component]](Fields.Exports)(List()) - } yield ExtendedComponentGroup(reference, exports) + componentGroup <- + decodeExtendedComponentGroup( + moduleReference, + json.downField(moduleName) + ) + } yield componentGroup } + + private def decodeModuleName(cursor: ACursor): Decoder.Result[String] = + ConfigCodecs + .getNameFromKey(cursor) + .toRight(decodingFailure(cursor.history)) + + private def decodeExtendedComponentGroup( + reference: ModuleReference, + cursor: ACursor + ): Decoder.Result[ExtendedComponentGroup] = + if (cursor.keys.nonEmpty) { + for { + exports <- cursor.getOrElse[List[Component]](Fields.Exports)(List()) + } yield ExtendedComponentGroup(reference, exports) + } else { + Left(decodingFailure(cursor.history)) + } + + private def decodingFailure(history: List[CursorOp]): DecodingFailure = + DecodingFailure("Failed to decode extended component group", history) + } /** A single component of a component group. @@ -150,7 +199,6 @@ case class Component(name: String, shortcut: Option[Shortcut]) object Component { object Fields { - val Name = "name" val Shortcut = "shortcut" } @@ -159,8 +207,9 @@ object Component { component.shortcut match { case Some(shortcut) => Json.obj( - Fields.Name -> component.name.asJson, - Fields.Shortcut -> shortcut.asJson + component.name -> Json.obj( + Fields.Shortcut -> shortcut.asJson + ) ) case None => component.name.asJson @@ -175,11 +224,28 @@ object Component { case Left(_) => for { name <- ConfigCodecs - .getFromObject[String]("component name", Fields.Name, json) - shortcut <- json.getOrElse[Option[Shortcut]](Fields.Shortcut)(None) - } yield Component(name, shortcut) + .getNameFromKey(json) + .toRight(decodingFailure(json.history)) + component <- decodeComponent(name, json.downField(name)) + } yield component } } + + private def decodeComponent( + name: String, + cursor: ACursor + ): Decoder.Result[Component] = { + if (cursor.keys.nonEmpty) { + for { + shortcut <- cursor.getOrElse[Option[Shortcut]](Fields.Shortcut)(None) + } yield Component(name, shortcut) + } else { + Left(decodingFailure(cursor.history)) + } + } + + private def decodingFailure(history: List[CursorOp]): DecodingFailure = + DecodingFailure("Failed to decode exported component", history) } /** The shortcut reference to the component. @@ -196,7 +262,12 @@ object Shortcut { /** [[Decoder]] instance for the [[Shortcut]]. */ implicit val decoder: Decoder[Shortcut] = { json => - ConfigCodecs.getScalar("shortcut", json).map(Shortcut(_)) + ConfigCodecs + .getScalar(json) + .map(Shortcut(_)) + .toRight( + DecodingFailure("Failed to decode shortcut", json.history) + ) } } @@ -222,41 +293,19 @@ case class ModuleReference( } object ModuleReference { - private def toModuleString(moduleReference: ModuleReference): String = { - s"${moduleReference.libraryName.namespace}${LibraryName.separator}" + - s"${moduleReference.libraryName.name}${LibraryName.separator}" + - moduleReference.moduleName.name - } - - /** [[Encoder]] instance for the [[ModuleReference]]. */ - implicit val encoder: Encoder[ModuleReference] = { moduleReference => - toModuleString(moduleReference).asJson - } - - /** [[Decoder]] instance for the [[ModuleReference]]. */ - implicit val decoder: Decoder[ModuleReference] = { json => - json.as[String].flatMap { moduleString => - moduleString.split(LibraryName.separator).toList match { - case namespace :: name :: module :: modules => - Right( - ModuleReference( - LibraryName(namespace, name), - ModuleName.fromComponents(module, modules) - ) + /** Create a [[ModuleReference]] from string. */ + def fromModuleName(moduleName: String): Option[ModuleReference] = + moduleName.split(LibraryName.separator).toList match { + case namespace :: name :: module :: modules => + Some( + ModuleReference( + LibraryName(namespace, name), + ModuleName.fromComponents(module, modules) ) - case _ => - Left( - DecodingFailure( - s"Failed to decode '$moduleString' as module reference. " + - s"Module reference should consist of a namespace (author), " + - s"library name and a module name (e.g. Standard.Base.Data).", - json.history - ) - ) - } + ) + case _ => + None } - } - } /** The module name. @@ -266,6 +315,7 @@ object ModuleReference { case class ModuleName(name: String) object ModuleName { + /** Create a [[ModuleName]] from its components. */ def fromComponents(item: String, items: List[String]): ModuleName = ModuleName((item :: items).mkString(LibraryName.separator.toString)) diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/ConfigCodecs.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/ConfigCodecs.scala index dc8b064c0f8..16984334705 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/ConfigCodecs.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/ConfigCodecs.scala @@ -1,74 +1,38 @@ package org.enso.pkg import io.circe._ -import io.circe.syntax._ /** A collection of utility codecs used in the [[Config]]. */ object ConfigCodecs { - /** The common decoding failure. + /** Get the decoded entity name. * - * @param entity the name of decoded entity - * @param history the list of JSON cursor operations - */ - private def decodingFailure( - entity: String, - history: List[CursorOp] - ): DecodingFailure = - DecodingFailure(s"Failed to decode $entity", history) - - /** Try to decode the entity `A` from a JSON object. - * - * The entity can be encoded either in the first key of the JSON object with - * the Null value, - * {{{ - * { entity: null } - * }}} - * - * or as a value of the provided `keyName` argument + * The entity name is encoded as a key of JSON object. * * {{{ - * { `keyName`: entity } + * { `name`: entity } * }}} * - * @param entity the name of decoded entity that is used in error reporting - * @param keyName the key of the JSON object that contains the entity * @param cursor the current focus in the JSON document */ - def getFromObject[A: Decoder]( - entity: String, - keyName: String, - cursor: HCursor - ): Decoder.Result[A] = - cursor.keys match { - case Some(keys) if keys.nonEmpty => - cursor - .get[A](keyName) - .orElse { - cursor.get[Json](keys.head).flatMap { json => - if (json.isNull) { - Decoder[A].decodeJson(keys.head.asJson) - } else { - Left(decodingFailure(entity, cursor.history)) - } - } - } - case _ => - Left(decodingFailure(entity, cursor.history)) + def getNameFromKey(cursor: ACursor): Option[String] = + cursor.keys.flatMap { + case keys if keys.size == 1 => keys.headOption + case _ => None } /** Get the scalar value of the provided JSON element. * - * @param entity the name of decoded entity * @param cursor the current focus in the JSON document */ - def getScalar(entity: String, cursor: HCursor): Decoder.Result[String] = + def getScalar(cursor: HCursor): Option[String] = cursor.value.fold( - jsonNull = Left(decodingFailure(entity, cursor.history)), - jsonBoolean = value => Right(value.toString), - jsonNumber = value => Right(value.toString), - jsonString = value => Right(value), - jsonArray = _ => Left(decodingFailure(entity, cursor.history)), - jsonObject = _ => Left(decodingFailure(entity, cursor.history)) + jsonNull = None, + jsonBoolean = value => Some(value.toString), + jsonNumber = value => Some(value.toString), + jsonString = value => Some(value), + jsonArray = _ => None, + jsonObject = _ => None ) + } diff --git a/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala b/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala index d9aeece8937..72bc32275c4 100644 --- a/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala +++ b/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala @@ -102,17 +102,19 @@ class ConfigSpec """name: FooBar |component-groups: | new: - | - Group 1: - | color: '#C047AB' - | icon: icon-name - | exports: - | - foo: - | shortcut: f - | - bar + | - Group 1: + | color: '#C047AB' + | icon: icon-name + | exports: + | - foo: + | shortcut: f + | - bar | extends: - | - Standard.Base.Group 2: - | exports: - | - bax + | - Standard.Base.Group 2: + | exports: + | - baz: + | shortcut: k + | - quux |""".stripMargin val parsed = Config.fromYaml(config).get @@ -134,7 +136,10 @@ class ConfigSpec LibraryName("Standard", "Base"), ModuleName("Group 2") ), - exports = List(Component("bax", None)) + exports = List( + Component("baz", Some(Shortcut("k"))), + Component("quux", None) + ) ) ) ) @@ -144,17 +149,19 @@ class ConfigSpec serialized should include( """component-groups: | new: - | - module: Group 1 - | color: '#C047AB' - | icon: icon-name - | exports: - | - name: foo - | shortcut: f - | - bar + | - Group 1: + | color: '#C047AB' + | icon: icon-name + | exports: + | - foo: + | shortcut: f + | - bar | extends: - | - module: Standard.Base.Group 2 - | exports: - | - bax""".stripMargin.linesIterator.mkString("\n") + | - Standard.Base.Group 2: + | exports: + | - baz: + | shortcut: k + | - quux""".stripMargin.linesIterator.mkString("\n") ) } @@ -188,15 +195,15 @@ class ConfigSpec |component-groups: | extends: | - Group 1: - | exports: - | - bax + | exports: + | - bax |""".stripMargin val parsed = Config.fromYaml(config).get parsed.componentGroups match { case Left(f: DecodingFailure) => Show[DecodingFailure].show(f) should include( - "Failed to decode 'Group 1' as module reference" + "Failed to decode 'Group 1' as a module reference" ) case unexpected => fail(s"Unexpected result: $unexpected") @@ -209,16 +216,16 @@ class ConfigSpec |component-groups: | new: | - Group 1: - | exports: - | - foo: - | shortcut: f - | - bar: - | shortcut: fgh - | - baz: - | shortcut: 0 - | - quux: - | shortcut: - | - hmmm: + | exports: + | - foo: + | shortcut: f + | - bar: + | shortcut: fgh + | - baz: + | shortcut: 0 + | - quux: + | shortcut: + | - hmmm |""".stripMargin val parsed = Config.fromYaml(config).get val expectedComponentGroups = ComponentGroups( @@ -248,9 +255,9 @@ class ConfigSpec |component-groups: | new: | - Group 1: - | exports: - | - foo: - | shortcut: [] + | exports: + | - foo: + | shortcut: [] |""".stripMargin val parsed = Config.fromYaml(config).get parsed.componentGroups match { @@ -275,7 +282,7 @@ class ConfigSpec parsed.componentGroups match { case Left(f: DecodingFailure) => Show[DecodingFailure].show(f) should include( - "Failed to decode component group name" + "Failed to decode component group" ) case unexpected => fail(s"Unexpected result: $unexpected") @@ -288,14 +295,14 @@ class ConfigSpec |component-groups: | new: | - Group 1: - | exports: - | - one: two + | exports: + | - one: two |""".stripMargin val parsed = Config.fromYaml(config).get parsed.componentGroups match { case Left(f: DecodingFailure) => Show[DecodingFailure].show(f) should include( - "Failed to decode component name" + "Failed to decode exported component" ) case unexpected => fail(s"Unexpected result: $unexpected")