Skip to content

Commit

Permalink
Add a method for getting component groups without execution context (#…
Browse files Browse the repository at this point in the history
…7569)

close #7510

Changelog:
- add: `runtime/getComponentGroups` request returning the component groups currently available in runtime
  • Loading branch information
4e6 authored Aug 14, 2023
1 parent d3436fa commit 060323e
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 6 deletions.
49 changes: 43 additions & 6 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ transport formats, please look [here](./protocol-architecture).
- [`library/getPackage`](#librarygetpackage)
- [`library/publish`](#librarypublish)
- [`library/preinstall`](#librarypreinstall)
- [Runtime Operations](#runtime-operations)
- [`runtime/getComponentGroups`](#runtime-getcomponentgroups)
- [Errors](#errors-75)
- [`Error`](#error)
- [`AccessDeniedError`](#accessdeniederror)
Expand Down Expand Up @@ -3888,12 +3890,10 @@ null;
Sent from the client to the server to get the list of component groups available
in runtime.

The engine is started with an empty list of libraries loaded. It means that the
request should be sent after the first
[`executionContext/executionComplete`](#executioncontextexecutioncomplete)
notification indicating that all the libraries are loaded, and the component
group list is populated. If the request is sent before the first notification,
the response may be empty or not contain all available components.
#### Deprecated

The request is deprecated in favor of
[`runtime/getComponentGroups`](#runtime-getcomponentgroups).

- **Type:** Request
- **Direction:** Client -> Server
Expand Down Expand Up @@ -5333,6 +5333,43 @@ null;
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
file-system error.

## Runtime Operations

### `runtime/getComponentGroups`

Sent from the client to the server to get the list of component groups available
in runtime.

The engine is started with an empty list of libraries loaded. It means that the
request should be sent after the first
[`executionContext/executionComplete`](#executioncontextexecutioncomplete)
notification indicating that all the libraries are loaded, and the component
group list is populated. If the request is sent before the first notification,
the response may be empty or not contain all available components.

- **Type:** Request
- **Direction:** Client -> Server
- **Connection:** Protocol
- **Visibility:** Public

#### Parameters

```typescript
null;
```

#### Result

```typescript
{
componentGroups: LibraryComponentGroup[];
}
```

#### Errors

None

## Errors

The language server component also has its own set of errors. This section is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import org.enso.languageserver.requesthandler.visualization.{
import org.enso.languageserver.requesthandler.workspace.ProjectInfoHandler
import org.enso.languageserver.runtime.ContextRegistryProtocol
import org.enso.languageserver.runtime.ExecutionApi._
import org.enso.languageserver.runtime.RuntimeApi.RuntimeGetComponentGroups
import org.enso.languageserver.runtime.VisualizationApi.{
AttachVisualization,
DetachVisualization,
Expand Down Expand Up @@ -584,6 +585,10 @@ class JsonConnectionController(
RenameSymbol -> RenameSymbolHandler.props(
requestTimeout,
runtimeConnector
),
RuntimeGetComponentGroups -> runtime.GetComponentGroupsHandler.props(
requestTimeout,
runtimeConnector
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.enso.languageserver.runtime.VisualizationApi._
import org.enso.languageserver.session.SessionApi.InitProtocolConnection
import org.enso.languageserver.text.TextApi._
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.runtime.RuntimeApi.RuntimeGetComponentGroups
import org.enso.languageserver.vcsmanager.VcsManagerApi._
import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo

Expand Down Expand Up @@ -100,6 +101,7 @@ object JsonRpc {
.registerRequest(LibraryGetPackage)
.registerRequest(LibraryPublish)
.registerRequest(LibraryPreinstall)
.registerRequest(RuntimeGetComponentGroups)
.registerNotification(TaskStarted)
.registerNotification(TaskProgressUpdate)
.registerNotification(TaskFinished)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package org.enso.languageserver.requesthandler.runtime

import akka.actor.{Actor, ActorRef, Cancellable, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.editions.LibraryName
import org.enso.jsonrpc._
import org.enso.languageserver.libraries.{
ComponentGroupsResolver,
ComponentGroupsValidator,
LibraryComponentGroup
}
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.runtime.RuntimeApi._
import org.enso.languageserver.util.UnhandledLogging
import org.enso.pkg.ComponentGroups
import org.enso.polyglot.runtime.Runtime.Api

import java.util.UUID

import scala.collection.immutable.ListMap
import scala.concurrent.duration.FiniteDuration

/** A request handler for `runtime/getComponentGroups` commands.
*
* @param timeout request timeout
* @param runtime a reference to the runtime connector
* @param componentGroupsResolver resolves dependencies between the component
* groups of different packages
* @param componentGroupsValidator validates the component groups
*/
class GetComponentGroupsHandler(
timeout: FiniteDuration,
runtime: ActorRef,
componentGroupsResolver: ComponentGroupsResolver,
componentGroupsValidator: ComponentGroupsValidator
) extends Actor
with LazyLogging
with UnhandledLogging {

import context.dispatcher

override def receive: Receive = requestStage

private def requestStage: Receive = {
case Request(
RuntimeGetComponentGroups,
id,
_
) =>
runtime ! Api.Request(UUID.randomUUID(), Api.GetComponentGroupsRequest())
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
}

private def responseStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable
): Receive = {
case RequestTimeout =>
logger.error("Request [{}] timed out.", id)
replyTo ! ResponseError(Some(id), Errors.RequestTimeout)
context.stop(self)

case Api.Response(_, Api.GetComponentGroupsResponse(componentGroups)) =>
replyTo ! ResponseResult(
RuntimeGetComponentGroups,
id,
RuntimeGetComponentGroups.Result(
resolveComponentGroups(componentGroups.to(ListMap))
)
)
cancellable.cancel()
context.stop(self)
}

private def resolveComponentGroups(
componentGroups: Map[LibraryName, ComponentGroups]
): Seq[LibraryComponentGroup] = {
val validated = componentGroupsValidator.validate(componentGroups)

validated.collect { case (_, Left(error)) =>
logValidationError(error)
}

val validatedComponents = validated
.collect { case (libraryName, Right(componentGroups)) =>
libraryName -> componentGroups
}
componentGroupsResolver.resolveComponentGroups(validatedComponents)
}

private def logValidationError(
error: ComponentGroupsValidator.ValidationError
): Unit =
error match {
case ComponentGroupsValidator.ValidationError
.InvalidComponentGroups(libraryName, message) =>
logger.warn(
s"Validation error. Failed to read library [$libraryName] " +
s"component groups (reason: $message)."
)
case ComponentGroupsValidator.ValidationError
.DuplicatedComponentGroup(libraryName, moduleReference) =>
logger.warn(
s"Validation error. Library [$libraryName] defines duplicate " +
s"component group [$moduleReference]."
)
case ComponentGroupsValidator.ValidationError
.ComponentGroupExtendsNothing(libraryName, moduleReference) =>
logger.warn(
s"Validation error. Library [$libraryName] component group " +
s"[$moduleReference] extends nothing."
)
}
}

object GetComponentGroupsHandler {

/** Creates configuration object used to create a
* [[GetComponentGroupsHandler]].
*
* @param timeout request timeout
* @param runtime a reference to the runtime connector
* @param componentGroupsResolver resolves dependencies between the component
* groups of different packages
* @param componentGroupsValidator validates the component groups
*/
def props(
timeout: FiniteDuration,
runtime: ActorRef,
componentGroupsResolver: ComponentGroupsResolver =
new ComponentGroupsResolver,
componentGroupsValidator: ComponentGroupsValidator =
new ComponentGroupsValidator
): Props =
Props(
new GetComponentGroupsHandler(
timeout,
runtime,
componentGroupsResolver,
componentGroupsValidator
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.enso.languageserver.runtime

import org.enso.jsonrpc.{HasParams, HasResult, Method, Unused}
import org.enso.languageserver.libraries.LibraryComponentGroup

object RuntimeApi {

case object RuntimeGetComponentGroups
extends Method("runtime/getComponentGroups") {

case class Result(componentGroups: Seq[LibraryComponentGroup])

implicit val hasParams: HasParams.Aux[this.type, Unused.type] =
new HasParams[this.type] {
type Params = Unused.type
}
implicit val hasResult
: HasResult.Aux[this.type, RuntimeGetComponentGroups.Result] =
new HasResult[this.type] {
type Result = RuntimeGetComponentGroups.Result
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.enso.languageserver.websocket.json

import io.circe.literal._
import org.enso.languageserver.runtime.TestComponentGroups
import org.enso.polyglot.runtime.Runtime.Api

class RuntimeTest extends BaseServerTest {

"runtime/getComponentGroups" should {

"return component groups successfully" in {
val client = getInitialisedWsClient()

// get component groups
client.send(
json"""
{ "jsonrpc": "2.0",
"method": "runtime/getComponentGroups",
"id": 1,
"params": null
}
"""
)
val requestId =
runtimeConnectorProbe.receiveN(1).head match {
case Api.Request(requestId, Api.GetComponentGroupsRequest()) =>
requestId
case msg =>
fail(s"Unexpected message: $msg")
}

runtimeConnectorProbe.lastSender ! Api.Response(
requestId,
Api.GetComponentGroupsResponse(
TestComponentGroups.standardBase.toVector
)
)
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 1,
"result": {
"componentGroups": [
{
"library" : "Standard.Base",
"name" : "Input",
"exports" : [
{
"name" : "Standard.Base.File.new"
},
{
"name" : "Standard.Database.Connection.Database.connect"
}
]
}
]
}
}
""")
}
}

}

0 comments on commit 060323e

Please sign in to comment.