mirror of
https://github.com/apple/pkl.git
synced 2026-03-22 00:59:17 +01:00
Initial commit
This commit is contained in:
252
pkl-server/src/main/kotlin/org/pkl/server/BinaryEvaluator.kt
Normal file
252
pkl-server/src/main/kotlin/org/pkl/server/BinaryEvaluator.kt
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import org.msgpack.core.MessagePacker
|
||||
import org.pkl.core.*
|
||||
import org.pkl.core.ast.member.ObjectMember
|
||||
import org.pkl.core.module.ModuleKeyFactory
|
||||
import org.pkl.core.project.DeclaredDependencies
|
||||
import org.pkl.core.resource.ResourceReader
|
||||
import org.pkl.core.runtime.*
|
||||
|
||||
internal class BinaryEvaluator(
|
||||
transformer: StackFrameTransformer,
|
||||
manager: SecurityManager,
|
||||
logger: Logger,
|
||||
factories: Collection<ModuleKeyFactory?>,
|
||||
readers: Collection<ResourceReader?>,
|
||||
environmentVariables: Map<String, String>,
|
||||
externalProperties: Map<String, String>,
|
||||
timeout: Duration?,
|
||||
moduleCacheDir: Path?,
|
||||
declaredDependencies: DeclaredDependencies?,
|
||||
outputFormat: String?
|
||||
) :
|
||||
EvaluatorImpl(
|
||||
transformer,
|
||||
manager,
|
||||
logger,
|
||||
factories,
|
||||
readers,
|
||||
environmentVariables,
|
||||
externalProperties,
|
||||
timeout,
|
||||
moduleCacheDir,
|
||||
declaredDependencies,
|
||||
outputFormat
|
||||
) {
|
||||
fun evaluate(moduleSource: ModuleSource, expression: String?): ByteArray {
|
||||
return doEvaluate(moduleSource) { module ->
|
||||
val evalResult =
|
||||
expression?.let { VmUtils.evaluateExpression(module, it, securityManager, moduleResolver) }
|
||||
?: module
|
||||
VmValue.force(evalResult, false)
|
||||
threadLocalBufferPacker
|
||||
.get()
|
||||
.apply {
|
||||
clear()
|
||||
ValueEncoder(this).visit(evalResult)
|
||||
}
|
||||
.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
private class ValueEncoder(private val packer: MessagePacker) : VmValueVisitor {
|
||||
companion object {
|
||||
private const val CODE_OBJECT: Byte = 0x1
|
||||
private const val CODE_MAP: Byte = 0x2
|
||||
private const val CODE_MAPPING: Byte = 0x3
|
||||
private const val CODE_LIST: Byte = 0x4
|
||||
private const val CODE_LISTING: Byte = 0x5
|
||||
private const val CODE_SET: Byte = 0x6
|
||||
private const val CODE_DURATION: Byte = 0x7
|
||||
private const val CODE_DATASIZE: Byte = 0x8
|
||||
private const val CODE_PAIR: Byte = 0x9
|
||||
private const val CODE_INTSEQ: Byte = 0xA
|
||||
private const val CODE_REGEX: Byte = 0xB
|
||||
private const val CODE_CLASS: Byte = 0xC
|
||||
private const val CODE_TYPEALIAS: Byte = 0xD
|
||||
private const val CODE_FUNCTION: Byte = 0xE
|
||||
private const val CODE_PROPERTY: Byte = 0x10
|
||||
private const val CODE_ENTRY: Byte = 0x11
|
||||
private const val CODE_ELEMENT: Byte = 0x12
|
||||
}
|
||||
|
||||
override fun visitString(value: String) {
|
||||
packer.packString(value)
|
||||
}
|
||||
|
||||
override fun visitBoolean(value: Boolean) {
|
||||
packer.packBoolean(value)
|
||||
}
|
||||
|
||||
override fun visitInt(value: Long) {
|
||||
packer.packLong(value)
|
||||
}
|
||||
|
||||
override fun visitFloat(value: Double) {
|
||||
packer.packDouble(value)
|
||||
}
|
||||
|
||||
override fun visitDuration(value: VmDuration) {
|
||||
packer.packArrayHeader(3)
|
||||
packer.packInt(CODE_DURATION.toInt())
|
||||
packer.packDouble(value.value)
|
||||
packer.packString(value.unit.toString())
|
||||
}
|
||||
|
||||
override fun visitDataSize(value: VmDataSize) {
|
||||
packer.packArrayHeader(3)
|
||||
packer.packInt(CODE_DATASIZE.toInt())
|
||||
packer.packDouble(value.value)
|
||||
packer.packString(value.unit.toString())
|
||||
}
|
||||
|
||||
override fun visitIntSeq(value: VmIntSeq) {
|
||||
packer.packArrayHeader(4)
|
||||
packer.packInt(CODE_INTSEQ.toInt())
|
||||
packer.packLong(value.start)
|
||||
packer.packLong(value.end)
|
||||
packer.packLong(value.step)
|
||||
}
|
||||
|
||||
private fun doVisitCollection(length: Int, value: Iterable<Any>) {
|
||||
packer.packArrayHeader(length)
|
||||
for (elem in value) {
|
||||
visit(elem)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitList(value: VmList) {
|
||||
packer.packArrayHeader(2)
|
||||
packer.packInt(CODE_LIST.toInt())
|
||||
doVisitCollection(value.length, value)
|
||||
}
|
||||
|
||||
override fun visitSet(value: VmSet) {
|
||||
packer.packArrayHeader(2)
|
||||
packer.packInt(CODE_SET.toInt())
|
||||
doVisitCollection(value.length, value)
|
||||
}
|
||||
|
||||
override fun visitMap(value: VmMap) {
|
||||
packer.packArrayHeader(2)
|
||||
packer.packInt(CODE_MAP.toInt())
|
||||
packer.packMapHeader(value.length)
|
||||
for ((k, v) in value) {
|
||||
visit(k)
|
||||
visit(v)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitTyped(value: VmTyped) {
|
||||
packObjectPreamble(value)
|
||||
packer.packArrayHeader(value.vmClass.allRegularPropertyNames.size())
|
||||
value.iterateAlreadyForcedMemberValues(this::doVisitObjectMember)
|
||||
}
|
||||
|
||||
private fun doVisitObjectMember(key: Any, member: ObjectMember, value: Any): Boolean {
|
||||
if (member.isClass || member.isTypeAlias) return true
|
||||
|
||||
packer.packArrayHeader(3)
|
||||
when {
|
||||
member.isProp -> {
|
||||
packer.packInt(CODE_PROPERTY.toInt())
|
||||
packer.packString(key.toString())
|
||||
}
|
||||
member.isEntry -> {
|
||||
packer.packInt(CODE_ENTRY.toInt())
|
||||
visit(key)
|
||||
}
|
||||
else -> {
|
||||
packer.packInt(CODE_ELEMENT.toInt())
|
||||
packer.packLong(key as Long)
|
||||
}
|
||||
}
|
||||
visit(value)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visitDynamic(value: VmDynamic) {
|
||||
packObjectPreamble(value)
|
||||
packer.packArrayHeader(value.regularMemberCount)
|
||||
value.iterateAlreadyForcedMemberValues(this::doVisitObjectMember)
|
||||
}
|
||||
|
||||
private fun packObjectPreamble(value: VmObjectLike) {
|
||||
packer.packArrayHeader(4)
|
||||
packer.packInt(CODE_OBJECT.toInt())
|
||||
packer.packString(value.vmClass.displayName)
|
||||
packer.packString(value.vmClass.module.moduleInfo.moduleKey.uri.toString())
|
||||
}
|
||||
|
||||
override fun visitListing(value: VmListing) {
|
||||
packer.packArrayHeader(2)
|
||||
packer.packInt(CODE_LISTING.toInt())
|
||||
packer.packArrayHeader(value.length)
|
||||
value.iterateAlreadyForcedMemberValues { _, _, memberValue ->
|
||||
visit(memberValue)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitMapping(value: VmMapping) {
|
||||
packer.packArrayHeader(2)
|
||||
packer.packInt(CODE_MAPPING.toInt())
|
||||
packer.packMapHeader(value.entryCount)
|
||||
value.iterateAlreadyForcedMemberValues { key, _, memberValue ->
|
||||
visit(key)
|
||||
visit(memberValue)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitClass(value: VmClass) {
|
||||
packer.packArrayHeader(1)
|
||||
packer.packInt(CODE_CLASS.toInt())
|
||||
}
|
||||
|
||||
override fun visitTypeAlias(value: VmTypeAlias) {
|
||||
packer.packArrayHeader(1)
|
||||
packer.packInt(CODE_TYPEALIAS.toInt())
|
||||
}
|
||||
|
||||
override fun visitPair(value: VmPair) {
|
||||
packer.packArrayHeader(3)
|
||||
packer.packInt(CODE_PAIR.toInt())
|
||||
visit(value.first)
|
||||
visit(value.second)
|
||||
}
|
||||
|
||||
override fun visitRegex(value: VmRegex) {
|
||||
packer.packArrayHeader(2)
|
||||
packer.packInt(CODE_REGEX.toInt())
|
||||
packer.packString(value.pattern.pattern())
|
||||
}
|
||||
|
||||
override fun visitNull(value: VmNull) {
|
||||
packer.packNil()
|
||||
}
|
||||
|
||||
override fun visitFunction(value: VmFunction) {
|
||||
packer.packArrayHeader(1)
|
||||
packer.packInt(CODE_FUNCTION.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
32
pkl-server/src/main/kotlin/org/pkl/server/ClientLogger.kt
Normal file
32
pkl-server/src/main/kotlin/org/pkl/server/ClientLogger.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import org.pkl.core.Logger
|
||||
import org.pkl.core.StackFrame
|
||||
|
||||
internal class ClientLogger(
|
||||
private val evaluatorId: Long,
|
||||
private val transport: MessageTransport
|
||||
) : Logger {
|
||||
override fun trace(message: String, frame: StackFrame) {
|
||||
transport.send(LogMessage(evaluatorId, level = 0, message, frame.moduleUri))
|
||||
}
|
||||
|
||||
override fun warn(message: String, frame: StackFrame) {
|
||||
transport.send(LogMessage(evaluatorId, level = 1, message, frame.moduleUri))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.random.Random
|
||||
import org.pkl.core.SecurityManager
|
||||
import org.pkl.core.module.ModuleKey
|
||||
import org.pkl.core.module.ModuleKeyFactory
|
||||
import org.pkl.core.module.PathElement
|
||||
import org.pkl.core.module.ResolvedModuleKey
|
||||
import org.pkl.core.module.ResolvedModuleKeys
|
||||
|
||||
internal class ClientModuleKeyFactory(
|
||||
private val readerSpecs: Collection<ModuleReaderSpec>,
|
||||
transport: MessageTransport,
|
||||
evaluatorId: Long
|
||||
) : ModuleKeyFactory {
|
||||
companion object {
|
||||
private class ClientModuleKeyResolver(
|
||||
private val transport: MessageTransport,
|
||||
private val evaluatorId: Long,
|
||||
) {
|
||||
private val readResponses: MutableMap<URI, Future<String>> = ConcurrentHashMap()
|
||||
|
||||
private val listResponses: MutableMap<URI, Future<List<PathElement>>> = ConcurrentHashMap()
|
||||
|
||||
fun listElements(securityManager: SecurityManager, uri: URI): List<PathElement> {
|
||||
securityManager.checkResolveModule(uri)
|
||||
return doListElements(uri)
|
||||
}
|
||||
|
||||
fun hasElement(securityManager: SecurityManager, uri: URI): Boolean {
|
||||
securityManager.checkResolveModule(uri)
|
||||
return try {
|
||||
doReadModule(uri)
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun resolveModule(securityManager: SecurityManager, uri: URI): String {
|
||||
securityManager.checkResolveModule(uri)
|
||||
return doReadModule(uri)
|
||||
}
|
||||
|
||||
private fun doReadModule(uri: URI): String =
|
||||
readResponses
|
||||
.computeIfAbsent(uri) {
|
||||
CompletableFuture<String>().apply {
|
||||
val request = ReadModuleRequest(Random.nextLong(), evaluatorId, uri)
|
||||
transport.send(request) { response ->
|
||||
when (response) {
|
||||
is ReadModuleResponse -> {
|
||||
if (response.error != null) {
|
||||
completeExceptionally(IOException(response.error))
|
||||
} else {
|
||||
complete(response.contents!!)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
completeExceptionally(ProtocolException("unexpected response"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.getUnderlying()
|
||||
|
||||
private fun doListElements(uri: URI): List<PathElement> =
|
||||
listResponses
|
||||
.computeIfAbsent(uri) {
|
||||
CompletableFuture<List<PathElement>>().apply {
|
||||
val request = ListModulesRequest(Random.nextLong(), evaluatorId, uri)
|
||||
transport.send(request) { response ->
|
||||
when (response) {
|
||||
is ListModulesResponse -> {
|
||||
if (response.error != null) {
|
||||
completeExceptionally(IOException(response.error))
|
||||
} else {
|
||||
complete(response.pathElements!!)
|
||||
}
|
||||
}
|
||||
else -> completeExceptionally(ProtocolException("unexpected response"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.getUnderlying()
|
||||
}
|
||||
|
||||
/** [ModuleKey] that delegates module reads to the client. */
|
||||
private class ClientModuleKey(
|
||||
private val uri: URI,
|
||||
private val spec: ModuleReaderSpec,
|
||||
private val resolver: ClientModuleKeyResolver,
|
||||
) : ModuleKey {
|
||||
override fun isLocal(): Boolean = spec.isLocal
|
||||
|
||||
override fun hasHierarchicalUris(): Boolean = spec.hasHierarchicalUris
|
||||
|
||||
override fun isGlobbable(): Boolean = spec.isGlobbable
|
||||
|
||||
override fun getUri(): URI = uri
|
||||
|
||||
override fun listElements(securityManager: SecurityManager, baseUri: URI): List<PathElement> =
|
||||
resolver.listElements(securityManager, baseUri)
|
||||
|
||||
override fun resolve(securityManager: SecurityManager): ResolvedModuleKey {
|
||||
val contents = resolver.resolveModule(securityManager, uri)
|
||||
return ResolvedModuleKeys.virtual(this, uri, contents, true)
|
||||
}
|
||||
|
||||
override fun hasElement(securityManager: SecurityManager, uri: URI): Boolean {
|
||||
return resolver.hasElement(securityManager, uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val schemes = readerSpecs.map { it.scheme }
|
||||
|
||||
private val resolver: ClientModuleKeyResolver = ClientModuleKeyResolver(transport, evaluatorId)
|
||||
|
||||
override fun create(uri: URI): Optional<ModuleKey> =
|
||||
when (uri.scheme) {
|
||||
in schemes -> {
|
||||
val readerSpec = readerSpecs.find { it.scheme == uri.scheme }!!
|
||||
val moduleKey = ClientModuleKey(uri, readerSpec, resolver)
|
||||
Optional.of(moduleKey)
|
||||
}
|
||||
else -> Optional.empty()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.random.Random
|
||||
import org.pkl.core.SecurityManager
|
||||
import org.pkl.core.module.PathElement
|
||||
import org.pkl.core.resource.Resource
|
||||
import org.pkl.core.resource.ResourceReader
|
||||
|
||||
/** Resource reader that delegates read logic to the client. */
|
||||
internal class ClientResourceReader(
|
||||
private val transport: MessageTransport,
|
||||
private val evaluatorId: Long,
|
||||
private val readerSpec: ResourceReaderSpec,
|
||||
) : ResourceReader {
|
||||
private val readResponses: MutableMap<URI, Future<ByteArray>> = ConcurrentHashMap()
|
||||
|
||||
private val listResources: MutableMap<URI, Future<List<PathElement>>> = ConcurrentHashMap()
|
||||
|
||||
override fun hasHierarchicalUris(): Boolean = readerSpec.hasHierarchicalUris
|
||||
|
||||
override fun isGlobbable(): Boolean = readerSpec.isGlobbable
|
||||
|
||||
override fun getUriScheme() = readerSpec.scheme
|
||||
|
||||
override fun read(uri: URI): Optional<Any> = Optional.of(Resource(uri, doRead(uri)))
|
||||
|
||||
override fun hasElement(securityManager: SecurityManager, elementUri: URI): Boolean {
|
||||
securityManager.checkResolveResource(elementUri)
|
||||
return try {
|
||||
doRead(elementUri)
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun listElements(securityManager: SecurityManager, baseUri: URI): List<PathElement> {
|
||||
securityManager.checkResolveResource(baseUri)
|
||||
return doListElements(baseUri)
|
||||
}
|
||||
|
||||
private fun doListElements(baseUri: URI): List<PathElement> =
|
||||
listResources
|
||||
.computeIfAbsent(baseUri) {
|
||||
CompletableFuture<List<PathElement>>().apply {
|
||||
val request = ListResourcesRequest(Random.nextLong(), evaluatorId, baseUri)
|
||||
transport.send(request) { response ->
|
||||
when (response) {
|
||||
is ListResourcesResponse ->
|
||||
if (response.pathElements != null) {
|
||||
complete(response.pathElements)
|
||||
} else {
|
||||
completeExceptionally(IOException(response.error))
|
||||
}
|
||||
else -> completeExceptionally(ProtocolException("Unexpected response"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.getUnderlying()
|
||||
|
||||
private fun doRead(uri: URI): ByteArray =
|
||||
readResponses
|
||||
.computeIfAbsent(uri) {
|
||||
CompletableFuture<ByteArray>().apply {
|
||||
val request = ReadResourceRequest(Random.nextLong(), evaluatorId, uri)
|
||||
transport.send(request) { response ->
|
||||
when (response) {
|
||||
is ReadResourceResponse -> {
|
||||
if (response.error != null) {
|
||||
completeExceptionally(IOException(response.error))
|
||||
} else {
|
||||
complete(response.contents!!)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
completeExceptionally(ProtocolException("Unexpected response: $response"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.getUnderlying()
|
||||
}
|
||||
320
pkl-server/src/main/kotlin/org/pkl/server/Message.kt
Normal file
320
pkl-server/src/main/kotlin/org/pkl/server/Message.kt
Normal file
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import org.pkl.core.module.PathElement
|
||||
import org.pkl.core.packages.Checksums
|
||||
|
||||
sealed interface Message {
|
||||
val type: MessageType
|
||||
}
|
||||
|
||||
sealed interface OneWayMessage : Message
|
||||
|
||||
sealed interface RequestMessage : Message {
|
||||
val requestId: Long
|
||||
}
|
||||
|
||||
sealed interface ResponseMessage : Message {
|
||||
val requestId: Long
|
||||
}
|
||||
|
||||
sealed class ClientMessage : Message
|
||||
|
||||
sealed class ClientRequestMessage : ClientMessage(), RequestMessage
|
||||
|
||||
sealed class ClientResponseMessage : ClientMessage(), ResponseMessage
|
||||
|
||||
sealed class ClientOneWayMessage : ClientMessage(), OneWayMessage
|
||||
|
||||
sealed class ServerMessage : Message
|
||||
|
||||
sealed class ServerRequestMessage : ServerMessage(), RequestMessage
|
||||
|
||||
sealed class ServerResponseMessage : ServerMessage(), ResponseMessage
|
||||
|
||||
sealed class ServerOneWayMessage : ServerMessage(), OneWayMessage
|
||||
|
||||
enum class MessageType(val code: Int) {
|
||||
CREATE_EVALUATOR_REQUEST(0x20),
|
||||
CREATE_EVALUATOR_RESPONSE(0x21),
|
||||
CLOSE_EVALUATOR(0x22),
|
||||
EVALUATE_REQUEST(0x23),
|
||||
EVALUATE_RESPONSE(0x24),
|
||||
LOG_MESSAGE(0x25),
|
||||
READ_RESOURCE_REQUEST(0x26),
|
||||
READ_RESOURCE_RESPONSE(0x27),
|
||||
READ_MODULE_REQUEST(0x28),
|
||||
READ_MODULE_RESPONSE(0x29),
|
||||
LIST_RESOURCES_REQUEST(0x2a),
|
||||
LIST_RESOURCES_RESPONSE(0x2b),
|
||||
LIST_MODULES_REQUEST(0x2c),
|
||||
LIST_MODULES_RESPONSE(0x2d),
|
||||
}
|
||||
|
||||
data class ModuleReaderSpec(
|
||||
val scheme: String,
|
||||
val hasHierarchicalUris: Boolean,
|
||||
val isLocal: Boolean,
|
||||
val isGlobbable: Boolean
|
||||
)
|
||||
|
||||
data class ResourceReaderSpec(
|
||||
val scheme: String,
|
||||
val hasHierarchicalUris: Boolean,
|
||||
val isGlobbable: Boolean,
|
||||
)
|
||||
|
||||
private fun <T> T?.equalsNullable(other: Any?): Boolean {
|
||||
return Objects.equals(this, other)
|
||||
}
|
||||
|
||||
enum class DependencyType(val value: String) {
|
||||
LOCAL("local"),
|
||||
REMOTE("remote")
|
||||
}
|
||||
|
||||
sealed interface Dependency {
|
||||
val type: DependencyType
|
||||
val packageUri: URI?
|
||||
}
|
||||
|
||||
data class RemoteDependency(override val packageUri: URI, val checksums: Checksums?) : Dependency {
|
||||
override val type: DependencyType = DependencyType.REMOTE
|
||||
}
|
||||
|
||||
data class Project(
|
||||
val projectFileUri: URI,
|
||||
override val packageUri: URI?,
|
||||
val dependencies: Map<String, Dependency>
|
||||
) : Dependency {
|
||||
override val type: DependencyType = DependencyType.LOCAL
|
||||
}
|
||||
|
||||
data class CreateEvaluatorRequest(
|
||||
override val requestId: Long,
|
||||
val allowedModules: List<Pattern>?,
|
||||
val allowedResources: List<Pattern>?,
|
||||
val clientModuleReaders: List<ModuleReaderSpec>?,
|
||||
val clientResourceReaders: List<ResourceReaderSpec>?,
|
||||
val modulePaths: List<Path>?,
|
||||
val env: Map<String, String>?,
|
||||
val properties: Map<String, String>?,
|
||||
val timeout: Duration?,
|
||||
val rootDir: Path?,
|
||||
val cacheDir: Path?,
|
||||
val outputFormat: String?,
|
||||
val project: Project?,
|
||||
) : ClientRequestMessage() {
|
||||
override val type = MessageType.CREATE_EVALUATOR_REQUEST
|
||||
|
||||
// need to implement this manually because [Pattern.equals] returns false for two patterns
|
||||
// that have the same underlying pattern string.
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null) return false
|
||||
if (other !is CreateEvaluatorRequest) return false
|
||||
return requestId == other.requestId &&
|
||||
Objects.equals(
|
||||
allowedModules?.map { it.pattern() },
|
||||
other.allowedModules?.map { it.pattern() }
|
||||
) &&
|
||||
Objects.equals(
|
||||
allowedResources?.map { it.pattern() },
|
||||
other.allowedResources?.map { it.pattern() }
|
||||
) &&
|
||||
clientModuleReaders.equalsNullable(other.clientModuleReaders) &&
|
||||
clientResourceReaders.equalsNullable(other.clientResourceReaders) &&
|
||||
modulePaths.equalsNullable(other.modulePaths) &&
|
||||
env.equalsNullable(other.env) &&
|
||||
properties.equalsNullable(other.properties) &&
|
||||
timeout.equalsNullable(other.timeout) &&
|
||||
rootDir.equalsNullable(other.rootDir) &&
|
||||
cacheDir.equalsNullable(other.cacheDir) &&
|
||||
outputFormat.equalsNullable(other.outputFormat) &&
|
||||
project.equalsNullable(other.project)
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode") // false duplicate within method
|
||||
override fun hashCode(): Int {
|
||||
var result = requestId.hashCode()
|
||||
result = 31 * result + allowedModules?.map { it.pattern() }.hashCode()
|
||||
result = 31 * result + allowedResources?.map { it.pattern() }.hashCode()
|
||||
result = 31 * result + clientModuleReaders.hashCode()
|
||||
result = 31 * result + clientResourceReaders.hashCode()
|
||||
result = 31 * result + modulePaths.hashCode()
|
||||
result = 31 * result + env.hashCode()
|
||||
result = 31 * result + properties.hashCode()
|
||||
result = 31 * result + timeout.hashCode()
|
||||
result = 31 * result + rootDir.hashCode()
|
||||
result = 31 * result + cacheDir.hashCode()
|
||||
result = 31 * result + outputFormat.hashCode()
|
||||
result = 31 * result + project.hashCode()
|
||||
result = 31 * result + type.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class CreateEvaluatorResponse(
|
||||
override val requestId: Long,
|
||||
val evaluatorId: Long?,
|
||||
val error: String?,
|
||||
) : ServerResponseMessage() {
|
||||
override val type
|
||||
get() = MessageType.CREATE_EVALUATOR_RESPONSE
|
||||
}
|
||||
|
||||
data class ListResourcesRequest(override val requestId: Long, val evaluatorId: Long, val uri: URI) :
|
||||
ServerRequestMessage() {
|
||||
override val type: MessageType
|
||||
get() = MessageType.LIST_RESOURCES_REQUEST
|
||||
}
|
||||
|
||||
data class ListResourcesResponse(
|
||||
override val requestId: Long,
|
||||
val evaluatorId: Long,
|
||||
val pathElements: List<PathElement>?,
|
||||
val error: String?
|
||||
) : ClientResponseMessage() {
|
||||
override val type: MessageType
|
||||
get() = MessageType.LIST_RESOURCES_RESPONSE
|
||||
}
|
||||
|
||||
data class ListModulesRequest(override val requestId: Long, val evaluatorId: Long, val uri: URI) :
|
||||
ServerRequestMessage() {
|
||||
override val type: MessageType
|
||||
get() = MessageType.LIST_MODULES_REQUEST
|
||||
}
|
||||
|
||||
data class ListModulesResponse(
|
||||
override val requestId: Long,
|
||||
val evaluatorId: Long,
|
||||
val pathElements: List<PathElement>?,
|
||||
val error: String?
|
||||
) : ClientResponseMessage() {
|
||||
override val type: MessageType
|
||||
get() = MessageType.LIST_MODULES_RESPONSE
|
||||
}
|
||||
|
||||
data class CloseEvaluator(val evaluatorId: Long) : ClientOneWayMessage() {
|
||||
override val type = MessageType.CLOSE_EVALUATOR
|
||||
}
|
||||
|
||||
data class EvaluateRequest(
|
||||
override val requestId: Long,
|
||||
val evaluatorId: Long,
|
||||
val moduleUri: URI,
|
||||
val moduleText: String?,
|
||||
val expr: String?
|
||||
) : ClientRequestMessage() {
|
||||
override val type = MessageType.EVALUATE_REQUEST
|
||||
}
|
||||
|
||||
data class EvaluateResponse(
|
||||
override val requestId: Long,
|
||||
val evaluatorId: Long,
|
||||
val result: ByteArray?,
|
||||
val error: String?
|
||||
) : ServerResponseMessage() {
|
||||
override val type
|
||||
get() = MessageType.EVALUATE_RESPONSE
|
||||
|
||||
// override to use [ByteArray.contentEquals]
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is EvaluateResponse) return false
|
||||
|
||||
return requestId == other.requestId &&
|
||||
evaluatorId == other.evaluatorId &&
|
||||
result.contentEquals(other.result) &&
|
||||
error == other.error
|
||||
}
|
||||
|
||||
// override to use [ByteArray.contentHashCode]
|
||||
override fun hashCode(): Int {
|
||||
var result1 = requestId.hashCode()
|
||||
result1 = 31 * result1 + evaluatorId.hashCode()
|
||||
result1 = 31 * result1 + result.contentHashCode()
|
||||
result1 = 31 * result1 + error.hashCode()
|
||||
return result1
|
||||
}
|
||||
}
|
||||
|
||||
data class LogMessage(
|
||||
val evaluatorId: Long,
|
||||
val level: Int,
|
||||
val message: String,
|
||||
val frameUri: String
|
||||
) : ServerOneWayMessage() {
|
||||
override val type
|
||||
get() = MessageType.LOG_MESSAGE
|
||||
}
|
||||
|
||||
data class ReadResourceRequest(override val requestId: Long, val evaluatorId: Long, val uri: URI) :
|
||||
ServerRequestMessage() {
|
||||
override val type
|
||||
get() = MessageType.READ_RESOURCE_REQUEST
|
||||
}
|
||||
|
||||
data class ReadResourceResponse(
|
||||
override val requestId: Long,
|
||||
val evaluatorId: Long,
|
||||
val contents: ByteArray?,
|
||||
val error: String?
|
||||
) : ClientResponseMessage() {
|
||||
override val type = MessageType.READ_RESOURCE_RESPONSE
|
||||
|
||||
// override to use [ByteArray.contentEquals]
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is ReadResourceResponse) return false
|
||||
|
||||
return requestId == other.requestId &&
|
||||
evaluatorId == other.evaluatorId &&
|
||||
contents.contentEquals(other.contents) &&
|
||||
error == other.error
|
||||
}
|
||||
|
||||
// override to use [ByteArray.contentHashCode]
|
||||
override fun hashCode(): Int {
|
||||
var result = requestId.hashCode()
|
||||
result = 31 * result + evaluatorId.hashCode()
|
||||
result = 31 * result + contents.contentHashCode()
|
||||
result = 31 * result + error.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class ReadModuleRequest(override val requestId: Long, val evaluatorId: Long, val uri: URI) :
|
||||
ServerRequestMessage() {
|
||||
override val type
|
||||
get() = MessageType.READ_MODULE_REQUEST
|
||||
}
|
||||
|
||||
data class ReadModuleResponse(
|
||||
override val requestId: Long,
|
||||
val evaluatorId: Long,
|
||||
val contents: String?,
|
||||
val error: String?
|
||||
) : ClientResponseMessage() {
|
||||
override val type = MessageType.READ_MODULE_RESPONSE
|
||||
}
|
||||
21
pkl-server/src/main/kotlin/org/pkl/server/MessageDecoder.kt
Normal file
21
pkl-server/src/main/kotlin/org/pkl/server/MessageDecoder.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
/** Decodes a stream of messages. */
|
||||
internal interface MessageDecoder {
|
||||
fun decode(): Message?
|
||||
}
|
||||
31
pkl-server/src/main/kotlin/org/pkl/server/MessageDecoders.kt
Normal file
31
pkl-server/src/main/kotlin/org/pkl/server/MessageDecoders.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.io.InputStream
|
||||
import org.msgpack.core.MessagePack
|
||||
import org.msgpack.core.MessageUnpacker
|
||||
|
||||
/** Factory methods for creating [MessageDecoder]s. */
|
||||
internal object MessageDecoders {
|
||||
fun from(stream: InputStream): MessageDecoder =
|
||||
MessagePackDecoder(MessagePack.newDefaultUnpacker(stream))
|
||||
|
||||
fun from(unpacker: MessageUnpacker): MessageDecoder = MessagePackDecoder(unpacker)
|
||||
|
||||
fun from(array: ByteArray): MessageDecoder =
|
||||
MessagePackDecoder(MessagePack.newDefaultUnpacker(array))
|
||||
}
|
||||
21
pkl-server/src/main/kotlin/org/pkl/server/MessageEncoder.kt
Normal file
21
pkl-server/src/main/kotlin/org/pkl/server/MessageEncoder.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
/** Encodes a stream of messages. */
|
||||
internal interface MessageEncoder {
|
||||
fun encode(msg: Message)
|
||||
}
|
||||
28
pkl-server/src/main/kotlin/org/pkl/server/MessageEncoders.kt
Normal file
28
pkl-server/src/main/kotlin/org/pkl/server/MessageEncoders.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.io.OutputStream
|
||||
import org.msgpack.core.MessagePack
|
||||
import org.msgpack.core.MessagePacker
|
||||
|
||||
/** Factory methods for creating [MessageEncoder]s. */
|
||||
internal object MessageEncoders {
|
||||
fun into(stream: OutputStream): MessageEncoder =
|
||||
MessagePackEncoder(MessagePack.newDefaultPacker(stream))
|
||||
|
||||
fun into(packer: MessagePacker): MessageEncoder = MessagePackEncoder(packer)
|
||||
}
|
||||
276
pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt
Normal file
276
pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt
Normal file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.util.regex.Pattern
|
||||
import org.msgpack.core.MessageTypeException
|
||||
import org.msgpack.core.MessageUnpacker
|
||||
import org.msgpack.value.Value
|
||||
import org.msgpack.value.impl.ImmutableStringValueImpl
|
||||
import org.pkl.core.module.PathElement
|
||||
import org.pkl.core.packages.Checksums
|
||||
|
||||
internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : MessageDecoder {
|
||||
override fun decode(): Message? {
|
||||
if (!unpacker.hasNext()) return null
|
||||
|
||||
val code =
|
||||
try {
|
||||
val arraySize = unpacker.unpackArrayHeader()
|
||||
if (arraySize != 2) {
|
||||
throw DecodeException("Malformed message header (expected size 2, but got $arraySize).")
|
||||
}
|
||||
unpacker.unpackInt()
|
||||
} catch (e: MessageTypeException) {
|
||||
throw DecodeException("Malformed message header.", e)
|
||||
}
|
||||
|
||||
return try {
|
||||
val map = unpacker.unpackValue().asMapValue().map()
|
||||
when (code) {
|
||||
MessageType.CREATE_EVALUATOR_REQUEST.code -> {
|
||||
CreateEvaluatorRequest(
|
||||
requestId = map.get("requestId").asIntegerValue().asLong(),
|
||||
allowedModules = map.unpackStringListOrNull("allowedModules")?.map(Pattern::compile),
|
||||
allowedResources =
|
||||
map.unpackStringListOrNull("allowedResources")?.map(Pattern::compile),
|
||||
clientModuleReaders = map.unpackModuleReaderSpec("clientModuleReaders"),
|
||||
clientResourceReaders = map.unpackResourceReaderSpec("clientResourceReaders"),
|
||||
modulePaths = map.unpackStringListOrNull("modulePaths")?.map(Path::of),
|
||||
env = map.unpackStringMapOrNull("env"),
|
||||
properties = map.unpackStringMapOrNull("properties"),
|
||||
timeout = map.unpackLongOrNull("timeoutSeconds")?.let(Duration::ofSeconds),
|
||||
rootDir = map.unpackStringOrNull("rootDir")?.let(Path::of),
|
||||
cacheDir = map.unpackStringOrNull("cacheDir")?.let(Path::of),
|
||||
outputFormat = map.unpackStringOrNull("outputFormat"),
|
||||
project = map.unpackProject("project")
|
||||
)
|
||||
}
|
||||
MessageType.CREATE_EVALUATOR_RESPONSE.code -> {
|
||||
CreateEvaluatorResponse(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLongOrNull("evaluatorId"),
|
||||
error = map.unpackStringOrNull("error")
|
||||
)
|
||||
}
|
||||
MessageType.CLOSE_EVALUATOR.code -> {
|
||||
CloseEvaluator(evaluatorId = map.unpackLong("evaluatorId"))
|
||||
}
|
||||
MessageType.EVALUATE_REQUEST.code -> {
|
||||
EvaluateRequest(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
moduleUri = map.unpackString("moduleUri").let(::URI),
|
||||
moduleText = map.unpackStringOrNull("moduleText"),
|
||||
expr = map.unpackStringOrNull("expr")
|
||||
)
|
||||
}
|
||||
MessageType.EVALUATE_RESPONSE.code -> {
|
||||
EvaluateResponse(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
result = map.unpackByteArrayOrNull("result"),
|
||||
error = map.unpackStringOrNull("error")
|
||||
)
|
||||
}
|
||||
MessageType.LOG_MESSAGE.code -> {
|
||||
LogMessage(
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
level = map.unpackIntValue("level"),
|
||||
message = map.unpackString("message"),
|
||||
frameUri = map.unpackString("frameUri")
|
||||
)
|
||||
}
|
||||
MessageType.READ_RESOURCE_REQUEST.code -> {
|
||||
ReadResourceRequest(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
uri = map.unpackString("uri").let(::URI)
|
||||
)
|
||||
}
|
||||
MessageType.READ_RESOURCE_RESPONSE.code -> {
|
||||
ReadResourceResponse(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
contents = map.unpackByteArrayOrNull("contents"),
|
||||
error = map.unpackStringOrNull("error")
|
||||
)
|
||||
}
|
||||
MessageType.READ_MODULE_REQUEST.code -> {
|
||||
ReadModuleRequest(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
uri = map.unpackString("uri").let(::URI)
|
||||
)
|
||||
}
|
||||
MessageType.READ_MODULE_RESPONSE.code -> {
|
||||
ReadModuleResponse(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
contents = map.unpackStringOrNull("contents"),
|
||||
error = map.unpackStringOrNull("error")
|
||||
)
|
||||
}
|
||||
MessageType.LIST_MODULES_REQUEST.code -> {
|
||||
ListModulesRequest(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
uri = map.unpackString("uri").let(::URI)
|
||||
)
|
||||
}
|
||||
MessageType.LIST_MODULES_RESPONSE.code -> {
|
||||
ListModulesResponse(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
pathElements = map.unpackPathElements("pathElements"),
|
||||
error = map.unpackStringOrNull("error")
|
||||
)
|
||||
}
|
||||
MessageType.LIST_RESOURCES_REQUEST.code -> {
|
||||
ListResourcesRequest(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
uri = map.unpackString("uri").let(::URI)
|
||||
)
|
||||
}
|
||||
MessageType.LIST_RESOURCES_RESPONSE.code -> {
|
||||
ListResourcesResponse(
|
||||
requestId = map.unpackLong("requestId"),
|
||||
evaluatorId = map.unpackLong("evaluatorId"),
|
||||
pathElements = map.unpackPathElements("pathElements"),
|
||||
error = map.unpackStringOrNull("error")
|
||||
)
|
||||
}
|
||||
else -> throw ProtocolException("Invalid message code: $code")
|
||||
}
|
||||
} catch (e: MessageTypeException) {
|
||||
throw DecodeException("Malformed message body for message with code `$code`.", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Array<Value>.unpackValueOrNull(key: String): Value? {
|
||||
for (i in indices.step(2)) {
|
||||
val currKey = this[i].asStringValue().asString()
|
||||
if (currKey == key) return this[i + 1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Map<Value, Value>.getNullable(key: String): Value? =
|
||||
this[ImmutableStringValueImpl(key)]
|
||||
|
||||
private fun Map<Value, Value>.get(key: String): Value =
|
||||
getNullable(key) ?: throw DecodeException("Missing message parameter `$key`")
|
||||
|
||||
private fun Array<Value>.unpackValue(key: String): Value =
|
||||
unpackValueOrNull(key) ?: throw DecodeException("Missing message parameter `$key`.")
|
||||
|
||||
private fun Map<Value, Value>.unpackStringListOrNull(key: String): List<String>? {
|
||||
val value = getNullable(key) ?: return null
|
||||
return value.asArrayValue().map { it.asStringValue().asString() }
|
||||
}
|
||||
|
||||
private fun Map<Value, Value>.unpackStringMapOrNull(key: String): Map<String, String>? {
|
||||
val value = getNullable(key) ?: return null
|
||||
return value.asMapValue().entrySet().associate { (k, v) ->
|
||||
k.asStringValue().asString() to v.asStringValue().asString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Map<Value, Value>.unpackLong(key: String): Long = get(key).asIntegerValue().asLong()
|
||||
|
||||
private fun Map<Value, Value>.unpackBoolean(key: String): Boolean =
|
||||
get(key).asBooleanValue().boolean
|
||||
|
||||
private fun Map<Value, Value>.unpackBooleanOrNull(key: String): Boolean? =
|
||||
getNullable(key)?.asBooleanValue()?.boolean
|
||||
|
||||
private fun Map<Value, Value>.unpackLongOrNull(key: String): Long? =
|
||||
getNullable(key)?.asIntegerValue()?.asLong()
|
||||
|
||||
private fun Map<Value, Value>.unpackIntValue(key: String): Int = get(key).asIntegerValue().asInt()
|
||||
|
||||
private fun Map<Value, Value>.unpackString(key: String): String =
|
||||
get(key).asStringValue().asString()
|
||||
|
||||
private fun Map<Value, Value>.unpackStringOrNull(key: String): String? =
|
||||
getNullable(key)?.asStringValue()?.asString()
|
||||
|
||||
private fun Map<Value, Value>.unpackByteArrayOrNull(key: String): ByteArray? =
|
||||
getNullable(key)?.asBinaryValue()?.asByteArray()
|
||||
|
||||
private fun Map<Value, Value>.unpackPathElements(key: String): List<PathElement>? =
|
||||
getNullable(key)?.asArrayValue()?.map { pathElement ->
|
||||
val map = pathElement.asMapValue().map()
|
||||
PathElement(map.unpackString("name"), map.unpackBoolean("isDirectory"))
|
||||
}
|
||||
|
||||
private fun Map<Value, Value>.unpackModuleReaderSpec(name: String): List<ModuleReaderSpec>? {
|
||||
val keys = getNullable(name) ?: return null
|
||||
return keys.asArrayValue().toList().map { value ->
|
||||
val readerMap = value.asMapValue().map()
|
||||
ModuleReaderSpec(
|
||||
scheme = readerMap.unpackString("scheme"),
|
||||
hasHierarchicalUris = readerMap.unpackBoolean("hasHierarchicalUris"),
|
||||
isLocal = readerMap.unpackBoolean("isLocal"),
|
||||
isGlobbable = readerMap.unpackBoolean("isGlobbable")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Map<Value, Value>.unpackResourceReaderSpec(name: String): List<ResourceReaderSpec> {
|
||||
val keys = getNullable(name) ?: return emptyList()
|
||||
return keys.asArrayValue().toList().map { value ->
|
||||
val readerMap = value.asMapValue().map()
|
||||
ResourceReaderSpec(
|
||||
scheme = readerMap.unpackString("scheme"),
|
||||
hasHierarchicalUris = readerMap.unpackBoolean("hasHierarchicalUris"),
|
||||
isGlobbable = readerMap.unpackBoolean("isGlobbable")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Map<Value, Value>.unpackProject(name: String): Project? {
|
||||
val projMap = getNullable(name)?.asMapValue()?.map() ?: return null
|
||||
val projectFileUri = URI(projMap.unpackString("projectFileUri"))
|
||||
val dependencies = projMap.unpackDependencies("dependencies")
|
||||
return Project(projectFileUri, null, dependencies)
|
||||
}
|
||||
|
||||
private fun Map<Value, Value>.unpackDependencies(name: String): Map<String, Dependency> {
|
||||
val mapValue = get(name).asMapValue().map()
|
||||
return mapValue.entries.associate { (key, value) ->
|
||||
val dependencyName = key.asStringValue().asString()
|
||||
val dependencyObj = value.asMapValue().map()
|
||||
val type = dependencyObj.unpackString("type")
|
||||
val packageUri = URI(dependencyObj.unpackString("packageUri"))
|
||||
if (type == DependencyType.REMOTE.value) {
|
||||
val checksums =
|
||||
dependencyObj.getNullable("checksums")?.asMapValue()?.map()?.let { obj ->
|
||||
val sha256 = obj.unpackString("sha256")
|
||||
Checksums(sha256)
|
||||
}
|
||||
return@associate dependencyName to RemoteDependency(packageUri, checksums)
|
||||
}
|
||||
val dependencies = dependencyObj.unpackDependencies("dependencies")
|
||||
val projectFileUri = dependencyObj.unpackString("projectFileUri")
|
||||
dependencyName to Project(URI(projectFileUri), packageUri, dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
303
pkl-server/src/main/kotlin/org/pkl/server/MessagePackEncoder.kt
Normal file
303
pkl-server/src/main/kotlin/org/pkl/server/MessagePackEncoder.kt
Normal file
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import kotlin.io.path.pathString
|
||||
import org.msgpack.core.MessagePacker
|
||||
import org.pkl.core.module.PathElement
|
||||
import org.pkl.core.packages.Checksums
|
||||
|
||||
internal class MessagePackEncoder(private val packer: MessagePacker) : MessageEncoder {
|
||||
private fun MessagePacker.packModuleReaderSpec(reader: ModuleReaderSpec) {
|
||||
packMapHeader(4)
|
||||
packKeyValue("scheme", reader.scheme)
|
||||
packKeyValue("hasHierarchicalUris", reader.hasHierarchicalUris)
|
||||
packKeyValue("isLocal", reader.isLocal)
|
||||
packKeyValue("isGlobbable", reader.isGlobbable)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packResourceReaderSpec(reader: ResourceReaderSpec) {
|
||||
packMapHeader(3)
|
||||
packKeyValue("scheme", reader.scheme)
|
||||
packKeyValue("hasHierarchicalUris", reader.hasHierarchicalUris)
|
||||
packKeyValue("isGlobbable", reader.isGlobbable)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packPathElement(pathElement: PathElement) {
|
||||
packMapHeader(2)
|
||||
packKeyValue("name", pathElement.name)
|
||||
packKeyValue("isDirectory", pathElement.isDirectory)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packProject(project: Project) {
|
||||
packMapHeader(2)
|
||||
packKeyValue("projectFileUri", project.projectFileUri.toString())
|
||||
packString("dependencies")
|
||||
packDependencies(project.dependencies)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packDependencies(dependencies: Map<String, Dependency>) {
|
||||
packMapHeader(dependencies.size)
|
||||
for ((name, dep) in dependencies) {
|
||||
packString(name)
|
||||
if (dep is Project) {
|
||||
packMapHeader(4)
|
||||
packKeyValue("type", dep.type.value)
|
||||
packKeyValue("packageUri", dep.packageUri.toString())
|
||||
packKeyValue("projectFileUri", dep.projectFileUri.toString())
|
||||
packString("dependencies")
|
||||
packDependencies(dep.dependencies)
|
||||
} else {
|
||||
dep as RemoteDependency
|
||||
packMapHeader(dep.checksums?.let { 3 } ?: 2)
|
||||
packKeyValue("type", dep.type.value)
|
||||
packKeyValue("packageUri", dep.packageUri.toString())
|
||||
dep.checksums?.let { checksums ->
|
||||
packString("checksums")
|
||||
packChecksums(checksums)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessagePacker.packChecksums(checksums: Checksums) {
|
||||
packMapHeader(1)
|
||||
packKeyValue("sha256", checksums.sha256)
|
||||
}
|
||||
|
||||
override fun encode(msg: Message) =
|
||||
with(packer) {
|
||||
packArrayHeader(2)
|
||||
packInt(msg.type.code)
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
when (msg.type.code) {
|
||||
MessageType.CREATE_EVALUATOR_REQUEST.code -> {
|
||||
msg as CreateEvaluatorRequest
|
||||
packMapHeader(8, msg.timeout, msg.rootDir, msg.cacheDir, msg.outputFormat, msg.project)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("allowedModules", msg.allowedModules?.map { it.toString() })
|
||||
packKeyValue("allowedResources", msg.allowedResources?.map { it.toString() })
|
||||
if (msg.clientModuleReaders != null) {
|
||||
packString("clientModuleReaders")
|
||||
packArrayHeader(msg.clientModuleReaders.size)
|
||||
for (moduleReader in msg.clientModuleReaders) {
|
||||
packModuleReaderSpec(moduleReader)
|
||||
}
|
||||
}
|
||||
if (msg.clientResourceReaders != null) {
|
||||
packString("clientResourceReaders")
|
||||
packArrayHeader(msg.clientResourceReaders.size)
|
||||
for (resourceReader in msg.clientResourceReaders) {
|
||||
packResourceReaderSpec(resourceReader)
|
||||
}
|
||||
}
|
||||
packKeyValue("modulePaths", msg.modulePaths?.map { it.pathString })
|
||||
packKeyValue("env", msg.env)
|
||||
packKeyValue("properties", msg.properties)
|
||||
packKeyValue("timeoutSeconds", msg.timeout?.toSeconds())
|
||||
packKeyValue("rootDir", msg.rootDir?.pathString)
|
||||
packKeyValue("cacheDir", msg.cacheDir?.pathString)
|
||||
packKeyValue("outputFormat", msg.outputFormat)
|
||||
if (msg.project != null) {
|
||||
packString("project")
|
||||
packProject(msg.project)
|
||||
}
|
||||
}
|
||||
MessageType.CREATE_EVALUATOR_RESPONSE.code -> {
|
||||
msg as CreateEvaluatorResponse
|
||||
packMapHeader(1, msg.evaluatorId, msg.error)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("error", msg.error)
|
||||
}
|
||||
MessageType.CLOSE_EVALUATOR.code -> {
|
||||
msg as CloseEvaluator
|
||||
packMapHeader(1)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
}
|
||||
MessageType.EVALUATE_REQUEST.code -> {
|
||||
msg as EvaluateRequest
|
||||
packMapHeader(3, msg.moduleText, msg.expr)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("moduleUri", msg.moduleUri.toString())
|
||||
packKeyValue("moduleText", msg.moduleText)
|
||||
packKeyValue("expr", msg.expr)
|
||||
}
|
||||
MessageType.EVALUATE_RESPONSE.code -> {
|
||||
msg as EvaluateResponse
|
||||
packMapHeader(2, msg.result, msg.error)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("result", msg.result)
|
||||
packKeyValue("error", msg.error)
|
||||
}
|
||||
MessageType.LOG_MESSAGE.code -> {
|
||||
msg as LogMessage
|
||||
packMapHeader(4)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("level", msg.level)
|
||||
packKeyValue("message", msg.message)
|
||||
packKeyValue("frameUri", msg.frameUri)
|
||||
}
|
||||
MessageType.READ_RESOURCE_REQUEST.code -> {
|
||||
msg as ReadResourceRequest
|
||||
packMapHeader(3)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("uri", msg.uri.toString())
|
||||
}
|
||||
MessageType.READ_RESOURCE_RESPONSE.code -> {
|
||||
msg as ReadResourceResponse
|
||||
packMapHeader(2, msg.contents, msg.error)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("contents", msg.contents)
|
||||
packKeyValue("error", msg.error)
|
||||
}
|
||||
MessageType.READ_MODULE_REQUEST.code -> {
|
||||
msg as ReadModuleRequest
|
||||
packMapHeader(3)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("uri", msg.uri.toString())
|
||||
}
|
||||
MessageType.READ_MODULE_RESPONSE.code -> {
|
||||
msg as ReadModuleResponse
|
||||
packMapHeader(2, msg.contents, msg.error)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("contents", msg.contents)
|
||||
packKeyValue("error", msg.error)
|
||||
}
|
||||
MessageType.LIST_MODULES_REQUEST.code -> {
|
||||
msg as ListModulesRequest
|
||||
packMapHeader(3)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("uri", msg.uri.toString())
|
||||
}
|
||||
MessageType.LIST_MODULES_RESPONSE.code -> {
|
||||
msg as ListModulesResponse
|
||||
packMapHeader(2, msg.pathElements, msg.error)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
if (msg.pathElements != null) {
|
||||
packString("pathElements")
|
||||
packArrayHeader(msg.pathElements.size)
|
||||
for (pathElement in msg.pathElements) {
|
||||
packPathElement(pathElement)
|
||||
}
|
||||
}
|
||||
packKeyValue("error", msg.error)
|
||||
}
|
||||
MessageType.LIST_RESOURCES_REQUEST.code -> {
|
||||
msg as ListResourcesRequest
|
||||
packMapHeader(3)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
packKeyValue("uri", msg.uri.toString())
|
||||
}
|
||||
MessageType.LIST_RESOURCES_RESPONSE.code -> {
|
||||
msg as ListResourcesResponse
|
||||
packMapHeader(2, msg.pathElements, msg.error)
|
||||
packKeyValue("requestId", msg.requestId)
|
||||
packKeyValue("evaluatorId", msg.evaluatorId)
|
||||
if (msg.pathElements != null) {
|
||||
packString("pathElements")
|
||||
packArrayHeader(msg.pathElements.size)
|
||||
for (pathElement in msg.pathElements) {
|
||||
packPathElement(pathElement)
|
||||
}
|
||||
}
|
||||
packKeyValue("error", msg.error)
|
||||
}
|
||||
else -> {
|
||||
throw RuntimeException("Missing encoding for ${msg.javaClass.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
flush()
|
||||
}
|
||||
|
||||
private fun MessagePacker.packMapHeader(size: Int, value1: Any?, value2: Any?) =
|
||||
packMapHeader(size + (if (value1 != null) 1 else 0) + (if (value2 != null) 1 else 0))
|
||||
|
||||
private fun MessagePacker.packMapHeader(
|
||||
size: Int,
|
||||
value1: Any?,
|
||||
value2: Any?,
|
||||
value3: Any?,
|
||||
value4: Any?,
|
||||
value5: Any?
|
||||
) =
|
||||
packMapHeader(
|
||||
size +
|
||||
(if (value1 != null) 1 else 0) +
|
||||
(if (value2 != null) 1 else 0) +
|
||||
(if (value3 != null) 1 else 0) +
|
||||
(if (value4 != null) 1 else 0) +
|
||||
(if (value5 != null) 1 else 0)
|
||||
)
|
||||
|
||||
private fun MessagePacker.packKeyValue(name: String, value: Int?) {
|
||||
if (value == null) return
|
||||
packString(name)
|
||||
packInt(value)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packKeyValue(name: String, value: Long?) {
|
||||
if (value == null) return
|
||||
packString(name)
|
||||
packLong(value)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packKeyValue(name: String, value: String?) {
|
||||
if (value == null) return
|
||||
packString(name)
|
||||
packString(value)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packKeyValue(name: String, value: Collection<String>?) {
|
||||
if (value == null) return
|
||||
packString(name)
|
||||
packArrayHeader(value.size)
|
||||
for (elem in value) packString(elem)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packKeyValue(name: String, value: Map<String, String>?) {
|
||||
if (value == null) return
|
||||
packString(name)
|
||||
packMapHeader(value.size)
|
||||
for ((k, v) in value) {
|
||||
packString(k)
|
||||
packString(v)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessagePacker.packKeyValue(name: String, value: ByteArray?) {
|
||||
if (value == null) return
|
||||
packString(name)
|
||||
packBinaryHeader(value.size)
|
||||
writePayload(value)
|
||||
}
|
||||
|
||||
private fun MessagePacker.packKeyValue(name: String, value: Boolean) {
|
||||
packString(name)
|
||||
packBoolean(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
/** A bidirectional transport for sending and receiving messages. */
|
||||
interface MessageTransport : AutoCloseable {
|
||||
fun start(oneWayHandler: (OneWayMessage) -> Unit, requestHandler: (RequestMessage) -> Unit)
|
||||
|
||||
fun send(message: OneWayMessage)
|
||||
|
||||
fun send(message: RequestMessage, responseHandler: (ResponseMessage) -> Unit)
|
||||
|
||||
fun send(message: ResponseMessage)
|
||||
}
|
||||
135
pkl-server/src/main/kotlin/org/pkl/server/MessageTransports.kt
Normal file
135
pkl-server/src/main/kotlin/org/pkl/server/MessageTransports.kt
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/** Factory methods for creating [MessageTransport]s. */
|
||||
object MessageTransports {
|
||||
/** Creates a message transport that reads from [inputStream] and writes to [outputStream]. */
|
||||
fun stream(inputStream: InputStream, outputStream: OutputStream): MessageTransport {
|
||||
return EncodingMessageTransport(
|
||||
MessageDecoders.from(inputStream),
|
||||
MessageEncoders.into(outputStream)
|
||||
)
|
||||
}
|
||||
|
||||
/** Creates "client" and "server" transports that are directly connected to each other. */
|
||||
fun direct(): Pair<MessageTransport, MessageTransport> {
|
||||
val transport1 = DirectMessageTransport()
|
||||
val transport2 = DirectMessageTransport()
|
||||
transport1.other = transport2
|
||||
transport2.other = transport1
|
||||
return transport1 to transport2
|
||||
}
|
||||
|
||||
internal class EncodingMessageTransport(
|
||||
private val decoder: MessageDecoder,
|
||||
private val encoder: MessageEncoder,
|
||||
) : AbstractMessageTransport() {
|
||||
@Volatile private var isClosed: Boolean = false
|
||||
|
||||
override fun doStart() {
|
||||
while (!isClosed) {
|
||||
val message = decoder.decode() ?: return
|
||||
accept(message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun doClose() {
|
||||
isClosed = true
|
||||
}
|
||||
|
||||
override fun doSend(message: Message) {
|
||||
encoder.encode(message)
|
||||
}
|
||||
}
|
||||
|
||||
internal class DirectMessageTransport : AbstractMessageTransport() {
|
||||
lateinit var other: DirectMessageTransport
|
||||
|
||||
override fun doStart() {}
|
||||
|
||||
override fun doClose() {}
|
||||
|
||||
override fun doSend(message: Message) {
|
||||
other.accept(message)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: clean up callbacks if evaluation fails for some reason (ThreadInterrupt, timeout, etc)
|
||||
internal abstract class AbstractMessageTransport : MessageTransport {
|
||||
private lateinit var oneWayHandler: (OneWayMessage) -> Unit
|
||||
private lateinit var requestHandler: (RequestMessage) -> Unit
|
||||
private val responseHandlers: MutableMap<Long, (ResponseMessage) -> Unit> = ConcurrentHashMap()
|
||||
|
||||
protected abstract fun doStart()
|
||||
|
||||
protected abstract fun doClose()
|
||||
|
||||
protected abstract fun doSend(message: Message)
|
||||
|
||||
protected fun accept(message: Message) {
|
||||
log("Received message: $message")
|
||||
when (message) {
|
||||
is OneWayMessage -> oneWayHandler(message)
|
||||
is RequestMessage -> requestHandler(message)
|
||||
is ResponseMessage -> {
|
||||
val handler =
|
||||
responseHandlers.remove(message.requestId)
|
||||
?: throw ProtocolException(
|
||||
"Received response ${message.javaClass.simpleName} for unknown request ID `${message.requestId}`."
|
||||
)
|
||||
handler(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final override fun start(
|
||||
oneWayHandler: (OneWayMessage) -> Unit,
|
||||
requestHandler: (RequestMessage) -> Unit
|
||||
) {
|
||||
log("Starting transport: $this")
|
||||
this.oneWayHandler = oneWayHandler
|
||||
this.requestHandler = requestHandler
|
||||
doStart()
|
||||
}
|
||||
|
||||
final override fun close() {
|
||||
log("Closing transport: $this")
|
||||
doClose()
|
||||
responseHandlers.clear()
|
||||
}
|
||||
|
||||
override fun send(message: OneWayMessage) {
|
||||
log("Sending message: $message")
|
||||
doSend(message)
|
||||
}
|
||||
|
||||
override fun send(message: RequestMessage, responseHandler: (ResponseMessage) -> Unit) {
|
||||
log("Sending message: $message")
|
||||
responseHandlers[message.requestId] = responseHandler
|
||||
return doSend(message)
|
||||
}
|
||||
|
||||
override fun send(message: ResponseMessage) {
|
||||
log("Sending message: $message")
|
||||
doSend(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
225
pkl-server/src/main/kotlin/org/pkl/server/Server.kt
Normal file
225
pkl-server/src/main/kotlin/org/pkl/server/Server.kt
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.net.URI
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.random.Random
|
||||
import org.pkl.core.*
|
||||
import org.pkl.core.module.ModuleKeyFactories
|
||||
import org.pkl.core.module.ModuleKeyFactory
|
||||
import org.pkl.core.module.ModulePathResolver
|
||||
import org.pkl.core.packages.PackageUri
|
||||
import org.pkl.core.project.DeclaredDependencies
|
||||
import org.pkl.core.resource.ResourceReader
|
||||
import org.pkl.core.resource.ResourceReaders
|
||||
|
||||
class Server(private val transport: MessageTransport) : AutoCloseable {
|
||||
private val evaluators: MutableMap<Long, BinaryEvaluator> = ConcurrentHashMap()
|
||||
|
||||
// https://github.com/jano7/executor would be the perfect executor here
|
||||
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
|
||||
|
||||
/** Starts listening to incoming messages */
|
||||
fun start() {
|
||||
transport.start(
|
||||
{ message ->
|
||||
when (message) {
|
||||
is CloseEvaluator -> handleCloseEvaluator(message)
|
||||
else -> throw ProtocolException("Unexpected incoming one-way message: $message")
|
||||
}
|
||||
},
|
||||
{ message ->
|
||||
when (message) {
|
||||
is CreateEvaluatorRequest -> handleCreateEvaluator(message)
|
||||
is EvaluateRequest -> handleEvaluate(message)
|
||||
else -> throw ProtocolException("Unexpected incoming request message: $message")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops listening to incoming messages, cancels pending evaluation requests, and releases
|
||||
* resources held by this server.
|
||||
*/
|
||||
override fun close() {
|
||||
transport.closeQuietly()
|
||||
for ((_, evaluator) in evaluators) {
|
||||
// if currently in use, blocks until cancellation complete
|
||||
evaluator.closeQuietly()
|
||||
}
|
||||
executor.shutdown()
|
||||
}
|
||||
|
||||
private fun handleCreateEvaluator(message: CreateEvaluatorRequest) {
|
||||
val evaluatorId = Random.Default.nextLong()
|
||||
val baseResponse = CreateEvaluatorResponse(message.requestId, evaluatorId = null, error = null)
|
||||
|
||||
val evaluator =
|
||||
try {
|
||||
createEvaluator(message, evaluatorId)
|
||||
} catch (e: ServerException) {
|
||||
transport.send(baseResponse.copy(error = e.message))
|
||||
return
|
||||
}
|
||||
|
||||
evaluators[evaluatorId] = evaluator
|
||||
transport.send(baseResponse.copy(evaluatorId = evaluatorId))
|
||||
}
|
||||
|
||||
private fun handleEvaluate(msg: EvaluateRequest) {
|
||||
val baseResponse = EvaluateResponse(msg.requestId, msg.evaluatorId, result = null, error = null)
|
||||
|
||||
val evaluator = evaluators[msg.evaluatorId]
|
||||
if (evaluator == null) {
|
||||
transport.send(
|
||||
baseResponse.copy(error = "Evaluator with ID ${msg.evaluatorId} was not found.")
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
executor.execute {
|
||||
try {
|
||||
val resp = evaluator.evaluate(ModuleSource.create(msg.moduleUri, msg.moduleText), msg.expr)
|
||||
transport.send(baseResponse.copy(result = resp))
|
||||
} catch (e: PklBugException) {
|
||||
transport.send(baseResponse.copy(error = e.toString()))
|
||||
} catch (e: PklException) {
|
||||
transport.send(baseResponse.copy(error = e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCloseEvaluator(message: CloseEvaluator) {
|
||||
val evaluator = evaluators.remove(message.evaluatorId)
|
||||
if (evaluator == null) {
|
||||
log("Ignoring close request for unknown evaluator ID `${message.evaluatorId}`.")
|
||||
return
|
||||
}
|
||||
evaluator.close()
|
||||
}
|
||||
|
||||
private fun buildDeclaredDependencies(
|
||||
projectFileUri: URI,
|
||||
dependencies: Map<String, Dependency>,
|
||||
myPackageUri: URI?
|
||||
): DeclaredDependencies {
|
||||
val remoteDependencies = buildMap {
|
||||
for ((key, dep) in dependencies) {
|
||||
if (dep is RemoteDependency) {
|
||||
put(
|
||||
key,
|
||||
org.pkl.core.packages.Dependency.RemoteDependency(
|
||||
PackageUri(dep.packageUri),
|
||||
dep.checksums
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val localDependencies = buildMap {
|
||||
for ((key, dep) in dependencies) {
|
||||
if (dep is Project) {
|
||||
val localDep =
|
||||
buildDeclaredDependencies(dep.projectFileUri, dep.dependencies, dep.packageUri)
|
||||
put(key, localDep)
|
||||
}
|
||||
}
|
||||
}
|
||||
return DeclaredDependencies(
|
||||
remoteDependencies,
|
||||
localDependencies,
|
||||
projectFileUri,
|
||||
myPackageUri?.let(::PackageUri)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createEvaluator(message: CreateEvaluatorRequest, evaluatorId: Long): BinaryEvaluator {
|
||||
val modulePaths = message.modulePaths ?: emptyList()
|
||||
val resolver = ModulePathResolver(modulePaths)
|
||||
val allowedModules = message.allowedModules ?: emptyList()
|
||||
val allowedResources = message.allowedResources ?: emptyList()
|
||||
val rootDir = message.rootDir
|
||||
val env = message.env ?: emptyMap()
|
||||
val properties = message.properties ?: emptyMap()
|
||||
val timeout = message.timeout
|
||||
val cacheDir = message.cacheDir
|
||||
val dependencies =
|
||||
message.project?.let { proj ->
|
||||
buildDeclaredDependencies(proj.projectFileUri, proj.dependencies, null)
|
||||
}
|
||||
log("Got dependencies: $dependencies")
|
||||
return BinaryEvaluator(
|
||||
StackFrameTransformers.defaultTransformer,
|
||||
SecurityManagers.standard(
|
||||
allowedModules,
|
||||
allowedResources,
|
||||
SecurityManagers.defaultTrustLevels,
|
||||
rootDir
|
||||
),
|
||||
ClientLogger(evaluatorId, transport),
|
||||
createModuleKeyFactories(message, evaluatorId, resolver),
|
||||
createResourceReaders(message, evaluatorId, resolver),
|
||||
env,
|
||||
properties,
|
||||
timeout,
|
||||
cacheDir,
|
||||
dependencies,
|
||||
message.outputFormat
|
||||
)
|
||||
}
|
||||
|
||||
private fun createResourceReaders(
|
||||
message: CreateEvaluatorRequest,
|
||||
evaluatorId: Long,
|
||||
modulePathResolver: ModulePathResolver
|
||||
): List<ResourceReader> = buildList {
|
||||
add(ResourceReaders.environmentVariable())
|
||||
add(ResourceReaders.externalProperty())
|
||||
add(ResourceReaders.file())
|
||||
add(ResourceReaders.http())
|
||||
add(ResourceReaders.https())
|
||||
add(ResourceReaders.pkg())
|
||||
add(ResourceReaders.projectpackage())
|
||||
add(ResourceReaders.modulePath(modulePathResolver))
|
||||
// add client-side resource readers last to ensure they win over builtin ones
|
||||
for (readerSpec in message.clientResourceReaders ?: emptyList()) {
|
||||
val resourceReader = ClientResourceReader(transport, evaluatorId, readerSpec)
|
||||
add(resourceReader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createModuleKeyFactories(
|
||||
message: CreateEvaluatorRequest,
|
||||
evaluatorId: Long,
|
||||
modulePathResolver: ModulePathResolver
|
||||
): List<ModuleKeyFactory> = buildList {
|
||||
// add client-side module key factory first to ensure it wins over builtin ones
|
||||
if (message.clientModuleReaders?.isNotEmpty() == true) {
|
||||
add(ClientModuleKeyFactory(message.clientModuleReaders, transport, evaluatorId))
|
||||
}
|
||||
add(ModuleKeyFactories.standardLibrary)
|
||||
addAll(ModuleKeyFactories.fromServiceProviders())
|
||||
add(ModuleKeyFactories.file)
|
||||
add(ModuleKeyFactories.modulePath(modulePathResolver))
|
||||
add(ModuleKeyFactories.pkg)
|
||||
add(ModuleKeyFactories.projectpackage)
|
||||
add(ModuleKeyFactories.genericUrl)
|
||||
}
|
||||
}
|
||||
24
pkl-server/src/main/kotlin/org/pkl/server/ServerException.kt
Normal file
24
pkl-server/src/main/kotlin/org/pkl/server/ServerException.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
sealed class ServerException(msg: String, cause: Throwable?) : Exception(msg, cause)
|
||||
|
||||
open class ProtocolException(msg: String, cause: Throwable? = null) : ServerException(msg, cause)
|
||||
|
||||
class InvalidCommandException(msg: String, cause: Throwable? = null) : ServerException(msg, cause)
|
||||
|
||||
class DecodeException(msg: String, cause: Throwable? = null) : ProtocolException(msg, cause)
|
||||
67
pkl-server/src/main/kotlin/org/pkl/server/Utils.kt
Normal file
67
pkl-server/src/main/kotlin/org/pkl/server/Utils.kt
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
import org.msgpack.core.MessageBufferPacker
|
||||
import org.msgpack.core.MessagePack
|
||||
|
||||
internal fun log(msg: String) {
|
||||
if (System.getenv("PKL_DEBUG") == "1") {
|
||||
System.err.println("[pkl-server] $msg")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun AutoCloseable.closeQuietly() {
|
||||
try {
|
||||
close()
|
||||
} catch (e: Exception) {
|
||||
log(e.message.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
internal val threadLocalBufferPacker: ThreadLocal<MessageBufferPacker> =
|
||||
ThreadLocal.withInitial { MessagePack.newDefaultBufferPacker() }
|
||||
|
||||
private val threadLocalEncoder: ThreadLocal<(Message) -> ByteArray> =
|
||||
ThreadLocal.withInitial {
|
||||
val packer = threadLocalBufferPacker.get()
|
||||
val encoder = MessageEncoders.into(packer);
|
||||
{ message: Message ->
|
||||
packer.clear()
|
||||
encoder.encode(message)
|
||||
packer.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun encode(message: Message): ByteArray {
|
||||
return threadLocalEncoder.get()(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is like [Future.get], except it throws the actual exception given to
|
||||
* [CompletableFuture.completeExceptionally].
|
||||
*
|
||||
* [Future.get] will wrap any exception in [ExecutionException], which is kind of silly.
|
||||
*/
|
||||
fun <T> Future<T>.getUnderlying(): T =
|
||||
try {
|
||||
get()
|
||||
} catch (e: ExecutionException) {
|
||||
throw e.cause!!
|
||||
}
|
||||
7
pkl-server/src/test/files/SnippetTests/input/basic.pkl
vendored
Normal file
7
pkl-server/src/test/files/SnippetTests/input/basic.pkl
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
res1 = "bar"
|
||||
res2 = ""
|
||||
res3 = 1
|
||||
res4 = 2.3
|
||||
res5 = true
|
||||
res6 = false
|
||||
res7 = null
|
||||
25
pkl-server/src/test/files/SnippetTests/input/classes.pkl
vendored
Normal file
25
pkl-server/src/test/files/SnippetTests/input/classes.pkl
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
module com.foo.bar.MyModule
|
||||
|
||||
class Person {
|
||||
firstName: String
|
||||
lastName: String
|
||||
age: Int
|
||||
}
|
||||
|
||||
barnOwl: Person = new {
|
||||
firstName = "Barn Owl"
|
||||
lastName = "Bird"
|
||||
age = 38
|
||||
}
|
||||
|
||||
pigeon: Person = new {
|
||||
firstName = "Pigeon"
|
||||
lastName = "Bird"
|
||||
age = 41
|
||||
}
|
||||
|
||||
typealias MyPerson = Person
|
||||
|
||||
personClass = Person
|
||||
|
||||
personTypeAlias = MyPerson
|
||||
11
pkl-server/src/test/files/SnippetTests/input/datasize.pkl
vendored
Normal file
11
pkl-server/src/test/files/SnippetTests/input/datasize.pkl
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
res1: DataSize = 1.b
|
||||
res2: DataSize = 2.kb
|
||||
res3: DataSize = 3.kib
|
||||
res4: DataSize = 4.mb
|
||||
res5: DataSize = 5.mib
|
||||
res6: DataSize = 6.gb
|
||||
res7: DataSize = 7.gib
|
||||
res8: DataSize = 8.tb
|
||||
res9: DataSize = 9.tib
|
||||
res10: DataSize = 10.pb
|
||||
res11: DataSize = 11.pib
|
||||
7
pkl-server/src/test/files/SnippetTests/input/duration.pkl
vendored
Normal file
7
pkl-server/src/test/files/SnippetTests/input/duration.pkl
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
res1 = 1.ns
|
||||
res2 = 2.us
|
||||
res3 = 3.ms
|
||||
res4 = 4.s
|
||||
res5 = 5.min
|
||||
res6 = 6.h
|
||||
res7 = 7.d
|
||||
2
pkl-server/src/test/files/SnippetTests/input/intseq.pkl
vendored
Normal file
2
pkl-server/src/test/files/SnippetTests/input/intseq.pkl
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
res1 = IntSeq(1, 3)
|
||||
res2 = IntSeq(1, 4).step(5)
|
||||
6
pkl-server/src/test/files/SnippetTests/input/list.pkl
vendored
Normal file
6
pkl-server/src/test/files/SnippetTests/input/list.pkl
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
res1: List<Int> = List(1, 3, 5, 7)
|
||||
res2: Listing<Int> = new { 2; 4; 6; 8 }
|
||||
res3: List<Int> = List()
|
||||
res4: Listing<Int> = new {}
|
||||
res5: List<List<Int>> = List(List(1, 2))
|
||||
res6: Listing<Listing<Int>> = new { new { 1; 2 } }
|
||||
15
pkl-server/src/test/files/SnippetTests/input/map.pkl
vendored
Normal file
15
pkl-server/src/test/files/SnippetTests/input/map.pkl
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
res1: Map = Map("foo", 1, "bar", 2)
|
||||
res2: Mapping = new {
|
||||
["foo"] = 1
|
||||
["bar"] = 2
|
||||
}
|
||||
res3: Mapping = new {
|
||||
["childMap"] = new Mapping {
|
||||
["childFoo"] = 3
|
||||
}
|
||||
}
|
||||
res4: Mapping = new {
|
||||
[Map("foo", 1)] = new Mapping {
|
||||
["bar"] = 2
|
||||
}
|
||||
}
|
||||
2
pkl-server/src/test/files/SnippetTests/input/pair.pkl
vendored
Normal file
2
pkl-server/src/test/files/SnippetTests/input/pair.pkl
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
res1 = Pair(1, 2)
|
||||
res2 = Pair("foo", "bar")
|
||||
3
pkl-server/src/test/files/SnippetTests/input/regex.pkl
vendored
Normal file
3
pkl-server/src/test/files/SnippetTests/input/regex.pkl
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
res1 = Regex("abc")
|
||||
res2 = Regex("")
|
||||
res3 = Regex("(?m)^abc$")
|
||||
3
pkl-server/src/test/files/SnippetTests/input/set.pkl
vendored
Normal file
3
pkl-server/src/test/files/SnippetTests/input/set.pkl
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
res1: Set<Int> = Set(1, 3, 5, 7)
|
||||
res2: Set<Int> = Set()
|
||||
res3: Set<Any> = Set(1, true, "", null)
|
||||
32
pkl-server/src/test/files/SnippetTests/output/basic.yaml
vendored
Normal file
32
pkl-server/src/test/files/SnippetTests/output/basic.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
- 1
|
||||
- basic
|
||||
- file:///$snippetsDir/input/basic.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
- bar
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
- ''
|
||||
-
|
||||
- 16
|
||||
- res3
|
||||
- 1
|
||||
-
|
||||
- 16
|
||||
- res4
|
||||
- 2.3
|
||||
-
|
||||
- 16
|
||||
- res5
|
||||
- true
|
||||
-
|
||||
- 16
|
||||
- res6
|
||||
- false
|
||||
-
|
||||
- 16
|
||||
- res7
|
||||
- null
|
||||
54
pkl-server/src/test/files/SnippetTests/output/classes.yaml
vendored
Normal file
54
pkl-server/src/test/files/SnippetTests/output/classes.yaml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
- 1
|
||||
- com.foo.bar.MyModule
|
||||
- file:///$snippetsDir/input/classes.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- barnOwl
|
||||
-
|
||||
- 1
|
||||
- com.foo.bar.MyModule#Person
|
||||
- file:///$snippetsDir/input/classes.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- firstName
|
||||
- Barn Owl
|
||||
-
|
||||
- 16
|
||||
- lastName
|
||||
- Bird
|
||||
-
|
||||
- 16
|
||||
- age
|
||||
- 38
|
||||
-
|
||||
- 16
|
||||
- pigeon
|
||||
-
|
||||
- 1
|
||||
- com.foo.bar.MyModule#Person
|
||||
- file:///$snippetsDir/input/classes.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- firstName
|
||||
- Pigeon
|
||||
-
|
||||
- 16
|
||||
- lastName
|
||||
- Bird
|
||||
-
|
||||
- 16
|
||||
- age
|
||||
- 41
|
||||
-
|
||||
- 16
|
||||
- personClass
|
||||
-
|
||||
- 12
|
||||
-
|
||||
- 16
|
||||
- personTypeAlias
|
||||
-
|
||||
- 13
|
||||
81
pkl-server/src/test/files/SnippetTests/output/datasize.yaml
vendored
Normal file
81
pkl-server/src/test/files/SnippetTests/output/datasize.yaml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
- 1
|
||||
- datasize
|
||||
- file:///$snippetsDir/input/datasize.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 8
|
||||
- 1.0
|
||||
- b
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 8
|
||||
- 2.0
|
||||
- kb
|
||||
-
|
||||
- 16
|
||||
- res3
|
||||
-
|
||||
- 8
|
||||
- 3.0
|
||||
- kib
|
||||
-
|
||||
- 16
|
||||
- res4
|
||||
-
|
||||
- 8
|
||||
- 4.0
|
||||
- mb
|
||||
-
|
||||
- 16
|
||||
- res5
|
||||
-
|
||||
- 8
|
||||
- 5.0
|
||||
- mib
|
||||
-
|
||||
- 16
|
||||
- res6
|
||||
-
|
||||
- 8
|
||||
- 6.0
|
||||
- gb
|
||||
-
|
||||
- 16
|
||||
- res7
|
||||
-
|
||||
- 8
|
||||
- 7.0
|
||||
- gib
|
||||
-
|
||||
- 16
|
||||
- res8
|
||||
-
|
||||
- 8
|
||||
- 8.0
|
||||
- tb
|
||||
-
|
||||
- 16
|
||||
- res9
|
||||
-
|
||||
- 8
|
||||
- 9.0
|
||||
- tib
|
||||
-
|
||||
- 16
|
||||
- res10
|
||||
-
|
||||
- 8
|
||||
- 10.0
|
||||
- pb
|
||||
-
|
||||
- 16
|
||||
- res11
|
||||
-
|
||||
- 8
|
||||
- 11.0
|
||||
- pib
|
||||
53
pkl-server/src/test/files/SnippetTests/output/duration.yaml
vendored
Normal file
53
pkl-server/src/test/files/SnippetTests/output/duration.yaml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
- 1
|
||||
- duration
|
||||
- file:///$snippetsDir/input/duration.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 7
|
||||
- 1.0
|
||||
- ns
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 7
|
||||
- 2.0
|
||||
- us
|
||||
-
|
||||
- 16
|
||||
- res3
|
||||
-
|
||||
- 7
|
||||
- 3.0
|
||||
- ms
|
||||
-
|
||||
- 16
|
||||
- res4
|
||||
-
|
||||
- 7
|
||||
- 4.0
|
||||
- s
|
||||
-
|
||||
- 16
|
||||
- res5
|
||||
-
|
||||
- 7
|
||||
- 5.0
|
||||
- min
|
||||
-
|
||||
- 16
|
||||
- res6
|
||||
-
|
||||
- 7
|
||||
- 6.0
|
||||
- h
|
||||
-
|
||||
- 16
|
||||
- res7
|
||||
-
|
||||
- 7
|
||||
- 7.0
|
||||
- d
|
||||
20
pkl-server/src/test/files/SnippetTests/output/intseq.yaml
vendored
Normal file
20
pkl-server/src/test/files/SnippetTests/output/intseq.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
- 1
|
||||
- intseq
|
||||
- file:///$snippetsDir/input/intseq.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 10
|
||||
- 1
|
||||
- 3
|
||||
- 1
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 10
|
||||
- 1
|
||||
- 4
|
||||
- 5
|
||||
58
pkl-server/src/test/files/SnippetTests/output/list.yaml
vendored
Normal file
58
pkl-server/src/test/files/SnippetTests/output/list.yaml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
- 1
|
||||
- list
|
||||
- file:///$snippetsDir/input/list.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 4
|
||||
-
|
||||
- 1
|
||||
- 3
|
||||
- 5
|
||||
- 7
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 5
|
||||
-
|
||||
- 2
|
||||
- 4
|
||||
- 6
|
||||
- 8
|
||||
-
|
||||
- 16
|
||||
- res3
|
||||
-
|
||||
- 4
|
||||
- []
|
||||
-
|
||||
- 16
|
||||
- res4
|
||||
-
|
||||
- 5
|
||||
- []
|
||||
-
|
||||
- 16
|
||||
- res5
|
||||
-
|
||||
- 4
|
||||
-
|
||||
-
|
||||
- 4
|
||||
-
|
||||
- 1
|
||||
- 2
|
||||
-
|
||||
- 16
|
||||
- res6
|
||||
-
|
||||
- 5
|
||||
-
|
||||
-
|
||||
- 5
|
||||
-
|
||||
- 1
|
||||
- 2
|
||||
44
pkl-server/src/test/files/SnippetTests/output/map.yaml
vendored
Normal file
44
pkl-server/src/test/files/SnippetTests/output/map.yaml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
- 1
|
||||
- map
|
||||
- file:///$snippetsDir/input/map.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 2
|
||||
-
|
||||
foo: 1
|
||||
bar: 2
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 3
|
||||
-
|
||||
foo: 1
|
||||
bar: 2
|
||||
-
|
||||
- 16
|
||||
- res3
|
||||
-
|
||||
- 3
|
||||
-
|
||||
childMap:
|
||||
- 3
|
||||
-
|
||||
childFoo: 3
|
||||
-
|
||||
- 16
|
||||
- res4
|
||||
-
|
||||
- 3
|
||||
-
|
||||
?
|
||||
- 2
|
||||
-
|
||||
foo: 1
|
||||
:
|
||||
- 3
|
||||
-
|
||||
bar: 2
|
||||
18
pkl-server/src/test/files/SnippetTests/output/pair.yaml
vendored
Normal file
18
pkl-server/src/test/files/SnippetTests/output/pair.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
- 1
|
||||
- pair
|
||||
- file:///$snippetsDir/input/pair.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 9
|
||||
- 1
|
||||
- 2
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 9
|
||||
- foo
|
||||
- bar
|
||||
22
pkl-server/src/test/files/SnippetTests/output/regex.yaml
vendored
Normal file
22
pkl-server/src/test/files/SnippetTests/output/regex.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
- 1
|
||||
- regex
|
||||
- file:///$snippetsDir/input/regex.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 11
|
||||
- abc
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 11
|
||||
- ''
|
||||
-
|
||||
- 16
|
||||
- res3
|
||||
-
|
||||
- 11
|
||||
- (?m)^abc$
|
||||
30
pkl-server/src/test/files/SnippetTests/output/set.yaml
vendored
Normal file
30
pkl-server/src/test/files/SnippetTests/output/set.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
- 1
|
||||
- set
|
||||
- file:///$snippetsDir/input/set.pkl
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- res1
|
||||
-
|
||||
- 6
|
||||
-
|
||||
- 1
|
||||
- 3
|
||||
- 5
|
||||
- 7
|
||||
-
|
||||
- 16
|
||||
- res2
|
||||
-
|
||||
- 6
|
||||
- []
|
||||
-
|
||||
- 16
|
||||
- res3
|
||||
-
|
||||
- 6
|
||||
-
|
||||
- 1
|
||||
- true
|
||||
- ''
|
||||
- null
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.nio.file.Path
|
||||
import kotlin.reflect.KClass
|
||||
import org.junit.platform.commons.annotation.Testable
|
||||
import org.pkl.commons.test.InputOutputTestEngine
|
||||
import org.pkl.core.Loggers
|
||||
import org.pkl.core.ModuleSource
|
||||
import org.pkl.core.SecurityManagers
|
||||
import org.pkl.core.StackFrameTransformers
|
||||
import org.pkl.core.module.ModuleKeyFactories
|
||||
|
||||
@Testable class BinaryEvaluatorSnippetTests
|
||||
|
||||
class BinaryEvaluatorSnippetTestEngine : InputOutputTestEngine() {
|
||||
override val testClass: KClass<*> = BinaryEvaluatorSnippetTests::class
|
||||
|
||||
private val snippetsDir = rootProjectDir.resolve("pkl-server/src/test/files/SnippetTests")
|
||||
|
||||
private val outputDir = snippetsDir.resolve("output")
|
||||
|
||||
override val inputDir: Path = snippetsDir.resolve("input")
|
||||
|
||||
override val isInputFile: (Path) -> Boolean = { true }
|
||||
|
||||
override fun expectedOutputFileFor(inputFile: Path): Path {
|
||||
val relativePath = inputDir.relativize(inputFile).toString()
|
||||
return outputDir.resolve(relativePath.dropLast(3) + "yaml")
|
||||
}
|
||||
|
||||
private val evaluator =
|
||||
BinaryEvaluator(
|
||||
StackFrameTransformers.empty,
|
||||
SecurityManagers.defaultManager,
|
||||
Loggers.stdErr(),
|
||||
listOf(ModuleKeyFactories.file),
|
||||
listOf(),
|
||||
mapOf(),
|
||||
mapOf(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
private fun String.stripFilePaths() = replace(snippetsDir.toString(), "/\$snippetsDir")
|
||||
|
||||
override fun generateOutputFor(inputFile: Path): Pair<Boolean, String> {
|
||||
val bytes = evaluator.evaluate(ModuleSource.path(inputFile), null)
|
||||
return true to bytes.debugRendering.stripFilePaths()
|
||||
}
|
||||
}
|
||||
150
pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorTest.kt
Normal file
150
pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorTest.kt
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.pkl.core.*
|
||||
import org.pkl.core.module.ModuleKeyFactories
|
||||
import org.pkl.core.resource.ResourceReaders
|
||||
|
||||
class BinaryEvaluatorTest {
|
||||
private val evaluator =
|
||||
BinaryEvaluator(
|
||||
StackFrameTransformers.defaultTransformer,
|
||||
SecurityManagers.standard(
|
||||
listOf(Pattern.compile(".*")),
|
||||
listOf(Pattern.compile(".*")),
|
||||
SecurityManagers.defaultTrustLevels,
|
||||
Path.of("")
|
||||
),
|
||||
Loggers.noop(),
|
||||
listOf(ModuleKeyFactories.standardLibrary),
|
||||
listOf(ResourceReaders.environmentVariable(), ResourceReaders.externalProperty()),
|
||||
mapOf(),
|
||||
mapOf(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
private fun evaluate(text: String, expression: String?) =
|
||||
evaluator.evaluate(ModuleSource.text(text), expression)
|
||||
|
||||
@Test
|
||||
fun `evaluate whole module`() {
|
||||
val bytes = evaluate("foo = 1", null)
|
||||
assertThat(bytes.debugRendering)
|
||||
.isEqualTo(
|
||||
"""
|
||||
- 1
|
||||
- text
|
||||
- repl:text
|
||||
-
|
||||
-
|
||||
- 16
|
||||
- foo
|
||||
- 1
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate subpath`() {
|
||||
val bytes =
|
||||
evaluate(
|
||||
"""
|
||||
foo {
|
||||
bar = 2
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
"foo.bar"
|
||||
)
|
||||
|
||||
assertThat(bytes.asInt()).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate output text`() {
|
||||
val bytes =
|
||||
evaluate(
|
||||
"""
|
||||
foo {
|
||||
bar = 2
|
||||
}
|
||||
|
||||
output {
|
||||
renderer = new YamlRenderer {}
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
"output.text"
|
||||
)
|
||||
|
||||
assertThat(bytes.asString())
|
||||
.isEqualTo(
|
||||
"""
|
||||
foo:
|
||||
bar: 2
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate let expression`() {
|
||||
val bytes = evaluate("foo = 1", "let (bar = 2) foo + bar")
|
||||
|
||||
assertThat(bytes.asInt()).isEqualTo(3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate import expression`() {
|
||||
val bytes = evaluate("", """import("pkl:release").current.documentation.homepage""")
|
||||
|
||||
assertThat(bytes.asString()).startsWith("https://pkl-lang.org/")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate expression with invalid syntax`() {
|
||||
val error = assertThrows<PklException> { evaluate("foo = 1", "<>!!!") }
|
||||
|
||||
assertThat(error).hasMessageContaining("Mismatched input")
|
||||
assertThat(error).hasMessageContaining("<>!!!")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate non-expression`() {
|
||||
val error = assertThrows<PklException> { evaluate("bar = 2", "bar = 15") }
|
||||
|
||||
assertThat(error).hasMessageContaining("Mismatched input")
|
||||
assertThat(error).hasMessageContaining("bar = 15")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate semantically invalid expression`() {
|
||||
val error = assertThrows<PklException> { evaluate("foo = 1", "foo as String") }
|
||||
|
||||
assertThat(error).hasMessageContaining("Expected value of type `String`, but got type `Int`")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.util.regex.Pattern
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.msgpack.core.MessagePack
|
||||
import org.pkl.core.module.PathElement
|
||||
import org.pkl.core.packages.Checksums
|
||||
|
||||
class MessagePackCodecTest {
|
||||
private val encoder: MessageEncoder
|
||||
private val decoder: MessageDecoder
|
||||
|
||||
init {
|
||||
val inputStream = PipedInputStream()
|
||||
val outputStream = PipedOutputStream(inputStream)
|
||||
encoder = MessagePackEncoder(MessagePack.newDefaultPacker(outputStream))
|
||||
decoder = MessagePackDecoder(MessagePack.newDefaultUnpacker(inputStream))
|
||||
}
|
||||
|
||||
private fun roundtrip(message: Message) {
|
||||
encoder.encode(message)
|
||||
val decoded = decoder.decode()
|
||||
assertThat(decoded).isEqualTo(message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip CreateEvaluatorRequest`() {
|
||||
val resourceReader1 =
|
||||
ResourceReaderSpec(
|
||||
scheme = "resourceReader1",
|
||||
hasHierarchicalUris = true,
|
||||
isGlobbable = true,
|
||||
)
|
||||
val resourceReader2 =
|
||||
ResourceReaderSpec(
|
||||
scheme = "resourceReader2",
|
||||
hasHierarchicalUris = true,
|
||||
isGlobbable = false,
|
||||
)
|
||||
val moduleReader1 =
|
||||
ModuleReaderSpec(
|
||||
scheme = "moduleReader1",
|
||||
hasHierarchicalUris = true,
|
||||
isGlobbable = true,
|
||||
isLocal = true
|
||||
)
|
||||
val moduleReader2 =
|
||||
ModuleReaderSpec(
|
||||
scheme = "moduleReader2",
|
||||
hasHierarchicalUris = true,
|
||||
isGlobbable = false,
|
||||
isLocal = false
|
||||
)
|
||||
roundtrip(
|
||||
CreateEvaluatorRequest(
|
||||
requestId = 123,
|
||||
allowedModules = listOf("pkl", "file", "https").map(Pattern::compile),
|
||||
allowedResources =
|
||||
listOf("pkl", "file", "https", "resourceReader1", "resourceReader2")
|
||||
.map(Pattern::compile),
|
||||
clientResourceReaders = listOf(resourceReader1, resourceReader2),
|
||||
clientModuleReaders = listOf(moduleReader1, moduleReader2),
|
||||
modulePaths = listOf(Path.of("some/path.zip"), Path.of("other/path.zip")),
|
||||
env = mapOf("KEY1" to "VALUE1", "KEY2" to "VALUE2"),
|
||||
properties = mapOf("property1" to "value1", "property2" to "value2"),
|
||||
timeout = Duration.ofSeconds(10),
|
||||
rootDir = Path.of("root/dir"),
|
||||
cacheDir = Path.of("cache/dir"),
|
||||
outputFormat = "pcf",
|
||||
project =
|
||||
Project(
|
||||
projectFileUri = URI("file:///dummy/PklProject"),
|
||||
packageUri = null,
|
||||
dependencies =
|
||||
mapOf(
|
||||
"foo" to
|
||||
Project(
|
||||
projectFileUri = URI("file:///foo"),
|
||||
packageUri = URI("package://localhost:12110/foo@1.0.0"),
|
||||
dependencies =
|
||||
mapOf(
|
||||
"bar" to
|
||||
Project(
|
||||
projectFileUri = URI("file:///bar"),
|
||||
packageUri = URI("package://localhost:12110/bar@1.1.0"),
|
||||
dependencies = emptyMap()
|
||||
)
|
||||
)
|
||||
),
|
||||
"baz" to
|
||||
RemoteDependency(URI("package://localhost:12110/baz@1.1.0"), Checksums("abc123"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip CreateEvaluatorResponse`() {
|
||||
roundtrip(CreateEvaluatorResponse(requestId = 123, evaluatorId = 456, error = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip CloseEvaluator`() {
|
||||
roundtrip(CloseEvaluator(evaluatorId = 123))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip EvaluateRequest`() {
|
||||
roundtrip(
|
||||
EvaluateRequest(
|
||||
requestId = 123,
|
||||
evaluatorId = 456,
|
||||
moduleUri = URI("some/module.pkl"),
|
||||
moduleText = null,
|
||||
expr = "some + expression"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip EvaluateResponse`() {
|
||||
roundtrip(
|
||||
EvaluateResponse(
|
||||
requestId = 123,
|
||||
evaluatorId = 456,
|
||||
result = byteArrayOf(1, 2, 3, 4, 5),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip LogMessage`() {
|
||||
roundtrip(
|
||||
LogMessage(
|
||||
evaluatorId = 123,
|
||||
level = 0,
|
||||
message = "Hello, world!",
|
||||
frameUri = "file:///some/module.pkl"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ReadResourceRequest`() {
|
||||
roundtrip(
|
||||
ReadResourceRequest(requestId = 123, evaluatorId = 456, uri = URI("some/resource.json"))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ReadResourceResponse`() {
|
||||
roundtrip(
|
||||
ReadResourceResponse(
|
||||
requestId = 123,
|
||||
evaluatorId = 456,
|
||||
contents = byteArrayOf(1, 2, 3, 4, 5),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ReadModuleRequest`() {
|
||||
roundtrip(ReadModuleRequest(requestId = 123, evaluatorId = 456, uri = URI("some/module.pkl")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ReadModuleResponse`() {
|
||||
roundtrip(
|
||||
ReadModuleResponse(requestId = 123, evaluatorId = 456, contents = "x = 42", error = null)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ListModulesRequest`() {
|
||||
roundtrip(ListModulesRequest(requestId = 135, evaluatorId = 246, uri = URI("foo:/bar/baz/biz")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ListModulesResponse`() {
|
||||
roundtrip(
|
||||
ListModulesResponse(
|
||||
requestId = 123,
|
||||
evaluatorId = 234,
|
||||
pathElements = listOf(PathElement("foo", true), PathElement("bar", false)),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
roundtrip(
|
||||
ListModulesResponse(
|
||||
requestId = 123,
|
||||
evaluatorId = 234,
|
||||
pathElements = null,
|
||||
error = "Something dun went wrong"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ListResourcesRequest`() {
|
||||
roundtrip(ListResourcesRequest(requestId = 987, evaluatorId = 1359, uri = URI("bar:/bazzy")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trip ListResourcesResponse`() {
|
||||
roundtrip(
|
||||
ListResourcesResponse(
|
||||
requestId = 3851,
|
||||
evaluatorId = 3019,
|
||||
pathElements = listOf(PathElement("foo", true), PathElement("bar", false)),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
roundtrip(
|
||||
ListResourcesResponse(
|
||||
requestId = 3851,
|
||||
evaluatorId = 3019,
|
||||
pathElements = null,
|
||||
error = "something went wrong"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decode request with missing request ID`() {
|
||||
val bytes =
|
||||
MessagePack.newDefaultBufferPacker()
|
||||
.apply {
|
||||
packArrayHeader(2)
|
||||
packInt(MessageType.CREATE_EVALUATOR_REQUEST.code)
|
||||
packMapHeader(1)
|
||||
packString("clientResourceSchemes")
|
||||
packArrayHeader(0)
|
||||
}
|
||||
.toByteArray()
|
||||
|
||||
val decoder = MessagePackDecoder(MessagePack.newDefaultUnpacker(bytes))
|
||||
val exception = assertThrows<DecodeException> { decoder.decode() }
|
||||
assertThat(exception.message).contains("requestId")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decode invalid message header`() {
|
||||
val bytes = MessagePack.newDefaultBufferPacker().apply { packInt(2) }.toByteArray()
|
||||
|
||||
val decoder = MessagePackDecoder(MessagePack.newDefaultUnpacker(bytes))
|
||||
val exception = assertThrows<DecodeException> { decoder.decode() }
|
||||
assertThat(exception).hasMessage("Malformed message header.")
|
||||
assertThat(exception).hasRootCauseMessage("Expected Array, but got Integer (02)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.lang.IllegalStateException
|
||||
import org.msgpack.core.MessagePack
|
||||
import org.msgpack.core.MessageUnpacker
|
||||
import org.msgpack.value.ValueType
|
||||
import org.pkl.core.util.yaml.YamlEmitter
|
||||
|
||||
/** Renders MessagePack structures in YAML. */
|
||||
class MessagePackDebugRenderer(bytes: ByteArray) {
|
||||
private val unpacker: MessageUnpacker = MessagePack.newDefaultUnpacker(bytes)
|
||||
private val currIndent = StringBuilder("")
|
||||
private val sb = StringBuilder()
|
||||
private val indent = " "
|
||||
private val yamlEmitter = YamlEmitter.create(sb, "1.2", indent)
|
||||
|
||||
private fun incIndent() {
|
||||
currIndent.append(indent)
|
||||
}
|
||||
|
||||
private fun decIndent() {
|
||||
currIndent.setLength(currIndent.length - indent.length)
|
||||
}
|
||||
|
||||
private fun newline() {
|
||||
sb.append("\n")
|
||||
sb.append(currIndent)
|
||||
}
|
||||
|
||||
private fun renderKey() {
|
||||
val mf = unpacker.nextFormat
|
||||
when (mf.valueType!!) {
|
||||
ValueType.STRING -> yamlEmitter.emit(unpacker.unpackString(), currIndent, true)
|
||||
ValueType.MAP,
|
||||
ValueType.ARRAY -> {
|
||||
sb.append("? ")
|
||||
incIndent()
|
||||
renderValue()
|
||||
decIndent()
|
||||
newline()
|
||||
}
|
||||
else -> renderValue()
|
||||
}
|
||||
sb.append(": ")
|
||||
}
|
||||
|
||||
private fun renderValue() {
|
||||
val mf = unpacker.nextFormat
|
||||
when (mf.valueType!!) {
|
||||
ValueType.INTEGER,
|
||||
ValueType.FLOAT,
|
||||
ValueType.BOOLEAN,
|
||||
ValueType.NIL -> sb.append(unpacker.unpackValue().toJson())
|
||||
ValueType.STRING -> yamlEmitter.emit(unpacker.unpackString(), currIndent, false)
|
||||
ValueType.ARRAY -> {
|
||||
val size = unpacker.unpackArrayHeader()
|
||||
if (size == 0) {
|
||||
sb.append("[]")
|
||||
return
|
||||
}
|
||||
for (i in 0 until size) {
|
||||
newline()
|
||||
sb.append("- ")
|
||||
incIndent()
|
||||
renderValue()
|
||||
decIndent()
|
||||
}
|
||||
}
|
||||
ValueType.MAP -> {
|
||||
val size = unpacker.unpackMapHeader()
|
||||
if (size == 0) {
|
||||
sb.append("{}")
|
||||
return
|
||||
}
|
||||
for (i in 0 until size) {
|
||||
newline()
|
||||
renderKey()
|
||||
incIndent()
|
||||
renderValue()
|
||||
decIndent()
|
||||
}
|
||||
}
|
||||
ValueType.BINARY,
|
||||
ValueType.EXTENSION -> throw IllegalStateException("Unexpected value type ${mf.valueType}")
|
||||
}
|
||||
}
|
||||
|
||||
val output by lazy {
|
||||
renderValue()
|
||||
sb.toString().removePrefix("\n")
|
||||
}
|
||||
}
|
||||
|
||||
val ByteArray.debugRendering
|
||||
get() = MessagePackDebugRenderer(this).output
|
||||
994
pkl-server/src/test/kotlin/org/pkl/server/ServerTest.kt
Normal file
994
pkl-server/src/test/kotlin/org/pkl/server/ServerTest.kt
Normal file
@@ -0,0 +1,994 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.outputStream
|
||||
import kotlin.io.path.writeText
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.msgpack.core.MessagePack
|
||||
import org.pkl.commons.test.PackageServer
|
||||
import org.pkl.core.module.PathElement
|
||||
|
||||
class ServerTest {
|
||||
companion object {
|
||||
private const val useDirectTransport = false
|
||||
|
||||
private val executor: ExecutorService =
|
||||
if (useDirectTransport) {
|
||||
createDirectExecutor()
|
||||
} else {
|
||||
Executors.newCachedThreadPool()
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@JvmStatic
|
||||
@Suppress("unused")
|
||||
fun afterAll() {
|
||||
executor.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
private val transports: Pair<MessageTransport, MessageTransport> = run {
|
||||
if (useDirectTransport) {
|
||||
MessageTransports.direct()
|
||||
} else {
|
||||
val in1 = PipedInputStream()
|
||||
val out1 = PipedOutputStream(in1)
|
||||
val in2 = PipedInputStream()
|
||||
val out2 = PipedOutputStream(in2)
|
||||
MessageTransports.stream(in1, out2) to MessageTransports.stream(in2, out1)
|
||||
}
|
||||
}
|
||||
|
||||
private val client: TestTransport = TestTransport(transports.first)
|
||||
private val server: Server = Server(transports.second)
|
||||
|
||||
@BeforeEach
|
||||
fun before() {
|
||||
executor.execute { server.start() }
|
||||
executor.execute { client.start() }
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun after() {
|
||||
client.close()
|
||||
server.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create and close evaluator`() {
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(requestId = 123)
|
||||
client.send(CloseEvaluator(evaluatorId = evaluatorId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate module`() {
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest()
|
||||
val requestId = 234L
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText =
|
||||
"""
|
||||
foo {
|
||||
bar = "bar"
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
expr = null
|
||||
)
|
||||
)
|
||||
|
||||
val response = client.receive<EvaluateResponse>()
|
||||
assertThat(response.error).isNull()
|
||||
assertThat(response.result).isNotNull
|
||||
assertThat(response.requestId).isEqualTo(requestId)
|
||||
|
||||
val unpacker = MessagePack.newDefaultUnpacker(response.result)
|
||||
val value = unpacker.unpackValue()
|
||||
assertThat(value.isArrayValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `trace logs`() {
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest()
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText =
|
||||
"""
|
||||
foo = trace(1 + 2 + 3)
|
||||
"""
|
||||
.trimIndent(),
|
||||
expr = null
|
||||
)
|
||||
)
|
||||
|
||||
val response = client.receive<LogMessage>()
|
||||
assertThat(response.level).isEqualTo(0)
|
||||
assertThat(response.message).isEqualTo("1 + 2 + 3 = 6")
|
||||
|
||||
client.receive<EvaluateResponse>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `warn logs`() {
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest()
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText =
|
||||
"""
|
||||
@Deprecated { message = "use bar instead" }
|
||||
function foo() = 5
|
||||
|
||||
result = foo()
|
||||
"""
|
||||
.trimIndent(),
|
||||
expr = null
|
||||
)
|
||||
)
|
||||
|
||||
val response = client.receive<LogMessage>()
|
||||
assertThat(response.level).isEqualTo(1)
|
||||
assertThat(response.message).contains("use bar instead")
|
||||
|
||||
client.receive<EvaluateResponse>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read resource`() {
|
||||
val reader =
|
||||
ResourceReaderSpec(scheme = "bahumbug", hasHierarchicalUris = true, isGlobbable = false)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(resourceReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText = """res = read("bahumbug:/foo.pkl").text""",
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
|
||||
val readResourceMsg = client.receive<ReadResourceRequest>()
|
||||
assertThat(readResourceMsg.uri.toString()).isEqualTo("bahumbug:/foo.pkl")
|
||||
assertThat(readResourceMsg.evaluatorId).isEqualTo(evaluatorId)
|
||||
|
||||
client.send(
|
||||
ReadResourceResponse(
|
||||
requestId = readResourceMsg.requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
contents = "my bahumbug".toByteArray(),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.error).isNull()
|
||||
|
||||
val unpacker = MessagePack.newDefaultUnpacker(evaluateResponse.result)
|
||||
val value = unpacker.unpackValue()
|
||||
assertThat(value.asStringValue().asString()).isEqualTo("my bahumbug")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read resource error`() {
|
||||
val reader =
|
||||
ResourceReaderSpec(scheme = "bahumbug", hasHierarchicalUris = true, isGlobbable = false)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(resourceReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText = """res = read("bahumbug:/foo.txt").text""",
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
|
||||
val readResourceMsg = client.receive<ReadResourceRequest>()
|
||||
|
||||
client.send(
|
||||
ReadResourceResponse(
|
||||
requestId = readResourceMsg.requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
contents = null,
|
||||
error = "cannot read my bahumbug"
|
||||
)
|
||||
)
|
||||
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.error).contains("bahumbug:/foo.txt")
|
||||
assertThat(evaluateResponse.error).doesNotContain("org.pkl.core.PklBugException")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `glob resource`() {
|
||||
val reader = ResourceReaderSpec(scheme = "bird", hasHierarchicalUris = true, isGlobbable = true)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(resourceReaders = listOf(reader))
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText =
|
||||
"""
|
||||
res = read*("bird:/**.txt").keys
|
||||
"""
|
||||
.trimIndent(),
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
val listResourcesRequest = client.receive<ListResourcesRequest>()
|
||||
assertThat(listResourcesRequest.uri.toString()).isEqualTo("bird:/")
|
||||
client.send(
|
||||
ListResourcesResponse(
|
||||
requestId = listResourcesRequest.requestId,
|
||||
evaluatorId = listResourcesRequest.evaluatorId,
|
||||
pathElements = listOf(PathElement("foo.txt", false), PathElement("subdir", true)),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
val listResourcesRequest2 = client.receive<ListResourcesRequest>()
|
||||
assertThat(listResourcesRequest2.uri.toString()).isEqualTo("bird:/subdir/")
|
||||
client.send(
|
||||
ListResourcesResponse(
|
||||
requestId = listResourcesRequest2.requestId,
|
||||
evaluatorId = listResourcesRequest2.evaluatorId,
|
||||
pathElements =
|
||||
listOf(
|
||||
PathElement("bar.txt", false),
|
||||
),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.result!!.debugYaml)
|
||||
.isEqualTo(
|
||||
"""
|
||||
- 6
|
||||
-
|
||||
- bird:/foo.txt
|
||||
- bird:/subdir/bar.txt
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `glob resource error`() {
|
||||
val reader = ResourceReaderSpec(scheme = "bird", hasHierarchicalUris = true, isGlobbable = true)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(resourceReaders = listOf(reader))
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText =
|
||||
"""
|
||||
res = read*("bird:/**.txt").keys
|
||||
"""
|
||||
.trimIndent(),
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
val listResourcesRequest = client.receive<ListResourcesRequest>()
|
||||
assertThat(listResourcesRequest.uri.toString()).isEqualTo("bird:/")
|
||||
client.send(
|
||||
ListResourcesResponse(
|
||||
requestId = listResourcesRequest.requestId,
|
||||
evaluatorId = listResourcesRequest.evaluatorId,
|
||||
pathElements = null,
|
||||
error = "didnt work"
|
||||
)
|
||||
)
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.error)
|
||||
.isEqualTo(
|
||||
"""
|
||||
–– Pkl Error ––
|
||||
I/O error resolving glob pattern `bird:/**.txt`.
|
||||
IOException: didnt work
|
||||
|
||||
1 | res = read*("bird:/**.txt").keys
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
at text#res (repl:text)
|
||||
|
||||
1 | res
|
||||
^^^
|
||||
at (repl:text)
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read module`() {
|
||||
val reader =
|
||||
ModuleReaderSpec(
|
||||
scheme = "bird",
|
||||
hasHierarchicalUris = true,
|
||||
isLocal = true,
|
||||
isGlobbable = false
|
||||
)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText = """res = import("bird:/pigeon.pkl").value""",
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
|
||||
val readModuleMsg = client.receive<ReadModuleRequest>()
|
||||
assertThat(readModuleMsg.uri.toString()).isEqualTo("bird:/pigeon.pkl")
|
||||
assertThat(readModuleMsg.evaluatorId).isEqualTo(evaluatorId)
|
||||
|
||||
client.send(
|
||||
ReadModuleResponse(
|
||||
requestId = readModuleMsg.requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
contents = "value = 5",
|
||||
error = null
|
||||
)
|
||||
)
|
||||
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.error).isNull()
|
||||
val unpacker = MessagePack.newDefaultUnpacker(evaluateResponse.result)
|
||||
val value = unpacker.unpackValue()
|
||||
assertThat(value.asIntegerValue().asInt()).isEqualTo(5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read module error`() {
|
||||
val reader =
|
||||
ModuleReaderSpec(
|
||||
scheme = "bird",
|
||||
hasHierarchicalUris = true,
|
||||
isLocal = true,
|
||||
isGlobbable = false
|
||||
)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText = """res = import("bird:/pigeon.pkl").value""",
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
|
||||
val readModuleMsg = client.receive<ReadModuleRequest>()
|
||||
assertThat(readModuleMsg.uri.toString()).isEqualTo("bird:/pigeon.pkl")
|
||||
assertThat(readModuleMsg.evaluatorId).isEqualTo(evaluatorId)
|
||||
|
||||
client.send(
|
||||
ReadModuleResponse(
|
||||
requestId = readModuleMsg.requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
contents = null,
|
||||
error = "Don't know where Pigeon is"
|
||||
)
|
||||
)
|
||||
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.error).contains("Don't know where Pigeon is")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `glob module`() {
|
||||
val reader =
|
||||
ModuleReaderSpec(
|
||||
scheme = "bird",
|
||||
hasHierarchicalUris = true,
|
||||
isLocal = true,
|
||||
isGlobbable = true
|
||||
)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText = """res = import*("bird:/**.pkl").keys""",
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
|
||||
val listModulesMsg = client.receive<ListModulesRequest>()
|
||||
assertThat(listModulesMsg.uri.scheme).isEqualTo("bird")
|
||||
assertThat(listModulesMsg.uri.path).isEqualTo("/")
|
||||
client.send(
|
||||
ListModulesResponse(
|
||||
requestId = listModulesMsg.requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
pathElements =
|
||||
listOf(
|
||||
PathElement("birds", true),
|
||||
PathElement("majesticBirds", true),
|
||||
PathElement("Person.pkl", false)
|
||||
),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
val listModulesMsg2 = client.receive<ListModulesRequest>()
|
||||
assertThat(listModulesMsg2.uri.scheme).isEqualTo("bird")
|
||||
assertThat(listModulesMsg2.uri.path).isEqualTo("/birds/")
|
||||
client.send(
|
||||
ListModulesResponse(
|
||||
requestId = listModulesMsg2.requestId,
|
||||
evaluatorId = listModulesMsg2.evaluatorId,
|
||||
pathElements =
|
||||
listOf(
|
||||
PathElement("pigeon.pkl", false),
|
||||
PathElement("parrot.pkl", false),
|
||||
),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
val listModulesMsg3 = client.receive<ListModulesRequest>()
|
||||
assertThat(listModulesMsg3.uri.scheme).isEqualTo("bird")
|
||||
assertThat(listModulesMsg3.uri.path).isEqualTo("/majesticBirds/")
|
||||
client.send(
|
||||
ListModulesResponse(
|
||||
requestId = listModulesMsg3.requestId,
|
||||
evaluatorId = listModulesMsg3.evaluatorId,
|
||||
pathElements =
|
||||
listOf(
|
||||
PathElement("barnOwl.pkl", false),
|
||||
PathElement("elfOwl.pkl", false),
|
||||
),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.result!!.debugRendering)
|
||||
.isEqualTo(
|
||||
"""
|
||||
- 6
|
||||
-
|
||||
- bird:/Person.pkl
|
||||
- bird:/birds/parrot.pkl
|
||||
- bird:/birds/pigeon.pkl
|
||||
- bird:/majesticBirds/barnOwl.pkl
|
||||
- bird:/majesticBirds/elfOwl.pkl
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `glob module error`() {
|
||||
val reader =
|
||||
ModuleReaderSpec(
|
||||
scheme = "bird",
|
||||
hasHierarchicalUris = true,
|
||||
isLocal = true,
|
||||
isGlobbable = true
|
||||
)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText = """res = import*("bird:/**.pkl").keys""",
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
|
||||
val listModulesMsg = client.receive<ListModulesRequest>()
|
||||
assertThat(listModulesMsg.uri.scheme).isEqualTo("bird")
|
||||
assertThat(listModulesMsg.uri.path).isEqualTo("/")
|
||||
client.send(
|
||||
ListModulesResponse(
|
||||
requestId = listModulesMsg.requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
pathElements = null,
|
||||
error = "nope"
|
||||
)
|
||||
)
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.error)
|
||||
.isEqualTo(
|
||||
"""
|
||||
–– Pkl Error ––
|
||||
I/O error resolving glob pattern `bird:/**.pkl`.
|
||||
IOException: nope
|
||||
|
||||
1 | res = import*("bird:/**.pkl").keys
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at text#res (repl:text)
|
||||
|
||||
1 | res
|
||||
^^^
|
||||
at (repl:text)
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read and evaluate module path from jar`(@TempDir tempDir: Path) {
|
||||
val jarFile = tempDir.resolve("resource1.jar")
|
||||
jarFile.outputStream().use { outStream ->
|
||||
javaClass.getResourceAsStream("resource1.jar")!!.use { inStream ->
|
||||
inStream.copyTo(outStream)
|
||||
}
|
||||
}
|
||||
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(modulePaths = listOf(jarFile))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("modulepath:/dir1/module.pkl"),
|
||||
moduleText = null,
|
||||
expr = "output.text"
|
||||
)
|
||||
)
|
||||
|
||||
val response = client.receive<EvaluateResponse>()
|
||||
assertThat(response.error).isNull()
|
||||
val tripleQuote = "\"\"\""
|
||||
assertThat(response.result!!.debugYaml)
|
||||
.isEqualTo(
|
||||
"""
|
||||
|
|
||||
res1 {
|
||||
uri = "modulepath:/dir1/resource1.txt"
|
||||
text = $tripleQuote
|
||||
content
|
||||
|
||||
$tripleQuote
|
||||
base64 = "Y29udGVudAo="
|
||||
}
|
||||
res2 {
|
||||
uri = "modulepath:/dir1/resource1.txt"
|
||||
text = $tripleQuote
|
||||
content
|
||||
|
||||
$tripleQuote
|
||||
base64 = "Y29udGVudAo="
|
||||
}
|
||||
res3 {
|
||||
ressy = "the module2 output"
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `import triple-dot path`() {
|
||||
val reader =
|
||||
ModuleReaderSpec(
|
||||
scheme = "bird",
|
||||
hasHierarchicalUris = true,
|
||||
isLocal = true,
|
||||
isGlobbable = true
|
||||
)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("bird:/foo/bar/baz.pkl"),
|
||||
moduleText =
|
||||
"""
|
||||
import ".../buz.pkl"
|
||||
|
||||
res = buz.res
|
||||
"""
|
||||
.trimIndent(),
|
||||
expr = "res"
|
||||
)
|
||||
)
|
||||
val readModuleRequest = client.receive<ReadModuleRequest>()
|
||||
assertThat(readModuleRequest.uri).isEqualTo(URI("bird:/foo/buz.pkl"))
|
||||
client.send(
|
||||
ReadModuleResponse(
|
||||
requestId = readModuleRequest.requestId,
|
||||
evaluatorId = readModuleRequest.evaluatorId,
|
||||
contents = null,
|
||||
error = "not here"
|
||||
)
|
||||
)
|
||||
|
||||
val readModuleRequest2 = client.receive<ReadModuleRequest>()
|
||||
assertThat(readModuleRequest2.uri).isEqualTo(URI("bird:/buz.pkl"))
|
||||
client.send(
|
||||
ReadModuleResponse(
|
||||
requestId = readModuleRequest2.requestId,
|
||||
evaluatorId = readModuleRequest2.evaluatorId,
|
||||
contents = "res = 1",
|
||||
error = null
|
||||
)
|
||||
)
|
||||
|
||||
val evaluatorResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluatorResponse.result!!.debugYaml).isEqualTo("1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate error`() {
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest()
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("repl:text"),
|
||||
moduleText = """foo = 1""",
|
||||
expr = "foo as String"
|
||||
)
|
||||
)
|
||||
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.requestId).isEqualTo(1)
|
||||
assertThat(evaluateResponse.error).contains("Expected value of type")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate client-provided module reader`() {
|
||||
val reader =
|
||||
ModuleReaderSpec(
|
||||
scheme = "bird",
|
||||
hasHierarchicalUris = true,
|
||||
isLocal = false,
|
||||
isGlobbable = false
|
||||
)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader))
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("bird:/pigeon.pkl"),
|
||||
moduleText = null,
|
||||
expr = "output.text",
|
||||
)
|
||||
)
|
||||
|
||||
val readModuleRequest = client.receive<ReadModuleRequest>()
|
||||
assertThat(readModuleRequest.uri.toString()).isEqualTo("bird:/pigeon.pkl")
|
||||
|
||||
client.send(
|
||||
ReadModuleResponse(
|
||||
requestId = readModuleRequest.requestId,
|
||||
evaluatorId = evaluatorId,
|
||||
contents =
|
||||
"""
|
||||
firstName = "Pigeon"
|
||||
lastName = "Bird"
|
||||
fullName = firstName + " " + lastName
|
||||
"""
|
||||
.trimIndent(),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
|
||||
val evaluateResponse = client.receive<EvaluateResponse>()
|
||||
assertThat(evaluateResponse.result).isNotNull
|
||||
assertThat(evaluateResponse.result!!.debugYaml)
|
||||
.isEqualTo(
|
||||
"""
|
||||
|
|
||||
firstName = "Pigeon"
|
||||
lastName = "Bird"
|
||||
fullName = "Pigeon Bird"
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `concurrent evaluations`() {
|
||||
val reader =
|
||||
ModuleReaderSpec(
|
||||
scheme = "bird",
|
||||
hasHierarchicalUris = true,
|
||||
isLocal = false,
|
||||
isGlobbable = false
|
||||
)
|
||||
val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader))
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("bird:/pigeon.pkl"),
|
||||
moduleText = null,
|
||||
expr = "output.text",
|
||||
)
|
||||
)
|
||||
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 2,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = URI("bird:/parrot.pkl"),
|
||||
moduleText = null,
|
||||
expr = "output.text"
|
||||
)
|
||||
)
|
||||
|
||||
// evaluation is single-threaded; `parrot.pkl` gets evaluated after `pigeon.pkl` completes.
|
||||
val response11 = client.receive<ReadModuleRequest>()
|
||||
assertThat(response11.uri.toString()).isEqualTo("bird:/pigeon.pkl")
|
||||
|
||||
client.send(
|
||||
ReadModuleResponse(
|
||||
response11.requestId,
|
||||
evaluatorId,
|
||||
contents =
|
||||
"""
|
||||
firstName = "Pigeon"
|
||||
lastName = "Bird"
|
||||
fullName = firstName + " " + lastName
|
||||
"""
|
||||
.trimIndent(),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
|
||||
val response12 = client.receive<EvaluateResponse>()
|
||||
assertThat(response12.result).isNotNull
|
||||
assertThat(response12.result!!.debugYaml)
|
||||
.isEqualTo(
|
||||
"""
|
||||
|
|
||||
firstName = "Pigeon"
|
||||
lastName = "Bird"
|
||||
fullName = "Pigeon Bird"
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
val response21 = client.receive<ReadModuleRequest>()
|
||||
assertThat(response21.uri.toString()).isEqualTo("bird:/parrot.pkl")
|
||||
|
||||
client.send(
|
||||
ReadModuleResponse(
|
||||
response21.requestId,
|
||||
evaluatorId,
|
||||
contents =
|
||||
"""
|
||||
firstName = "Parrot"
|
||||
lastName = "Bird"
|
||||
fullName = firstName + " " + lastName
|
||||
"""
|
||||
.trimIndent(),
|
||||
error = null
|
||||
)
|
||||
)
|
||||
|
||||
val response22 = client.receive<EvaluateResponse>()
|
||||
assertThat(response22.result).isNotNull
|
||||
assertThat(response22.result!!.debugYaml)
|
||||
.isEqualTo(
|
||||
"""
|
||||
|
|
||||
firstName = "Parrot"
|
||||
lastName = "Bird"
|
||||
fullName = "Parrot Bird"
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `evaluate with project dependencies`(@TempDir tempDir: Path) {
|
||||
val cacheDir = tempDir.resolve("cache").createDirectories()
|
||||
PackageServer.populateCacheDir(cacheDir)
|
||||
val libDir = tempDir.resolve("lib/").createDirectories()
|
||||
libDir
|
||||
.resolve("lib.pkl")
|
||||
.writeText(
|
||||
"""
|
||||
text = "This is from lib"
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
libDir
|
||||
.resolve("PklProject")
|
||||
.writeText(
|
||||
"""
|
||||
amends "pkl:Project"
|
||||
|
||||
package {
|
||||
name = "lib"
|
||||
baseUri = "package://localhost:12110/lib"
|
||||
version = "5.0.0"
|
||||
packageZipUrl = "https://localhost:12110/lib.zip"
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
val projectDir = tempDir.resolve("proj/").createDirectories()
|
||||
val module = projectDir.resolve("mod.pkl")
|
||||
module.writeText(
|
||||
"""
|
||||
import "@birds/Bird.pkl"
|
||||
import "@lib/lib.pkl"
|
||||
|
||||
res: Bird = new {
|
||||
name = "Birdie"
|
||||
favoriteFruit { name = "dragonfruit" }
|
||||
}
|
||||
|
||||
libContents = lib
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
val dollar = '$'
|
||||
projectDir
|
||||
.resolve("PklProject.deps.json")
|
||||
.writeText(
|
||||
"""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"resolvedDependencies": {
|
||||
"package://localhost:12110/birds@0": {
|
||||
"type": "remote",
|
||||
"uri": "projectpackage://localhost:12110/birds@0.5.0",
|
||||
"checksums": {
|
||||
"sha256": "${dollar}skipChecksumVerification"
|
||||
}
|
||||
},
|
||||
"package://localhost:12110/fruit@1": {
|
||||
"type": "remote",
|
||||
"uri": "projectpackage://localhost:12110/fruit@1.0.5",
|
||||
"checksums": {
|
||||
"sha256": "${dollar}skipChecksumVerification"
|
||||
}
|
||||
},
|
||||
"package://localhost:12110/lib@5": {
|
||||
"type": "local",
|
||||
"uri": "projectpackage://localhost:12110/lib@5.0.0",
|
||||
"path": "../lib"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
val evaluatorId =
|
||||
client.sendCreateEvaluatorRequest(
|
||||
cacheDir = cacheDir,
|
||||
project =
|
||||
Project(
|
||||
projectFileUri = projectDir.resolve("PklProject").toUri(),
|
||||
packageUri = null,
|
||||
dependencies =
|
||||
mapOf(
|
||||
"birds" to
|
||||
RemoteDependency(packageUri = URI("package://localhost:12110/birds@0.5.0"), null),
|
||||
"lib" to
|
||||
Project(
|
||||
projectFileUri = libDir.toUri().resolve("PklProject"),
|
||||
packageUri = URI("package://localhost:12110/lib@5.0.0"),
|
||||
dependencies = emptyMap()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
client.send(
|
||||
EvaluateRequest(
|
||||
requestId = 1,
|
||||
evaluatorId = evaluatorId,
|
||||
moduleUri = module.toUri(),
|
||||
moduleText = null,
|
||||
expr = "output.text",
|
||||
)
|
||||
)
|
||||
val resp2 = client.receive<EvaluateResponse>()
|
||||
assertThat(resp2.error).isNull()
|
||||
assertThat(resp2.result).isNotNull()
|
||||
assertThat(resp2.result!!.debugRendering.trim())
|
||||
.isEqualTo(
|
||||
"""
|
||||
|
|
||||
res {
|
||||
name = "Birdie"
|
||||
favoriteFruit {
|
||||
name = "dragonfruit"
|
||||
}
|
||||
}
|
||||
libContents {
|
||||
text = "This is from lib"
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
private val ByteArray.debugYaml
|
||||
get() = MessagePackDebugRenderer(this).output.trimIndent()
|
||||
|
||||
private fun TestTransport.sendCreateEvaluatorRequest(
|
||||
requestId: Long = 123,
|
||||
resourceReaders: List<ResourceReaderSpec> = listOf(),
|
||||
moduleReaders: List<ModuleReaderSpec> = listOf(),
|
||||
modulePaths: List<Path> = listOf(),
|
||||
project: Project? = null,
|
||||
cacheDir: Path? = null
|
||||
): Long {
|
||||
val message =
|
||||
CreateEvaluatorRequest(
|
||||
requestId = 123,
|
||||
allowedResources = listOf(Pattern.compile(".*")),
|
||||
allowedModules = listOf(Pattern.compile(".*")),
|
||||
clientResourceReaders = resourceReaders,
|
||||
clientModuleReaders = moduleReaders,
|
||||
modulePaths = modulePaths,
|
||||
env = mapOf(),
|
||||
properties = mapOf(),
|
||||
timeout = null,
|
||||
rootDir = null,
|
||||
cacheDir = cacheDir,
|
||||
outputFormat = null,
|
||||
project = project
|
||||
)
|
||||
|
||||
send(message)
|
||||
|
||||
val response = receive<CreateEvaluatorResponse>()
|
||||
assertThat(response.requestId).isEqualTo(requestId)
|
||||
assertThat(response.evaluatorId).isNotNull
|
||||
assertThat(response.error).isNull()
|
||||
|
||||
return response.evaluatorId!!
|
||||
}
|
||||
}
|
||||
50
pkl-server/src/test/kotlin/org/pkl/server/TestTransport.kt
Normal file
50
pkl-server/src/test/kotlin/org/pkl/server/TestTransport.kt
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
|
||||
internal class TestTransport(private val delegate: MessageTransport) : AutoCloseable {
|
||||
private val incomingMessages: BlockingQueue<Message> = ArrayBlockingQueue(10)
|
||||
|
||||
fun start() {
|
||||
delegate.start({ incomingMessages.put(it) }, { incomingMessages.put(it) })
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
delegate.close()
|
||||
}
|
||||
|
||||
fun send(message: ClientOneWayMessage) {
|
||||
delegate.send(message)
|
||||
}
|
||||
|
||||
fun send(message: ClientRequestMessage) {
|
||||
delegate.send(message) { incomingMessages.put(it) }
|
||||
}
|
||||
|
||||
fun send(message: ClientResponseMessage) {
|
||||
delegate.send(message)
|
||||
}
|
||||
|
||||
inline fun <reified T : Message> receive(): T {
|
||||
val message = incomingMessages.take()
|
||||
assertThat(message).isInstanceOf(T::class.java)
|
||||
return message as T
|
||||
}
|
||||
}
|
||||
53
pkl-server/src/test/kotlin/org/pkl/server/TestUtils.kt
Normal file
53
pkl-server/src/test/kotlin/org/pkl/server/TestUtils.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.server
|
||||
|
||||
import java.util.concurrent.AbstractExecutorService
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.msgpack.core.MessagePack
|
||||
import org.msgpack.value.ImmutableValue
|
||||
|
||||
fun ByteArray.unpack(): ImmutableValue = MessagePack.newDefaultUnpacker(this).unpackValue()
|
||||
|
||||
fun ByteArray.asInt(): Int = unpack().asIntegerValue().asInt()
|
||||
|
||||
fun ByteArray.asString(): String = unpack().asStringValue().asString()
|
||||
|
||||
fun createDirectExecutor(): ExecutorService =
|
||||
object : AbstractExecutorService() {
|
||||
override fun execute(command: Runnable) {
|
||||
command.run()
|
||||
}
|
||||
|
||||
override fun shutdown() {}
|
||||
|
||||
override fun shutdownNow(): MutableList<Runnable> {
|
||||
throw UnsupportedOperationException("shutdownNow")
|
||||
}
|
||||
|
||||
override fun isShutdown(): Boolean {
|
||||
throw UnsupportedOperationException("isShutdown")
|
||||
}
|
||||
|
||||
override fun isTerminated(): Boolean {
|
||||
throw UnsupportedOperationException("isTerminated")
|
||||
}
|
||||
|
||||
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
|
||||
throw UnsupportedOperationException("awaitTermination")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.pkl.server.BinaryEvaluatorSnippetTestEngine
|
||||
BIN
pkl-server/src/test/resources/org/pkl/server/resource1.jar
Normal file
BIN
pkl-server/src/test/resources/org/pkl/server/resource1.jar
Normal file
Binary file not shown.
Reference in New Issue
Block a user