diff --git a/bench/gradle.lockfile b/bench/gradle.lockfile index 752327022..5b2324d15 100644 --- a/bench/gradle.lockfile +++ b/bench/gradle.lockfile @@ -32,6 +32,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=jmh,jmhRuntimeClasspath org.openjdk.jmh:jmh-core:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.openjdk.jmh:jmh-generator-asm:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath org.openjdk.jmh:jmh-generator-bytecode:1.37=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath diff --git a/docs/gradle.lockfile b/docs/gradle.lockfile index 0b041bcbe..d466c7d68 100644 --- a/docs/gradle.lockfile +++ b/docs/gradle.lockfile @@ -30,6 +30,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=testRuntimeClasspath diff --git a/pkl-cli/gradle.lockfile b/pkl-cli/gradle.lockfile index e53e57801..6eab853a6 100644 --- a/pkl-cli/gradle.lockfile +++ b/pkl-cli/gradle.lockfile @@ -101,4 +101,4 @@ org.xmlunit:xmlunit-core:2.10.0=testCompileClasspath,testImplementationDependenc org.xmlunit:xmlunit-legacy:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.xmlunit:xmlunit-placeholders:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.yaml:snakeyaml:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -empty=annotationProcessor,archives,compile,intransitiveDependenciesMetadata,javaExecutable,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,runtime,runtimeOnlyDependenciesMetadata,shadow,signatures,sourcesJar,stagedAlpineLinuxAmd64Executable,stagedLinuxAarch64Executable,stagedLinuxAmd64Executable,stagedMacAarch64Executable,stagedMacAmd64Executable,testAnnotationProcessor,testApiDependenciesMetadata,testCompile,testCompileOnly,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntime +empty=annotationProcessor,archives,compile,intransitiveDependenciesMetadata,javaExecutable,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,runtime,runtimeOnlyDependenciesMetadata,shadow,signatures,sourcesJar,stagedAlpineLinuxAmd64Executable,stagedLinuxAarch64Executable,stagedLinuxAmd64Executable,stagedMacAarch64Executable,stagedMacAmd64Executable,stagedWindowsAmd64Executable,testAnnotationProcessor,testApiDependenciesMetadata,testCompile,testCompileOnly,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntime diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt index 3035802bd..7e8fdf16c 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/CliServer.kt @@ -18,14 +18,13 @@ package org.pkl.cli import org.pkl.commons.cli.CliBaseOptions import org.pkl.commons.cli.CliCommand import org.pkl.commons.cli.CliException -import org.pkl.server.MessageTransports -import org.pkl.server.ProtocolException +import org.pkl.core.messaging.ProtocolException import org.pkl.server.Server class CliServer(options: CliBaseOptions) : CliCommand(options) { override fun doRun() = try { - val server = Server(MessageTransports.stream(System.`in`, System.out)) + val server = Server.stream(System.`in`, System.out) server.use { it.start() } } catch (e: ProtocolException) { throw CliException(e.message!!) diff --git a/pkl-codegen-java/gradle.lockfile b/pkl-codegen-java/gradle.lockfile index ac71e00ae..983e5ff9b 100644 --- a/pkl-codegen-java/gradle.lockfile +++ b/pkl-codegen-java/gradle.lockfile @@ -22,10 +22,14 @@ org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=kotlinCompilerPluginClasspat org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.jupiter:junit-jupiter-engine:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata @@ -33,6 +37,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath diff --git a/pkl-codegen-kotlin/gradle.lockfile b/pkl-codegen-kotlin/gradle.lockfile index 4aee4cca2..8534c89d9 100644 --- a/pkl-codegen-kotlin/gradle.lockfile +++ b/pkl-codegen-kotlin/gradle.lockfile @@ -24,10 +24,14 @@ org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=kotlinCompilerPluginClasspat org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testRuntimeClasspath org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata @@ -36,6 +40,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath diff --git a/pkl-commons-cli/gradle.lockfile b/pkl-commons-cli/gradle.lockfile index b59c74b95..2884e0663 100644 --- a/pkl-commons-cli/gradle.lockfile +++ b/pkl-commons-cli/gradle.lockfile @@ -20,10 +20,14 @@ org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=kotlinCompilerPluginClasspat org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.jupiter:junit-jupiter-engine:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata @@ -31,6 +35,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath diff --git a/pkl-config-java/gradle.lockfile b/pkl-config-java/gradle.lockfile index b5b7a592e..f5496bc74 100644 --- a/pkl-config-java/gradle.lockfile +++ b/pkl-config-java/gradle.lockfile @@ -24,10 +24,14 @@ org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=kotlinCompilerPluginClasspat org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,pklCodegenJava,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=pklCodegenJava,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=pklCodegenJava,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,pklCodegenJava,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.21=pklCodegenJava +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21=pklCodegenJava +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.21=pklCodegenJava +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.21=pklCodegenJava org.jetbrains:annotations:13.0=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,pklCodegenJava,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.jupiter:junit-jupiter-engine:5.10.2=testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata @@ -35,6 +39,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=pklCodegenJava,runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=default,pklCodegenJava,runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=default,pklCodegenJava,runtimeClasspath,testRuntimeClasspath diff --git a/pkl-config-kotlin/gradle.lockfile b/pkl-config-kotlin/gradle.lockfile index 9c811290f..9d0b2cfeb 100644 --- a/pkl-config-kotlin/gradle.lockfile +++ b/pkl-config-kotlin/gradle.lockfile @@ -23,10 +23,14 @@ org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=kotlinCompilerPluginClasspat org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,pklCodegenKotlin,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,pklCodegenKotlin,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,pklCodegenKotlin,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,pklCodegenKotlin,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.21=pklCodegenKotlin +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21=pklCodegenKotlin +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.21=pklCodegenKotlin +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.21=pklCodegenKotlin org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,pklCodegenKotlin,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.jupiter:junit-jupiter-engine:5.10.2=testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata @@ -34,6 +38,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath diff --git a/pkl-core/gradle.lockfile b/pkl-core/gradle.lockfile index 5e7418399..a668f2a6d 100644 --- a/pkl-core/gradle.lockfile +++ b/pkl-core/gradle.lockfile @@ -37,6 +37,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath org.slf4j:slf4j-api:1.7.32=compileOnly diff --git a/pkl-core/pkl-core.gradle.kts b/pkl-core/pkl-core.gradle.kts index ca5e29c1b..5a9bb35cc 100644 --- a/pkl-core/pkl-core.gradle.kts +++ b/pkl-core/pkl-core.gradle.kts @@ -57,6 +57,7 @@ dependencies { compileOnly(projects.pklExecutor) implementation(libs.antlrRuntime) + implementation(libs.msgpack) implementation(libs.truffleApi) implementation(libs.graalSdk) diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/AbstractMessagePackDecoder.java b/pkl-core/src/main/java/org/pkl/core/messaging/AbstractMessagePackDecoder.java new file mode 100644 index 000000000..4cfc5ef8e --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/AbstractMessagePackDecoder.java @@ -0,0 +1,191 @@ +/** + * 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.core.messaging; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.msgpack.core.MessagePack; +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.messaging.Message.Type; +import org.pkl.core.util.ErrorMessages; +import org.pkl.core.util.Nullable; + +public abstract class AbstractMessagePackDecoder implements MessageDecoder { + + protected final MessageUnpacker unpacker; + + public AbstractMessagePackDecoder(MessageUnpacker unpacker) { + this.unpacker = unpacker; + } + + public AbstractMessagePackDecoder(InputStream stream) { + this(MessagePack.newDefaultUnpacker(stream)); + } + + protected abstract @Nullable Message decodeMessage(Type msgType, Map map) + throws DecodeException, URISyntaxException; + + @Override + public @Nullable Message decode() throws IOException, DecodeException { + if (!unpacker.hasNext()) { + return null; + } + + int code; + try { + var arraySize = unpacker.unpackArrayHeader(); + if (arraySize != 2) { + throw new DecodeException(ErrorMessages.create("malformedMessageHeaderLength", arraySize)); + } + code = unpacker.unpackInt(); + } catch (MessageTypeException e) { + throw new DecodeException(ErrorMessages.create("malformedMessageHeaderException"), e); + } + + Type msgType; + try { + msgType = Type.fromInt(code); + } catch (IllegalArgumentException e) { + throw new DecodeException( + ErrorMessages.create("malformedMessageHeaderUnrecognizedCode", Integer.toHexString(code)), + e); + } + + try { + var map = unpacker.unpackValue().asMapValue().map(); + var decoded = decodeMessage(msgType, map); + if (decoded != null) { + return decoded; + } + throw new DecodeException( + ErrorMessages.create("unhandledMessageCode", Integer.toHexString(code))); + } catch (MessageTypeException | URISyntaxException e) { + throw new DecodeException(ErrorMessages.create("malformedMessageBody", code), e); + } + } + + protected static @Nullable Value getNullable(Map map, String key) { + return map.get(new ImmutableStringValueImpl(key)); + } + + protected static Value get(Map map, String key) throws DecodeException { + var value = map.get(new ImmutableStringValueImpl(key)); + if (value == null) { + throw new DecodeException(ErrorMessages.create("missingMessageParameter", key)); + } + return value; + } + + protected static String unpackString(Map map, String key) throws DecodeException { + return get(map, key).asStringValue().asString(); + } + + protected static @Nullable String unpackStringOrNull(Map map, String key) { + var value = getNullable(map, key); + if (value == null) { + return null; + } + return value.asStringValue().asString(); + } + + protected static @Nullable T unpackStringOrNull( + Map map, String key, Function mapper) { + var value = getNullable(map, key); + if (value == null) { + return null; + } + return mapper.apply(value.asStringValue().asString()); + } + + protected static byte[] unpackByteArray(Map map, String key) { + var value = getNullable(map, key); + if (value == null) { + return new byte[0]; + } + return value.asBinaryValue().asByteArray(); + } + + protected static boolean unpackBoolean(Map map, String key) throws DecodeException { + return get(map, key).asBooleanValue().getBoolean(); + } + + protected static int unpackInt(Map map, String key) throws DecodeException { + return get(map, key).asIntegerValue().asInt(); + } + + protected static long unpackLong(Map map, String key) throws DecodeException { + return get(map, key).asIntegerValue().asLong(); + } + + protected static @Nullable Long unpackLongOrNull(Map map, String key) { + var value = getNullable(map, key); + if (value == null) { + return null; + } + return value.asIntegerValue().asLong(); + } + + protected static @Nullable T unpackLongOrNull( + Map map, String key, Function mapper) { + var value = unpackLongOrNull(map, key); + if (value == null) { + return null; + } + return mapper.apply(value); + } + + protected static @Nullable List unpackStringListOrNull( + Map map, String key) { + var value = getNullable(map, key); + if (value == null) { + return null; + } + + return value.asArrayValue().list().stream().map((it) -> it.asStringValue().asString()).toList(); + } + + protected static @Nullable Map unpackStringMapOrNull( + Map map, String key) { + var value = getNullable(map, key); + if (value == null) { + return null; + } + + return value.asMapValue().entrySet().stream() + .collect( + Collectors.toMap( + (e) -> e.getKey().asStringValue().asString(), + (e) -> e.getValue().asStringValue().asString())); + } + + protected static @Nullable List unpackStringListOrNull( + Map map, String key, Function mapper) { + var value = unpackStringListOrNull(map, key); + if (value == null) { + return null; + } + + return value.stream().map(mapper).toList(); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/AbstractMessagePackEncoder.java b/pkl-core/src/main/java/org/pkl/core/messaging/AbstractMessagePackEncoder.java new file mode 100644 index 000000000..c28ad1735 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/AbstractMessagePackEncoder.java @@ -0,0 +1,180 @@ +/** + * 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.core.messaging; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessagePacker; +import org.pkl.core.util.Nullable; + +public abstract class AbstractMessagePackEncoder implements MessageEncoder { + + protected final MessagePacker packer; + + public AbstractMessagePackEncoder(MessagePacker packer) { + this.packer = packer; + } + + public AbstractMessagePackEncoder(OutputStream stream) { + this(MessagePack.newDefaultPacker(stream)); + } + + protected abstract @Nullable void encodeMessage(Message msg) + throws ProtocolException, IOException; + + @Override + public final void encode(Message msg) throws IOException, ProtocolException { + packer.packArrayHeader(2); + packer.packInt(msg.getType().getCode()); + encodeMessage(msg); + packer.flush(); + } + + protected void packMapHeader(int size, @Nullable Object value1) throws IOException { + packer.packMapHeader(size + (value1 != null ? 1 : 0)); + } + + protected void packMapHeader(int size, @Nullable Object value1, @Nullable Object value2) + throws IOException { + packer.packMapHeader(size + (value1 != null ? 1 : 0) + (value2 != null ? 1 : 0)); + } + + protected void packMapHeader( + int size, + @Nullable Object value1, + @Nullable Object value2, + @Nullable Object value3, + @Nullable Object value4, + @Nullable Object value5, + @Nullable Object value6, + @Nullable Object value7, + @Nullable Object value8, + @Nullable Object value9, + @Nullable Object valueA, + @Nullable Object valueB, + @Nullable Object valueC, + @Nullable Object valueD) + throws IOException { + packer.packMapHeader( + size + + (value1 != null ? 1 : 0) + + (value2 != null ? 1 : 0) + + (value3 != null ? 1 : 0) + + (value4 != null ? 1 : 0) + + (value5 != null ? 1 : 0) + + (value6 != null ? 1 : 0) + + (value7 != null ? 1 : 0) + + (value8 != null ? 1 : 0) + + (value9 != null ? 1 : 0) + + (valueA != null ? 1 : 0) + + (valueB != null ? 1 : 0) + + (valueC != null ? 1 : 0) + + (valueD != null ? 1 : 0)); + } + + protected void packKeyValue(String name, @Nullable Integer value) throws IOException { + if (value == null) { + return; + } + packer.packString(name); + packer.packInt(value); + } + + protected void packKeyValue(String name, @Nullable Long value) throws IOException { + if (value == null) { + return; + } + packer.packString(name); + packer.packLong(value); + } + + protected void packKeyValueLong(String name, @Nullable T value, Function mapper) + throws IOException { + if (value == null) { + return; + } + packKeyValue(name, mapper.apply(value)); + } + + protected void packKeyValue(String name, @Nullable String value) throws IOException { + if (value == null) { + return; + } + packer.packString(name); + packer.packString(value); + } + + protected void packKeyValueString(String name, @Nullable T value, Function mapper) + throws IOException { + if (value == null) { + return; + } + packKeyValue(name, mapper.apply(value)); + } + + protected void packKeyValue(String name, @Nullable Collection value) throws IOException { + if (value == null) { + return; + } + packer.packString(name); + packer.packArrayHeader(value.size()); + for (String elem : value) { + packer.packString(elem); + } + } + + protected void packKeyValue( + String name, @Nullable Collection value, Function mapper) throws IOException { + if (value == null) { + return; + } + packer.packString(name); + packer.packArrayHeader(value.size()); + for (T elem : value) { + packer.packString(mapper.apply(elem)); + } + } + + protected void packKeyValue(String name, @Nullable Map value) throws IOException { + if (value == null) { + return; + } + packer.packString(name); + packer.packMapHeader(value.size()); + for (Map.Entry e : value.entrySet()) { + packer.packString(e.getKey()); + packer.packString(e.getValue()); + } + } + + protected void packKeyValue(String name, byte[] value) throws IOException { + if (value.length == 0) { + return; + } + packer.packString(name); + packer.packBinaryHeader(value.length); + packer.writePayload(value); + } + + protected void packKeyValue(String name, boolean value) throws IOException { + packer.packString(name); + packer.packBoolean(value); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/BaseMessagePackDecoder.java b/pkl-core/src/main/java/org/pkl/core/messaging/BaseMessagePackDecoder.java new file mode 100644 index 000000000..a0f4debee --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/BaseMessagePackDecoder.java @@ -0,0 +1,145 @@ +/** + * 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.core.messaging; + +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.msgpack.core.MessageUnpacker; +import org.msgpack.value.Value; +import org.pkl.core.messaging.Message.Type; +import org.pkl.core.messaging.Messages.*; +import org.pkl.core.module.PathElement; +import org.pkl.core.util.Nullable; + +public class BaseMessagePackDecoder extends AbstractMessagePackDecoder { + + public BaseMessagePackDecoder(MessageUnpacker unpacker) { + super(unpacker); + } + + public BaseMessagePackDecoder(InputStream stream) { + super(stream); + } + + protected @Nullable Message decodeMessage(Type msgType, Map map) + throws DecodeException, URISyntaxException { + return switch (msgType) { + case READ_RESOURCE_REQUEST -> + new ReadResourceRequest( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + new URI(unpackString(map, "uri"))); + case READ_RESOURCE_RESPONSE -> + new ReadResourceResponse( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + unpackByteArray(map, "contents"), + unpackStringOrNull(map, "error")); + case READ_MODULE_REQUEST -> + new ReadModuleRequest( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + new URI(unpackString(map, "uri"))); + case READ_MODULE_RESPONSE -> + new ReadModuleResponse( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + unpackStringOrNull(map, "contents"), + unpackStringOrNull(map, "error")); + case LIST_RESOURCES_REQUEST -> + new ListResourcesRequest( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + new URI(unpackString(map, "uri"))); + case LIST_RESOURCES_RESPONSE -> + new ListResourcesResponse( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + unpackPathElements(map, "pathElements"), + unpackStringOrNull(map, "error")); + case LIST_MODULES_REQUEST -> + new ListModulesRequest( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + new URI(unpackString(map, "uri"))); + case LIST_MODULES_RESPONSE -> + new ListModulesResponse( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + unpackPathElements(map, "pathElements"), + unpackStringOrNull(map, "error")); + default -> null; + }; + } + + protected static @Nullable List unpackModuleReaderSpec(Map map) + throws DecodeException { + var keys = getNullable(map, "clientModuleReaders"); + if (keys == null) { + return null; + } + + var result = new ArrayList(keys.asArrayValue().size()); + for (Value value : keys.asArrayValue()) { + var readerMap = value.asMapValue().map(); + result.add( + new ModuleReaderSpec( + unpackString(readerMap, "scheme"), + unpackBoolean(readerMap, "hasHierarchicalUris"), + unpackBoolean(readerMap, "isLocal"), + unpackBoolean(readerMap, "isGlobbable"))); + } + return result; + } + + protected static @Nullable List unpackResourceReaderSpec( + Map map) throws DecodeException { + var keys = getNullable(map, "clientResourceReaders"); + if (keys == null) { + return null; + } + + var result = new ArrayList(keys.asArrayValue().size()); + for (Value value : keys.asArrayValue()) { + var readerMap = value.asMapValue().map(); + result.add( + new ResourceReaderSpec( + unpackString(readerMap, "scheme"), + unpackBoolean(readerMap, "hasHierarchicalUris"), + unpackBoolean(readerMap, "isGlobbable"))); + } + return result; + } + + protected static @Nullable List unpackPathElements(Map map, String key) + throws DecodeException { + var value = getNullable(map, key); + if (value == null) { + return null; + } + + var result = new ArrayList(value.asArrayValue().size()); + for (Value pathElement : value.asArrayValue()) { + var pathElementMap = pathElement.asMapValue().map(); + result.add( + new PathElement( + unpackString(pathElementMap, "name"), unpackBoolean(pathElementMap, "isDirectory"))); + } + return result; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/BaseMessagePackEncoder.java b/pkl-core/src/main/java/org/pkl/core/messaging/BaseMessagePackEncoder.java new file mode 100644 index 000000000..82f669403 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/BaseMessagePackEncoder.java @@ -0,0 +1,136 @@ +/** + * 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.core.messaging; + +import java.io.IOException; +import java.io.OutputStream; +import org.msgpack.core.MessagePacker; +import org.pkl.core.messaging.Messages.*; +import org.pkl.core.module.PathElement; +import org.pkl.core.util.ErrorMessages; +import org.pkl.core.util.Nullable; + +public class BaseMessagePackEncoder extends AbstractMessagePackEncoder { + + public BaseMessagePackEncoder(MessagePacker packer) { + super(packer); + } + + public BaseMessagePackEncoder(OutputStream stream) { + super(stream); + } + + protected void packModuleReaderSpec(ModuleReaderSpec reader) throws IOException { + packer.packMapHeader(4); + packKeyValue("scheme", reader.getScheme()); + packKeyValue("hasHierarchicalUris", reader.getHasHierarchicalUris()); + packKeyValue("isLocal", reader.isLocal()); + packKeyValue("isGlobbable", reader.isGlobbable()); + } + + protected void packResourceReaderSpec(ResourceReaderSpec reader) throws IOException { + packer.packMapHeader(3); + packKeyValue("scheme", reader.getScheme()); + packKeyValue("hasHierarchicalUris", reader.getHasHierarchicalUris()); + packKeyValue("isGlobbable", reader.isGlobbable()); + } + + protected void packPathElement(PathElement pathElement) throws IOException { + packer.packMapHeader(2); + packKeyValue("name", pathElement.getName()); + packKeyValue("isDirectory", pathElement.isDirectory()); + } + + protected @Nullable void encodeMessage(Message msg) throws ProtocolException, IOException { + switch (msg.getType()) { + case READ_RESOURCE_REQUEST -> { + var m = (ReadResourceRequest) msg; + packer.packMapHeader(3); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + packKeyValue("uri", m.getUri().toString()); + } + case READ_RESOURCE_RESPONSE -> { + var m = (ReadResourceResponse) msg; + packMapHeader(2, m.getContents(), m.getError()); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + packKeyValue("contents", m.getContents()); + packKeyValue("error", m.getError()); + } + case READ_MODULE_REQUEST -> { + var m = (ReadModuleRequest) msg; + packer.packMapHeader(3); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + packKeyValue("uri", m.getUri().toString()); + } + case READ_MODULE_RESPONSE -> { + var m = (ReadModuleResponse) msg; + packMapHeader(2, m.getContents(), m.getError()); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + packKeyValue("contents", m.getContents()); + packKeyValue("error", m.getError()); + } + case LIST_RESOURCES_REQUEST -> { + var m = (ListResourcesRequest) msg; + packer.packMapHeader(3); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + packKeyValue("uri", m.getUri().toString()); + } + case LIST_RESOURCES_RESPONSE -> { + var m = (ListResourcesResponse) msg; + packMapHeader(2, m.getPathElements(), m.getError()); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + if (m.getPathElements() != null) { + packer.packString("pathElements"); + packer.packArrayHeader(m.getPathElements().size()); + for (var pathElement : m.getPathElements()) { + packPathElement(pathElement); + } + } + packKeyValue("error", m.getError()); + } + case LIST_MODULES_REQUEST -> { + var m = (ListModulesRequest) msg; + packer.packMapHeader(3); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + packKeyValue("uri", m.getUri().toString()); + } + case LIST_MODULES_RESPONSE -> { + var m = (ListModulesResponse) msg; + packMapHeader(2, m.getPathElements(), m.getError()); + packKeyValue("requestId", m.getRequestId()); + packKeyValue("evaluatorId", m.getEvaluatorId()); + if (m.getPathElements() != null) { + packer.packString("pathElements"); + packer.packArrayHeader(m.getPathElements().size()); + for (var pathElement : m.getPathElements()) { + packPathElement(pathElement); + } + } + packKeyValue("error", m.getError()); + } + default -> + throw new ProtocolException( + ErrorMessages.create("unhandledMessageType", msg.getType().toString())); + } + } +} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessageTransport.kt b/pkl-core/src/main/java/org/pkl/core/messaging/DecodeException.java similarity index 63% rename from pkl-server/src/main/kotlin/org/pkl/server/MessageTransport.kt rename to pkl-core/src/main/java/org/pkl/core/messaging/DecodeException.java index b990bf759..c2a5b5429 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessageTransport.kt +++ b/pkl-core/src/main/java/org/pkl/core/messaging/DecodeException.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.server +package org.pkl.core.messaging; -/** A bidirectional transport for sending and receiving messages. */ -interface MessageTransport : AutoCloseable { - fun start(oneWayHandler: (OneWayMessage) -> Unit, requestHandler: (RequestMessage) -> Unit) +public final class DecodeException extends ProtocolException { - fun send(message: OneWayMessage) + public DecodeException(String msg, Throwable cause) { + super(msg, cause); + } - fun send(message: RequestMessage, responseHandler: (ResponseMessage) -> Unit) - - fun send(message: ResponseMessage) + public DecodeException(String msg) { + super(msg); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/Message.java b/pkl-core/src/main/java/org/pkl/core/messaging/Message.java new file mode 100644 index 000000000..7faf7ebc5 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/Message.java @@ -0,0 +1,145 @@ +/** + * 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.core.messaging; + +import java.util.*; + +public interface Message { + + Type getType(); + + enum Type { + 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); + + private final int code; + + Type(int code) { + this.code = code; + } + + public static Type fromInt(int val) throws IllegalArgumentException { + for (Type t : Type.values()) { + if (t.code == val) { + return t; + } + } + + throw new IllegalArgumentException("Unknown Message.Type code"); + } + + public int getCode() { + return code; + } + } + + interface OneWay extends Message {} + + interface Request extends Message { + long getRequestId(); + } + + interface Response extends Message { + long getRequestId(); + } + + interface Client extends Message { + + interface Request extends Client, Message.Request {} + + interface Response extends Client, Message.Response {} + + interface OneWay extends Client, Message.OneWay {} + } + + interface Server extends Message { + + interface Request extends Server, Message.Request {} + + interface Response extends Server, Message.Response {} + + interface OneWay extends Server, Message.OneWay {} + } + + abstract class Base implements Message { + + private final Type type; + + @Override + public Type getType() { + return type; + } + + public Base(Type type) { + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Base base)) { + return false; + } + + return type == base.type; + } + + @Override + public int hashCode() { + return type.hashCode(); + } + + private static class TwoWay extends Base { + + private final long requestId; + + public TwoWay(Type type, long requestId) { + super(type); + this.requestId = requestId; + } + + public long getRequestId() { + return this.requestId; + } + } + + public static class Request extends TwoWay implements Message.Request { + public Request(Type type, long requestId) { + super(type, requestId); + } + } + + public static class Response extends TwoWay implements Message.Response { + public Response(Type type, long requestId) { + super(type, requestId); + } + } + } +} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessageDecoder.kt b/pkl-core/src/main/java/org/pkl/core/messaging/MessageDecoder.java similarity index 77% rename from pkl-server/src/main/kotlin/org/pkl/server/MessageDecoder.kt rename to pkl-core/src/main/java/org/pkl/core/messaging/MessageDecoder.java index 2bda9deea..cd220dd59 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessageDecoder.kt +++ b/pkl-core/src/main/java/org/pkl/core/messaging/MessageDecoder.java @@ -13,9 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.server +package org.pkl.core.messaging; + +import java.io.IOException; +import org.pkl.core.util.Nullable; /** Decodes a stream of messages. */ -internal interface MessageDecoder { - fun decode(): Message? +public interface MessageDecoder { + @Nullable + Message decode() throws IOException, DecodeException; } diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessageEncoder.kt b/pkl-core/src/main/java/org/pkl/core/messaging/MessageEncoder.java similarity index 81% rename from pkl-server/src/main/kotlin/org/pkl/server/MessageEncoder.kt rename to pkl-core/src/main/java/org/pkl/core/messaging/MessageEncoder.java index 5eb5d5960..cb2807ab1 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessageEncoder.kt +++ b/pkl-core/src/main/java/org/pkl/core/messaging/MessageEncoder.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.server +package org.pkl.core.messaging; + +import java.io.IOException; /** Encodes a stream of messages. */ -internal interface MessageEncoder { - fun encode(msg: Message) +public interface MessageEncoder { + void encode(Message msg) throws IOException, ProtocolException; } diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/MessageTransport.java b/pkl-core/src/main/java/org/pkl/core/messaging/MessageTransport.java new file mode 100644 index 000000000..206f35c81 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/MessageTransport.java @@ -0,0 +1,43 @@ +/** + * 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.core.messaging; + +import java.io.IOException; + +/** A bidirectional transport for sending and receiving messages. */ +public interface MessageTransport extends AutoCloseable { + interface OneWayHandler { + void handleOneWay(Message.OneWay msg); + } + + interface RequestHandler { + void handleRequest(Message.Request msg); + } + + interface ResponseHandler { + void handleResponse(Message.Response msg); + } + + void start(OneWayHandler oneWayHandler, RequestHandler requestHandler) + throws ProtocolException, IOException; + + void send(Message.OneWay message) throws ProtocolException, IOException; + + void send(Message.Request message, ResponseHandler responseHandler) + throws ProtocolException, IOException; + + void send(Message.Response message) throws ProtocolException, IOException; +} diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/MessageTransports.java b/pkl-core/src/main/java/org/pkl/core/messaging/MessageTransports.java new file mode 100644 index 000000000..114ec13da --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/MessageTransports.java @@ -0,0 +1,185 @@ +/** + * 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.core.messaging; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.pkl.core.messaging.Message.OneWay; +import org.pkl.core.messaging.Message.Response; +import org.pkl.core.util.ErrorMessages; +import org.pkl.core.util.Pair; + +/** Factory methods for creating [MessageTransport]s. */ +public class MessageTransports { + + public interface Logger { + + void log(String msg); + } + + /** Creates a message transport that reads from [inputStream] and writes to [outputStream]. */ + public static MessageTransport stream( + MessageDecoder decoder, MessageEncoder encoder, Logger logger) { + return new EncodingMessageTransport(decoder, encoder, logger); + } + + /** Creates "client" and "server" transports that are directly connected to each other. */ + public static Pair direct(Logger logger) { + var transport1 = new DirectMessageTransport(logger); + var transport2 = new DirectMessageTransport(logger); + transport1.setOther(transport2); + transport2.setOther(transport1); + return Pair.of(transport1, transport2); + } + + protected static class EncodingMessageTransport extends AbstractMessageTransport { + + private final MessageDecoder decoder; + private final MessageEncoder encoder; + private volatile boolean isClosed = false; + + protected EncodingMessageTransport( + MessageDecoder decoder, MessageEncoder encoder, Logger logger) { + super(logger); + this.decoder = decoder; + this.encoder = encoder; + } + + @Override + protected void doStart() throws ProtocolException, IOException { + while (!isClosed) { + var message = decoder.decode(); + if (message == null) { + return; + } + accept(message); + } + } + + @Override + protected void doClose() { + isClosed = true; + } + + @Override + protected void doSend(Message message) throws ProtocolException, IOException { + encoder.encode(message); + } + } + + protected static class DirectMessageTransport extends AbstractMessageTransport { + + private DirectMessageTransport other; + + protected DirectMessageTransport(Logger logger) { + super(logger); + } + + @Override + protected void doStart() {} + + @Override + protected void doClose() {} + + @Override + protected void doSend(Message message) throws ProtocolException { + other.accept(message); + } + + public void setOther(DirectMessageTransport other) { + this.other = other; + } + } + + // TODO: clean up callbacks if evaluation fails for some reason (ThreadInterrupt, timeout, etc) + protected abstract static class AbstractMessageTransport implements MessageTransport { + + private final Logger logger; + private MessageTransport.OneWayHandler oneWayHandler; + private MessageTransport.RequestHandler requestHandler; + private final Map responseHandlers = new ConcurrentHashMap<>(); + + protected AbstractMessageTransport(Logger logger) { + this.logger = logger; + } + + protected void log(String message, Object... args) { + var formatter = new MessageFormat(message); + logger.log(formatter.format(args)); + } + + protected abstract void doStart() throws ProtocolException, IOException; + + protected abstract void doClose(); + + protected abstract void doSend(Message message) throws ProtocolException, IOException; + + protected void accept(Message message) throws ProtocolException { + log("Received message: {0}", message); + if (message instanceof Message.OneWay msg) { + oneWayHandler.handleOneWay(msg); + } else if (message instanceof Message.Request msg) { + requestHandler.handleRequest(msg); + } else if (message instanceof Message.Response msg) { + var handler = responseHandlers.remove(msg.getRequestId()); + if (handler == null) { + throw new ProtocolException( + ErrorMessages.create( + "unknownRequestId", message.getClass().getSimpleName(), msg.getRequestId())); + } + handler.handleResponse(msg); + } + } + + @Override + public final void start(OneWayHandler oneWayHandler, RequestHandler requestHandler) + throws ProtocolException, IOException { + log("Starting transport: {0}", this); + this.oneWayHandler = oneWayHandler; + this.requestHandler = requestHandler; + doStart(); + } + + @Override + public final void close() throws Exception { + log("Closing transport: {0}", this); + doClose(); + responseHandlers.clear(); + } + + @Override + public void send(OneWay message) throws ProtocolException, IOException { + log("Sending message: {0}", message); + doSend(message); + } + + @Override + public void send(Message.Request message, ResponseHandler responseHandler) + throws ProtocolException, IOException { + log("Sending message: {0}", message); + responseHandlers.put(message.getRequestId(), responseHandler); + doSend(message); + } + + @Override + public void send(Response message) throws ProtocolException, IOException { + log("Sending message: {0}", message); + doSend(message); + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/Messages.java b/pkl-core/src/main/java/org/pkl/core/messaging/Messages.java new file mode 100644 index 000000000..62bde4eea --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/Messages.java @@ -0,0 +1,490 @@ +/** + * 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.core.messaging; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import org.pkl.core.messaging.Message.Base; +import org.pkl.core.messaging.Message.Client; +import org.pkl.core.messaging.Message.Server; +import org.pkl.core.module.PathElement; +import org.pkl.core.util.Nullable; + +public class Messages { + + public static final class ModuleReaderSpec { + + private final String scheme; + private final boolean hasHierarchicalUris; + private final boolean isLocal; + private final boolean isGlobbable; + + public ModuleReaderSpec( + String scheme, boolean hasHierarchicalUris, boolean isLocal, boolean isGlobbable) { + this.scheme = scheme; + this.hasHierarchicalUris = hasHierarchicalUris; + this.isLocal = isLocal; + this.isGlobbable = isGlobbable; + } + + public String getScheme() { + return scheme; + } + + public boolean getHasHierarchicalUris() { + return hasHierarchicalUris; + } + + public boolean isLocal() { + return isLocal; + } + + public boolean isGlobbable() { + return isGlobbable; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ModuleReaderSpec that)) { + return false; + } + + return hasHierarchicalUris == that.hasHierarchicalUris + && isLocal == that.isLocal + && isGlobbable == that.isGlobbable + && scheme.equals(that.scheme); + } + + @Override + public int hashCode() { + int result = scheme.hashCode(); + result = 31 * result + Boolean.hashCode(hasHierarchicalUris); + result = 31 * result + Boolean.hashCode(isLocal); + result = 31 * result + Boolean.hashCode(isGlobbable); + return result; + } + } + + public static final class ResourceReaderSpec { + + private final String scheme; + private final boolean hasHierarchicalUris; + private final boolean isGlobbable; + + public ResourceReaderSpec(String scheme, boolean hasHierarchicalUris, boolean isGlobbable) { + this.scheme = scheme; + this.hasHierarchicalUris = hasHierarchicalUris; + this.isGlobbable = isGlobbable; + } + + public String getScheme() { + return scheme; + } + + public boolean getHasHierarchicalUris() { + return hasHierarchicalUris; + } + + public boolean isGlobbable() { + return isGlobbable; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ResourceReaderSpec that)) { + return false; + } + + return hasHierarchicalUris == that.hasHierarchicalUris + && isGlobbable == that.isGlobbable + && scheme.equals(that.scheme); + } + + @Override + public int hashCode() { + int result = scheme.hashCode(); + result = 31 * result + Boolean.hashCode(hasHierarchicalUris); + result = 31 * result + Boolean.hashCode(isGlobbable); + return result; + } + } + + public static final class ListResourcesRequest extends Base.Request implements Server.Request { + + private final long evaluatorId; + private final URI uri; + + public ListResourcesRequest(long requestId, long evaluatorId, URI uri) { + super(Type.LIST_RESOURCES_REQUEST, requestId); + this.evaluatorId = evaluatorId; + this.uri = uri; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public URI getUri() { + return uri; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ListResourcesRequest that)) { + return false; + } + + return evaluatorId == that.evaluatorId && uri.equals(that.uri); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + uri.hashCode(); + return result; + } + } + + public static final class ListResourcesResponse extends Base.Response implements Client.Response { + + private final long evaluatorId; + private final @Nullable List pathElements; + private final @Nullable String error; + + public ListResourcesResponse( + long requestId, + long evaluatorId, + @Nullable List pathElements, + @Nullable String error) { + super(Type.LIST_RESOURCES_RESPONSE, requestId); + this.evaluatorId = evaluatorId; + this.pathElements = pathElements; + this.error = error; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public @Nullable List getPathElements() { + return pathElements; + } + + public @Nullable String getError() { + return error; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ListResourcesResponse that)) { + return false; + } + + return evaluatorId == that.evaluatorId + && Objects.equals(pathElements, that.pathElements) + && Objects.equals(error, that.error); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + Objects.hashCode(pathElements); + result = 31 * result + Objects.hashCode(error); + return result; + } + } + + public static final class ListModulesRequest extends Base.Request implements Message.Request { + + private final long evaluatorId; + private final URI uri; + + public ListModulesRequest(long requestId, long evaluatorId, URI uri) { + super(Type.LIST_MODULES_REQUEST, requestId); + this.evaluatorId = evaluatorId; + this.uri = uri; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public URI getUri() { + return uri; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ListModulesRequest that)) { + return false; + } + + return evaluatorId == that.evaluatorId && uri.equals(that.uri); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + uri.hashCode(); + return result; + } + } + + public static final class ListModulesResponse extends Base.Response implements Client.Response { + + private final long evaluatorId; + private final @Nullable List pathElements; + private final @Nullable String error; + + public ListModulesResponse( + long requestId, + long evaluatorId, + @Nullable List pathElements, + @Nullable String error) { + super(Type.LIST_MODULES_RESPONSE, requestId); + this.evaluatorId = evaluatorId; + this.pathElements = pathElements; + this.error = error; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public @Nullable List getPathElements() { + return pathElements; + } + + public @Nullable String getError() { + return error; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ListModulesResponse that)) { + return false; + } + + return evaluatorId == that.evaluatorId + && Objects.equals(pathElements, that.pathElements) + && Objects.equals(error, that.error); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + Objects.hashCode(pathElements); + result = 31 * result + Objects.hashCode(error); + return result; + } + } + + public static final class ReadResourceRequest extends Base.Request implements Message.Request { + + private final long evaluatorId; + private final URI uri; + + public ReadResourceRequest(long requestId, long evaluatorId, URI uri) { + super(Type.READ_RESOURCE_REQUEST, requestId); + this.evaluatorId = evaluatorId; + this.uri = uri; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public URI getUri() { + return uri; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadResourceRequest that)) { + return false; + } + + return evaluatorId == that.evaluatorId && uri.equals(that.uri); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + uri.hashCode(); + return result; + } + } + + public static final class ReadResourceResponse extends Base.Response implements Client.Response { + + private final long evaluatorId; + private final byte[] contents; + private final @Nullable String error; + + public ReadResourceResponse( + long requestId, long evaluatorId, byte[] contents, @Nullable String error) { + super(Type.READ_RESOURCE_RESPONSE, requestId); + this.evaluatorId = evaluatorId; + this.contents = contents; + this.error = error; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public byte[] getContents() { + return contents; + } + + public @Nullable String getError() { + return error; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadResourceResponse that)) { + return false; + } + + return evaluatorId == that.evaluatorId + && Arrays.equals(contents, that.contents) + && Objects.equals(error, that.error); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + Arrays.hashCode(contents); + result = 31 * result + Objects.hashCode(error); + return result; + } + } + + public static final class ReadModuleRequest extends Base.Request implements Message.Request { + + private final long evaluatorId; + private final URI uri; + + public ReadModuleRequest(long requestId, long evaluatorId, URI uri) { + super(Type.READ_MODULE_REQUEST, requestId); + this.evaluatorId = evaluatorId; + this.uri = uri; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public URI getUri() { + return uri; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadModuleRequest that)) { + return false; + } + + return evaluatorId == that.evaluatorId && uri.equals(that.uri); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + uri.hashCode(); + return result; + } + } + + public static final class ReadModuleResponse extends Base.Response implements Client.Response { + + private final long evaluatorId; + private final @Nullable String contents; + private final @Nullable String error; + + public ReadModuleResponse( + long requestId, long evaluatorId, @Nullable String contents, @Nullable String error) { + super(Type.READ_MODULE_RESPONSE, requestId); + this.evaluatorId = evaluatorId; + this.contents = contents; + this.error = error; + } + + public long getEvaluatorId() { + return evaluatorId; + } + + public @Nullable String getContents() { + return contents; + } + + public @Nullable String getError() { + return error; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadModuleResponse that)) { + return false; + } + + return evaluatorId == that.evaluatorId + && Objects.equals(contents, that.contents) + && Objects.equals(error, that.error); + } + + @Override + public int hashCode() { + int result = Long.hashCode(evaluatorId); + result = 31 * result + Objects.hashCode(contents); + result = 31 * result + Objects.hashCode(error); + return result; + } + } +} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/ServerException.kt b/pkl-core/src/main/java/org/pkl/core/messaging/ProtocolException.java similarity index 61% rename from pkl-server/src/main/kotlin/org/pkl/server/ServerException.kt rename to pkl-core/src/main/java/org/pkl/core/messaging/ProtocolException.java index 9af2df051..d2fd2d7a2 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/ServerException.kt +++ b/pkl-core/src/main/java/org/pkl/core/messaging/ProtocolException.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.server +package org.pkl.core.messaging; -sealed class ServerException(msg: String, cause: Throwable?) : Exception(msg, cause) +public class ProtocolException extends Exception { + public ProtocolException(String msg, Throwable cause) { + super(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) + public ProtocolException(String msg) { + super(msg); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/messaging/package-info.java b/pkl-core/src/main/java/org/pkl/core/messaging/package-info.java new file mode 100644 index 000000000..4253de960 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/messaging/package-info.java @@ -0,0 +1,4 @@ +@NonnullByDefault +package org.pkl.core.messaging; + +import org.pkl.core.util.NonnullByDefault; diff --git a/pkl-core/src/main/java/org/pkl/core/packages/Checksums.java b/pkl-core/src/main/java/org/pkl/core/packages/Checksums.java index a9ebd29aa..839286e8c 100644 --- a/pkl-core/src/main/java/org/pkl/core/packages/Checksums.java +++ b/pkl-core/src/main/java/org/pkl/core/packages/Checksums.java @@ -16,6 +16,7 @@ package org.pkl.core.packages; import java.util.Objects; +import org.pkl.core.util.Nullable; public final class Checksums { private final String sha256; @@ -34,7 +35,7 @@ public String getSha256() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index 98b7ea1be..15bd274cb 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -1057,3 +1057,27 @@ To fix this problem, add dependendy `org.pkl:pkl-certs`. # suppress inspection "HttpUrlsUsage" malformedProxyAddress=\ Malformed proxy URI (expecting `http://[:]`): `{0}`. + +malformedMessageHeaderLength=\ +Malformed message header (expected size 2, but got {0}). + +malformedMessageHeaderException=\ +Malformed message header. + +malformedMessageHeaderUnrecognizedCode=\ +Malformed message header (unrecognized code `{0}`). + +unhandledMessageCode=\ +Unhandled decoding message code `{0}`. + +unhandledMessageType=\ +Unhandled encoding message type `{0}`. + +malformedMessageBody=\ +Malformed message body for message with code `{0}`. + +missingMessageParameter=\ +Missing message parameter `{0}` + +unknownRequestId=\ +Received response {0} for unknown request ID `{1}`. diff --git a/pkl-core/src/test/kotlin/org/pkl/core/messaging/MessagePackCodecTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/messaging/MessagePackCodecTest.kt new file mode 100644 index 000000000..39f79dbf9 --- /dev/null +++ b/pkl-core/src/test/kotlin/org/pkl/core/messaging/MessagePackCodecTest.kt @@ -0,0 +1,128 @@ +/** + * 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.core.messaging + +import java.io.PipedInputStream +import java.io.PipedOutputStream +import java.net.URI +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.messaging.Messages.* +import org.pkl.core.module.PathElement + +class MessagePackCodecTest { + private val encoder: MessageEncoder + private val decoder: MessageDecoder + + init { + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + encoder = BaseMessagePackEncoder(MessagePack.newDefaultPacker(outputStream)) + decoder = BaseMessagePackDecoder(MessagePack.newDefaultUnpacker(inputStream)) + } + + private fun roundtrip(message: Message) { + encoder.encode(message) + val decoded = decoder.decode() + assertThat(decoded).isEqualTo(message) + } + + @Test + fun `round-trip ReadResourceRequest`() { + roundtrip(ReadResourceRequest(123, 456, URI("some/resource.json"))) + } + + @Test + fun `round-trip ReadResourceResponse`() { + roundtrip(ReadResourceResponse(123, 456, byteArrayOf(1, 2, 3, 4, 5), null)) + } + + @Test + fun `round-trip ReadModuleRequest`() { + roundtrip(ReadModuleRequest(123, 456, URI("some/module.pkl"))) + } + + @Test + fun `round-trip ReadModuleResponse`() { + roundtrip(ReadModuleResponse(123, 456, "x = 42", null)) + } + + @Test + fun `round-trip ListModulesRequest`() { + roundtrip(ListModulesRequest(135, 246, URI("foo:/bar/baz/biz"))) + } + + @Test + fun `round-trip ListModulesResponse`() { + roundtrip( + ListModulesResponse( + 123, + 234, + listOf(PathElement("foo", true), PathElement("bar", false)), + null + ) + ) + roundtrip(ListModulesResponse(123, 234, null, "Something dun went wrong")) + } + + @Test + fun `round-trip ListResourcesRequest`() { + roundtrip(ListResourcesRequest(987, 1359, URI("bar:/bazzy"))) + } + + @Test + fun `round-trip ListResourcesResponse`() { + roundtrip( + ListResourcesResponse( + 3851, + 3019, + listOf(PathElement("foo", true), PathElement("bar", false)), + null + ) + ) + roundtrip(ListResourcesResponse(3851, 3019, null, "something went wrong")) + } + + @Test + fun `decode request with missing request ID`() { + val bytes = + MessagePack.newDefaultBufferPacker() + .apply { + packArrayHeader(2) + packInt(Message.Type.LIST_RESOURCES_REQUEST.code) + packMapHeader(1) + packString("uri") + packString("file:/test") + } + .toByteArray() + + val decoder = BaseMessagePackDecoder(MessagePack.newDefaultUnpacker(bytes)) + val exception = assertThrows { decoder.decode() } + assertThat(exception.message).contains("requestId") + } + + @Test + fun `decode invalid message header`() { + val bytes = MessagePack.newDefaultBufferPacker().apply { packInt(2) }.toByteArray() + + val decoder = BaseMessagePackDecoder(MessagePack.newDefaultUnpacker(bytes)) + val exception = assertThrows { decoder.decode() } + assertThat(exception).hasMessage("Malformed message header.") + assertThat(exception).hasRootCauseMessage("Expected Array, but got Integer (02)") + } +} diff --git a/pkl-doc/gradle.lockfile b/pkl-doc/gradle.lockfile index 2e644be89..498fc781f 100644 --- a/pkl-doc/gradle.lockfile +++ b/pkl-doc/gradle.lockfile @@ -58,10 +58,14 @@ org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerP org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-serialization:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,testImplementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib:1.8.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-util-io:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.1=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath @@ -77,6 +81,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=runtimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath diff --git a/pkl-executor/gradle.lockfile b/pkl-executor/gradle.lockfile index 1d68115db..0f1ce09e5 100644 --- a/pkl-executor/gradle.lockfile +++ b/pkl-executor/gradle.lockfile @@ -29,6 +29,7 @@ org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplement org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit.platform:junit-platform-engine:1.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata +org.msgpack:msgpack-core:0.9.0=testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata org.organicdesign:Paguro:3.10.3=testRuntimeClasspath org.pkl-lang:pkl-config-java-all:0.25.0=pklHistoricalDistributions diff --git a/pkl-server/src/main/kotlin/org/pkl/server/ClientLogger.kt b/pkl-server/src/main/kotlin/org/pkl/server/ClientLogger.kt index 39a3457f5..42687be50 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/ClientLogger.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/ClientLogger.kt @@ -17,16 +17,17 @@ package org.pkl.server import org.pkl.core.Logger import org.pkl.core.StackFrame +import org.pkl.core.messaging.MessageTransport 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)) + transport.send(LogMessage(evaluatorId, 0, message, frame.moduleUri)) } override fun warn(message: String, frame: StackFrame) { - transport.send(LogMessage(evaluatorId, level = 1, message, frame.moduleUri)) + transport.send(LogMessage(evaluatorId, 1, message, frame.moduleUri)) } } diff --git a/pkl-server/src/main/kotlin/org/pkl/server/ClientModuleKeyFactory.kt b/pkl-server/src/main/kotlin/org/pkl/server/ClientModuleKeyFactory.kt index 90e70875d..59945adc3 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/ClientModuleKeyFactory.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/ClientModuleKeyFactory.kt @@ -23,6 +23,8 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Future import kotlin.random.Random import org.pkl.core.SecurityManager +import org.pkl.core.messaging.* +import org.pkl.core.messaging.Messages.* import org.pkl.core.module.ModuleKey import org.pkl.core.module.ModuleKeyFactory import org.pkl.core.module.PathElement diff --git a/pkl-server/src/main/kotlin/org/pkl/server/ClientResourceReader.kt b/pkl-server/src/main/kotlin/org/pkl/server/ClientResourceReader.kt index a88a1e899..cac60fc07 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/ClientResourceReader.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/ClientResourceReader.kt @@ -23,6 +23,9 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Future import kotlin.random.Random import org.pkl.core.SecurityManager +import org.pkl.core.messaging.MessageTransport +import org.pkl.core.messaging.Messages.* +import org.pkl.core.messaging.ProtocolException import org.pkl.core.module.PathElement import org.pkl.core.resource.Resource import org.pkl.core.resource.ResourceReader @@ -91,7 +94,7 @@ internal class ClientResourceReader( if (response.error != null) { completeExceptionally(IOException(response.error)) } else { - complete(response.contents!!) + complete(response.contents) } } else -> { diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessageDecoders.kt b/pkl-server/src/main/kotlin/org/pkl/server/MessageDecoders.kt deleted file mode 100644 index 335973290..000000000 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessageDecoders.kt +++ /dev/null @@ -1,31 +0,0 @@ -/** - * 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)) -} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessageEncoders.kt b/pkl-server/src/main/kotlin/org/pkl/server/MessageEncoders.kt deleted file mode 100644 index c49ca10cb..000000000 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessageEncoders.kt +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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) -} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt b/pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt deleted file mode 100644 index e87c3506f..000000000 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessagePackDecoder.kt +++ /dev/null @@ -1,292 +0,0 @@ -/** - * 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.evaluatorSettings.PklEvaluatorSettings -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(), - clientResourceReaders = map.unpackResourceReaderSpec(), - 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(), - http = map.unpackHttp(), - ) - } - 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.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.getNullable(key: String): Value? = - this[ImmutableStringValueImpl(key)] - - private fun Map.get(key: String): Value = - getNullable(key) ?: throw DecodeException("Missing message parameter `$key`") - - private fun Array.unpackValue(key: String): Value = - unpackValueOrNull(key) ?: throw DecodeException("Missing message parameter `$key`.") - - private fun Map.unpackStringListOrNull(key: String): List? { - val value = getNullable(key) ?: return null - return value.asArrayValue().map { it.asStringValue().asString() } - } - - private fun Map.unpackStringMapOrNull(key: String): Map? { - val value = getNullable(key) ?: return null - return value.asMapValue().entrySet().associate { (k, v) -> - k.asStringValue().asString() to v.asStringValue().asString() - } - } - - private fun Map.unpackLong(key: String): Long = get(key).asIntegerValue().asLong() - - private fun Map.unpackBoolean(key: String): Boolean = - get(key).asBooleanValue().boolean - - private fun Map.unpackBooleanOrNull(key: String): Boolean? = - getNullable(key)?.asBooleanValue()?.boolean - - private fun Map.unpackLongOrNull(key: String): Long? = - getNullable(key)?.asIntegerValue()?.asLong() - - private fun Map.unpackIntValue(key: String): Int = get(key).asIntegerValue().asInt() - - private fun Map.unpackString(key: String): String = - get(key).asStringValue().asString() - - private fun Map.unpackStringOrNull(key: String): String? = - getNullable(key)?.asStringValue()?.asString() - - private fun Map.unpackByteArrayOrNull(key: String): ByteArray? = - getNullable(key)?.asBinaryValue()?.asByteArray() - - private fun Map.unpackPathElements(key: String): List? = - getNullable(key)?.asArrayValue()?.map { pathElement -> - val map = pathElement.asMapValue().map() - PathElement(map.unpackString("name"), map.unpackBoolean("isDirectory")) - } - - private fun Map.unpackModuleReaderSpec(): List? { - val keys = getNullable("clientModuleReaders") ?: 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.unpackResourceReaderSpec(): List { - val keys = getNullable("clientResourceReaders") ?: 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.unpackProject(): Project? { - val projMap = getNullable("project")?.asMapValue()?.map() ?: return null - val projectFileUri = URI(projMap.unpackString("projectFileUri")) - val dependencies = projMap.unpackDependencies("dependencies") - return Project(projectFileUri, null, dependencies) - } - - private fun Map.unpackHttp(): Http? { - val httpMap = getNullable("http")?.asMapValue()?.map() ?: return null - val proxy = httpMap.unpackProxy() - val caCertificates = httpMap.getNullable("caCertificates")?.asBinaryValue()?.asByteArray() - return Http(caCertificates, proxy) - } - - private fun Map.unpackProxy(): PklEvaluatorSettings.Proxy? { - val proxyMap = getNullable("proxy")?.asMapValue()?.map() ?: return null - val address = proxyMap.unpackString("address") - val noProxy = proxyMap.unpackStringListOrNull("noProxy") - return PklEvaluatorSettings.Proxy.create(address, noProxy) - } - - private fun Map.unpackDependencies(name: String): Map { - 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) - } - } -} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessagePackEncoder.kt b/pkl-server/src/main/kotlin/org/pkl/server/MessagePackEncoder.kt deleted file mode 100644 index 2bbd4ccc1..000000000 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessagePackEncoder.kt +++ /dev/null @@ -1,332 +0,0 @@ -/** - * 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.packHttp(http: Http) { - if ((http.caCertificates ?: http.proxy) == null) { - packMapHeader(0) - return - } - packMapHeader(0, http.caCertificates, http.proxy) - packKeyValue("caCertificates", http.caCertificates) - http.proxy?.let { proxy -> - packString("proxy") - packMapHeader(0, proxy.address, proxy.noProxy) - packKeyValue("address", proxy.address?.toString()) - packKeyValue("noProxy", proxy.noProxy) - } - } - - private fun MessagePacker.packDependencies(dependencies: Map) { - 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, - msg.http - ) - 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) - } - if (msg.http != null) { - packString("http") - packHttp(msg.http) - } - } - 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?, - value6: 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) + - (if (value6 != 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?) { - if (value == null) return - packString(name) - packArrayHeader(value.size) - for (elem in value) packString(elem) - } - - private fun MessagePacker.packKeyValue(name: String, value: Map?) { - 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) - } -} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/MessageTransports.kt b/pkl-server/src/main/kotlin/org/pkl/server/MessageTransports.kt deleted file mode 100644 index 6a6547c37..000000000 --- a/pkl-server/src/main/kotlin/org/pkl/server/MessageTransports.kt +++ /dev/null @@ -1,135 +0,0 @@ -/** - * 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 { - 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 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) - } - } -} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/Server.kt b/pkl-server/src/main/kotlin/org/pkl/server/Server.kt index a0a71a8b5..8df1c4db7 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/Server.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/Server.kt @@ -15,6 +15,8 @@ */ package org.pkl.server +import java.io.InputStream +import java.io.OutputStream import java.net.URI import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService @@ -22,6 +24,8 @@ import java.util.concurrent.Executors import kotlin.random.Random import org.pkl.core.* import org.pkl.core.http.HttpClient +import org.pkl.core.messaging.* +import org.pkl.core.messaging.Messages.* import org.pkl.core.module.ModuleKeyFactories import org.pkl.core.module.ModuleKeyFactory import org.pkl.core.module.ModulePathResolver @@ -37,6 +41,17 @@ class Server(private val transport: MessageTransport) : AutoCloseable { // https://github.com/jano7/executor would be the perfect executor here private val executor: ExecutorService = Executors.newSingleThreadExecutor() + companion object { + fun stream(inputStream: InputStream, outputStream: OutputStream): Server = + Server( + MessageTransports.stream( + ServerMessagePackDecoder(inputStream), + ServerMessagePackEncoder(outputStream), + ::log + ) + ) + } + /** Starts listening to incoming messages */ fun start() { transport.start( @@ -71,13 +86,13 @@ class Server(private val transport: MessageTransport) : AutoCloseable { private fun handleCreateEvaluator(message: CreateEvaluatorRequest) { val evaluatorId = Random.Default.nextLong() - val baseResponse = CreateEvaluatorResponse(message.requestId, evaluatorId = null, error = null) + val baseResponse = CreateEvaluatorResponse(message.requestId, null, null) val evaluator = try { createEvaluator(message, evaluatorId) - } catch (e: ServerException) { - transport.send(baseResponse.copy(error = e.message)) + } catch (e: ProtocolException) { + transport.send(baseResponse.copy(error = e.message ?: "")) return } @@ -86,7 +101,7 @@ class Server(private val transport: MessageTransport) : AutoCloseable { } private fun handleEvaluate(msg: EvaluateRequest) { - val baseResponse = EvaluateResponse(msg.requestId, msg.evaluatorId, result = null, error = null) + val baseResponse = EvaluateResponse(msg.requestId, msg.evaluatorId, ByteArray(0), null) val evaluator = evaluators[msg.evaluatorId] if (evaluator == null) { @@ -103,7 +118,7 @@ class Server(private val transport: MessageTransport) : AutoCloseable { } catch (e: PklBugException) { transport.send(baseResponse.copy(error = e.toString())) } catch (e: PklException) { - transport.send(baseResponse.copy(error = e.message)) + transport.send(baseResponse.copy(error = e.message ?: "")) } } } diff --git a/pkl-server/src/main/kotlin/org/pkl/server/ServerMessagePackDecoder.kt b/pkl-server/src/main/kotlin/org/pkl/server/ServerMessagePackDecoder.kt new file mode 100644 index 000000000..82097b915 --- /dev/null +++ b/pkl-server/src/main/kotlin/org/pkl/server/ServerMessagePackDecoder.kt @@ -0,0 +1,129 @@ +/** + * 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.net.URI +import java.nio.file.Path +import java.time.Duration +import java.util.regex.Pattern +import org.msgpack.core.MessagePack +import org.msgpack.core.MessageUnpacker +import org.msgpack.value.Value +import org.pkl.core.evaluatorSettings.PklEvaluatorSettings +import org.pkl.core.messaging.BaseMessagePackDecoder +import org.pkl.core.messaging.Message +import org.pkl.core.packages.Checksums + +class ServerMessagePackDecoder(unpacker: MessageUnpacker) : BaseMessagePackDecoder(unpacker) { + + constructor(stream: InputStream) : this(MessagePack.newDefaultUnpacker(stream)) + + override fun decodeMessage(msgType: Message.Type, map: Map): Message? { + return when (msgType) { + Message.Type.CREATE_EVALUATOR_REQUEST -> + CreateEvaluatorRequest( + get(map, "requestId").asIntegerValue().asLong(), + unpackStringListOrNull(map, "allowedModules", Pattern::compile), + unpackStringListOrNull(map, "allowedResources", Pattern::compile), + unpackModuleReaderSpec(map), + unpackResourceReaderSpec(map), + unpackStringListOrNull(map, "modulePaths", Path::of), + unpackStringMapOrNull(map, "env"), + unpackStringMapOrNull(map, "properties"), + unpackLongOrNull(map, "timeoutSeconds", Duration::ofSeconds), + unpackStringOrNull(map, "rootDir", Path::of), + unpackStringOrNull(map, "cacheDir", Path::of), + unpackStringOrNull(map, "outputFormat"), + map.unpackProject(), + map.unpackHttp() + ) + Message.Type.CREATE_EVALUATOR_RESPONSE -> + CreateEvaluatorResponse( + unpackLong(map, "requestId"), + unpackLongOrNull(map, "evaluatorId"), + unpackStringOrNull(map, "error") + ) + Message.Type.CLOSE_EVALUATOR -> CloseEvaluator(unpackLong(map, "evaluatorId")) + Message.Type.EVALUATE_REQUEST -> + EvaluateRequest( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + URI(unpackString(map, "moduleUri")), + unpackStringOrNull(map, "moduleText"), + unpackStringOrNull(map, "expr") + ) + Message.Type.EVALUATE_RESPONSE -> + EvaluateResponse( + unpackLong(map, "requestId"), + unpackLong(map, "evaluatorId"), + unpackByteArray(map, "result"), + unpackStringOrNull(map, "error") + ) + Message.Type.LOG_MESSAGE -> + LogMessage( + unpackLong(map, "evaluatorId"), + unpackInt(map, "level"), + unpackString(map, "message"), + unpackString(map, "frameUri") + ) + else -> super.decodeMessage(msgType, map) + } + } + + private fun Map.unpackProject(): Project? { + val projMap = getNullable(this, "project")?.asMapValue()?.map() ?: return null + val projectFileUri = URI(unpackString(projMap, "projectFileUri")) + val dependencies = projMap.unpackDependencies("dependencies") + return Project(projectFileUri, null, dependencies) + } + + private fun Map.unpackHttp(): Http? { + val httpMap = getNullable(this, "http")?.asMapValue()?.map() ?: return null + val proxy = httpMap.unpackProxy() + val caCertificates = + getNullable(httpMap, "caCertificates")?.asBinaryValue()?.asByteArray() ?: ByteArray(0) + return Http(caCertificates, proxy) + } + + private fun Map.unpackProxy(): PklEvaluatorSettings.Proxy? { + val proxyMap = getNullable(this, "proxy")?.asMapValue()?.map() ?: return null + val address = unpackString(proxyMap, "address") + val noProxy = unpackStringListOrNull(proxyMap, "noProxy") + return PklEvaluatorSettings.Proxy.create(address, noProxy) + } + + private fun Map.unpackDependencies(name: String): Map { + val mapValue = get(this, name).asMapValue().map() + return mapValue.entries.associate { (key, value) -> + val dependencyName = key.asStringValue().asString() + val dependencyObj = value.asMapValue().map() + val type = unpackString(dependencyObj, "type") + val packageUri = URI(unpackString(dependencyObj, "packageUri")) + if (type == DependencyType.REMOTE.value) { + val checksums = + getNullable(dependencyObj, "checksums")?.asMapValue()?.map()?.let { obj -> + val sha256 = unpackString(obj, "sha256") + Checksums(sha256) + } + return@associate dependencyName to RemoteDependency(packageUri, checksums) + } + val dependencies = dependencyObj.unpackDependencies("dependencies") + val projectFileUri = unpackString(dependencyObj, "projectFileUri") + dependencyName to Project(URI(projectFileUri), packageUri, dependencies) + } + } +} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/ServerMessagePackEncoder.kt b/pkl-server/src/main/kotlin/org/pkl/server/ServerMessagePackEncoder.kt new file mode 100644 index 000000000..38502e8b6 --- /dev/null +++ b/pkl-server/src/main/kotlin/org/pkl/server/ServerMessagePackEncoder.kt @@ -0,0 +1,172 @@ +/** + * 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 java.nio.file.Path +import kotlin.io.path.pathString +import org.msgpack.core.MessagePack +import org.msgpack.core.MessagePacker +import org.pkl.core.messaging.BaseMessagePackEncoder +import org.pkl.core.messaging.Message +import org.pkl.core.packages.Checksums + +class ServerMessagePackEncoder(packer: MessagePacker) : BaseMessagePackEncoder(packer) { + + constructor(stream: OutputStream) : this(MessagePack.newDefaultPacker(stream)) + + private fun MessagePacker.packProject(project: Project) { + packMapHeader(2) + packKeyValue("projectFileUri", project.projectFileUri.toString()) + packString("dependencies") + packDependencies(project.dependencies) + } + + private fun MessagePacker.packHttp(http: Http) { + packMapHeader(1, http.proxy) + packKeyValue("caCertificates", http.caCertificates) + http.proxy?.let { proxy -> + packString("proxy") + packMapHeader(0, proxy.address, proxy.noProxy) + packKeyValue("address", proxy.address?.toString()) + packKeyValue("noProxy", proxy.noProxy) + } + } + + private fun MessagePacker.packDependencies(dependencies: Map) { + 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 encodeMessage(msg: Message) { + when (msg.type) { + Message.Type.CREATE_EVALUATOR_REQUEST -> { + msg as CreateEvaluatorRequest + packMapHeader( + 1, + msg.allowedModules, + msg.allowedResources, + msg.clientModuleReaders, + msg.clientResourceReaders, + msg.modulePaths, + msg.env, + msg.properties, + msg.timeout, + msg.rootDir, + msg.cacheDir, + msg.outputFormat, + msg.project, + msg.http + ) + packKeyValue("requestId", msg.requestId) + packKeyValue("allowedModules", msg.allowedModules?.map { it.toString() }) + packKeyValue("allowedResources", msg.allowedResources?.map { it.toString() }) + if (msg.clientModuleReaders != null) { + packer.packString("clientModuleReaders") + packer.packArrayHeader(msg.clientModuleReaders.size) + for (moduleReader in msg.clientModuleReaders) { + packModuleReaderSpec(moduleReader) + } + } + if (msg.clientResourceReaders != null) { + packer.packString("clientResourceReaders") + packer.packArrayHeader(msg.clientResourceReaders.size) + for (resourceReader in msg.clientResourceReaders) { + packResourceReaderSpec(resourceReader) + } + } + packKeyValue("modulePaths", msg.modulePaths, Path::toString) + 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) { + packer.packString("project") + packer.packProject(msg.project) + } + if (msg.http != null) { + packer.packString("http") + packer.packHttp(msg.http) + } + return + } + Message.Type.CREATE_EVALUATOR_RESPONSE -> { + msg as CreateEvaluatorResponse + packMapHeader(1, msg.evaluatorId, msg.error) + packKeyValue("requestId", msg.requestId) + packKeyValue("evaluatorId", msg.evaluatorId) + packKeyValue("error", msg.error) + } + Message.Type.CLOSE_EVALUATOR -> { + msg as CloseEvaluator + packer.packMapHeader(1) + packKeyValue("evaluatorId", msg.evaluatorId) + } + Message.Type.EVALUATE_REQUEST -> { + 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) + } + Message.Type.EVALUATE_RESPONSE -> { + 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) + } + Message.Type.LOG_MESSAGE -> { + msg as LogMessage + packer.packMapHeader(4) + packKeyValue("evaluatorId", msg.evaluatorId) + packKeyValue("level", msg.level) + packKeyValue("message", msg.message) + packKeyValue("frameUri", msg.frameUri) + } + else -> super.encodeMessage(msg) + } + } +} diff --git a/pkl-server/src/main/kotlin/org/pkl/server/Message.kt b/pkl-server/src/main/kotlin/org/pkl/server/ServerMessages.kt similarity index 52% rename from pkl-server/src/main/kotlin/org/pkl/server/Message.kt rename to pkl-server/src/main/kotlin/org/pkl/server/ServerMessages.kt index 3e2ce1e7f..eadbdeac1 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/Message.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/ServerMessages.kt @@ -21,97 +21,16 @@ import java.time.Duration import java.util.* import java.util.regex.Pattern import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.Proxy -import org.pkl.core.module.PathElement +import org.pkl.core.messaging.Message +import org.pkl.core.messaging.Messages.* 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?.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 -) : Dependency { - override val type: DependencyType = DependencyType.LOCAL -} - data class CreateEvaluatorRequest( - override val requestId: Long, + private val requestId: Long, val allowedModules: List?, val allowedResources: List?, val clientModuleReaders: List?, @@ -125,8 +44,11 @@ data class CreateEvaluatorRequest( val outputFormat: String?, val project: Project?, val http: Http? -) : ClientRequestMessage() { - override val type = MessageType.CREATE_EVALUATOR_REQUEST +) : Message.Client.Request { + + override fun getType(): Message.Type = Message.Type.CREATE_EVALUATOR_REQUEST + + override fun getRequestId(): Long = requestId // need to implement this manually because [Pattern.equals] returns false for two patterns // that have the same underlying pattern string. @@ -178,7 +100,7 @@ data class CreateEvaluatorRequest( data class Http( /** PEM-format CA certificates as raw bytes. */ - val caCertificates: ByteArray?, + val caCertificates: ByteArray, /** Proxy settings */ val proxy: Proxy? ) { @@ -186,83 +108,73 @@ data class Http( if (this === other) return true if (other !is Http) return false - if (caCertificates != null) { - if (other.caCertificates == null) return false - if (!caCertificates.contentEquals(other.caCertificates)) return false - } else if (other.caCertificates != null) return false - return Objects.equals(proxy, other.proxy) + return caCertificates.contentEquals(other.caCertificates) && Objects.equals(proxy, other.proxy) } override fun hashCode(): Int { - var result = caCertificates?.contentHashCode() ?: 0 + var result = caCertificates.contentHashCode() result = 31 * result + (proxy?.hashCode() ?: 0) return result } } -data class CreateEvaluatorResponse( - override val requestId: Long, - val evaluatorId: Long?, - val error: String?, -) : ServerResponseMessage() { - override val type - get() = MessageType.CREATE_EVALUATOR_RESPONSE +enum class DependencyType(val value: String) { + LOCAL("local"), + REMOTE("remote") } -data class ListResourcesRequest(override val requestId: Long, val evaluatorId: Long, val uri: URI) : - ServerRequestMessage() { - override val type: MessageType - get() = MessageType.LIST_RESOURCES_REQUEST +sealed interface Dependency { + val type: DependencyType + val packageUri: URI? } -data class ListResourcesResponse( - override val requestId: Long, - val evaluatorId: Long, - val pathElements: List?, - val error: String? -) : ClientResponseMessage() { - override val type: MessageType - get() = MessageType.LIST_RESOURCES_RESPONSE +data class RemoteDependency(override val packageUri: URI, val checksums: Checksums?) : Dependency { + override val type: DependencyType = DependencyType.REMOTE } -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 Project( + val projectFileUri: URI, + override val packageUri: URI?, + val dependencies: Map +) : Dependency { + override val type: DependencyType = DependencyType.LOCAL } -data class ListModulesResponse( - override val requestId: Long, - val evaluatorId: Long, - val pathElements: List?, - val error: String? -) : ClientResponseMessage() { - override val type: MessageType - get() = MessageType.LIST_MODULES_RESPONSE +data class CreateEvaluatorResponse( + private val requestId: Long, + val evaluatorId: Long?, + val error: String?, +) : Message.Server.Response { + override fun getType(): Message.Type = Message.Type.CREATE_EVALUATOR_RESPONSE + + override fun getRequestId(): Long = requestId } -data class CloseEvaluator(val evaluatorId: Long) : ClientOneWayMessage() { - override val type = MessageType.CLOSE_EVALUATOR +data class CloseEvaluator(val evaluatorId: Long) : Message.Client.OneWay { + override fun getType(): Message.Type = Message.Type.CLOSE_EVALUATOR } data class EvaluateRequest( - override val requestId: Long, + private val requestId: Long, val evaluatorId: Long, val moduleUri: URI, val moduleText: String?, val expr: String? -) : ClientRequestMessage() { - override val type = MessageType.EVALUATE_REQUEST +) : Message.Client.Request { + override fun getType(): Message.Type = Message.Type.EVALUATE_REQUEST + + override fun getRequestId(): Long = requestId } data class EvaluateResponse( - override val requestId: Long, + private val requestId: Long, val evaluatorId: Long, - val result: ByteArray?, + val result: ByteArray, val error: String? -) : ServerResponseMessage() { - override val type - get() = MessageType.EVALUATE_RESPONSE +) : Message.Server.Response { + override fun getType(): Message.Type = Message.Type.EVALUATE_RESPONSE + + override fun getRequestId(): Long = requestId // override to use [ByteArray.contentEquals] @Suppress("DuplicatedCode") @@ -291,58 +203,6 @@ data class LogMessage( 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 +) : Message.Server.OneWay { + override fun getType(): Message.Type = Message.Type.LOG_MESSAGE } diff --git a/pkl-server/src/main/kotlin/org/pkl/server/Utils.kt b/pkl-server/src/main/kotlin/org/pkl/server/Utils.kt index c7b592479..8981b4033 100644 --- a/pkl-server/src/main/kotlin/org/pkl/server/Utils.kt +++ b/pkl-server/src/main/kotlin/org/pkl/server/Utils.kt @@ -20,6 +20,7 @@ import java.util.concurrent.ExecutionException import java.util.concurrent.Future import org.msgpack.core.MessageBufferPacker import org.msgpack.core.MessagePack +import org.pkl.core.messaging.Message internal fun log(msg: String) { if (System.getenv("PKL_DEBUG") == "1") { @@ -41,7 +42,7 @@ internal val threadLocalBufferPacker: ThreadLocal = private val threadLocalEncoder: ThreadLocal<(Message) -> ByteArray> = ThreadLocal.withInitial { val packer = threadLocalBufferPacker.get() - val encoder = MessageEncoders.into(packer); + val encoder = ServerMessagePackEncoder(packer); { message: Message -> packer.clear() encoder.encode(message) diff --git a/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt index f7a78ce31..7b80ed798 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/AbstractServerTest.kt @@ -29,13 +29,14 @@ 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.messaging.Messages.* import org.pkl.core.module.PathElement abstract class AbstractServerTest { companion object { /** Set to `true` to bypass messagepack serialization when running [JvmServerTest]. */ - const val USE_DIRECT_TRANSPORT = false + const val USE_DIRECT_TRANSPORT = true val executor: ExecutorService = if (USE_DIRECT_TRANSPORT) { @@ -55,8 +56,8 @@ abstract class AbstractServerTest { @Test fun `create and close evaluator`() { - val evaluatorId = client.sendCreateEvaluatorRequest(requestId = 123) - client.send(CloseEvaluator(evaluatorId = evaluatorId)) + val evaluatorId = client.sendCreateEvaluatorRequest(123) + client.send(CloseEvaluator(evaluatorId)) } @Test @@ -66,17 +67,16 @@ abstract class AbstractServerTest { client.send( EvaluateRequest( - requestId = requestId, - evaluatorId = evaluatorId, - moduleUri = URI("repl:text"), - moduleText = - """ + requestId, + evaluatorId, + URI("repl:text"), + """ foo { bar = "bar" } """ - .trimIndent(), - expr = null + .trimIndent(), + null ) ) @@ -96,15 +96,14 @@ abstract class AbstractServerTest { client.send( EvaluateRequest( - requestId = 1, - evaluatorId = evaluatorId, - moduleUri = URI("repl:text"), - moduleText = - """ + 1, + evaluatorId, + URI("repl:text"), + """ foo = trace(1 + 2 + 3) """ - .trimIndent(), - expr = null + .trimIndent(), + null ) ) @@ -121,18 +120,17 @@ abstract class AbstractServerTest { client.send( EvaluateRequest( - requestId = 1, - evaluatorId = evaluatorId, - moduleUri = URI("repl:text"), - moduleText = - """ + 1, + evaluatorId, + URI("repl:text"), + """ @Deprecated { message = "use bar instead" } function foo() = 5 result = foo() """ - .trimIndent(), - expr = null + .trimIndent(), + null ) ) @@ -145,17 +143,16 @@ abstract class AbstractServerTest { @Test fun `read resource`() { - val reader = - ResourceReaderSpec(scheme = "bahumbug", hasHierarchicalUris = true, isGlobbable = false) + val reader = ResourceReaderSpec("bahumbug", true, 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" + 1, + evaluatorId, + URI("repl:text"), + """res = read("bahumbug:/foo.pkl").text""", + "res" ) ) @@ -165,10 +162,10 @@ abstract class AbstractServerTest { client.send( ReadResourceResponse( - requestId = readResourceMsg.requestId, - evaluatorId = evaluatorId, - contents = "my bahumbug".toByteArray(), - error = null + readResourceMsg.requestId, + evaluatorId, + "my bahumbug".toByteArray(), + null ) ) @@ -182,17 +179,16 @@ abstract class AbstractServerTest { @Test fun `read resource error`() { - val reader = - ResourceReaderSpec(scheme = "bahumbug", hasHierarchicalUris = true, isGlobbable = false) + val reader = ResourceReaderSpec("bahumbug", true, 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" + 1, + evaluatorId, + URI("repl:text"), + """res = read("bahumbug:/foo.txt").text""", + "res" ) ) @@ -200,10 +196,10 @@ abstract class AbstractServerTest { client.send( ReadResourceResponse( - requestId = readResourceMsg.requestId, - evaluatorId = evaluatorId, - contents = null, - error = "cannot read my bahumbug" + readResourceMsg.requestId, + evaluatorId, + ByteArray(0), + "cannot read my bahumbug" ) ) @@ -214,46 +210,44 @@ abstract class AbstractServerTest { @Test fun `glob resource`() { - val reader = ResourceReaderSpec(scheme = "bird", hasHierarchicalUris = true, isGlobbable = true) + val reader = ResourceReaderSpec("bird", true, true) val evaluatorId = client.sendCreateEvaluatorRequest(resourceReaders = listOf(reader)) client.send( EvaluateRequest( - requestId = 1, - evaluatorId = evaluatorId, - moduleUri = URI("repl:text"), - moduleText = - """ + 1, + evaluatorId, + URI("repl:text"), + """ res = read*("bird:/**.txt").keys """ - .trimIndent(), - expr = "res" + .trimIndent(), + "res" ) ) val listResourcesRequest = client.receive() 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 + listResourcesRequest.requestId, + listResourcesRequest.evaluatorId, + listOf(PathElement("foo.txt", false), PathElement("subdir", true)), + null ) ) val listResourcesRequest2 = client.receive() assertThat(listResourcesRequest2.uri.toString()).isEqualTo("bird:/subdir/") client.send( ListResourcesResponse( - requestId = listResourcesRequest2.requestId, - evaluatorId = listResourcesRequest2.evaluatorId, - pathElements = - listOf( - PathElement("bar.txt", false), - ), - error = null + listResourcesRequest2.requestId, + listResourcesRequest2.evaluatorId, + listOf( + PathElement("bar.txt", false), + ), + null ) ) val evaluateResponse = client.receive() - assertThat(evaluateResponse.result!!.debugYaml) + assertThat(evaluateResponse.result.debugYaml) .isEqualTo( """ - 6 @@ -267,32 +261,31 @@ abstract class AbstractServerTest { @Test fun `glob resources -- null pathElements and null error`() { - val reader = ResourceReaderSpec(scheme = "bird", hasHierarchicalUris = true, isGlobbable = true) + val reader = ResourceReaderSpec("bird", true, true) val evaluatorId = client.sendCreateEvaluatorRequest(resourceReaders = listOf(reader)) client.send( EvaluateRequest( - requestId = 1, - evaluatorId = evaluatorId, - moduleUri = URI("repl:text"), - moduleText = - """ + 1, + evaluatorId, + URI("repl:text"), + """ res = read*("bird:/**.txt").keys """ - .trimIndent(), - expr = "res" + .trimIndent(), + "res" ) ) val listResourcesRequest = client.receive() client.send( ListResourcesResponse( - requestId = listResourcesRequest.requestId, - evaluatorId = listResourcesRequest.evaluatorId, - pathElements = null, - error = null + listResourcesRequest.requestId, + listResourcesRequest.evaluatorId, + null, + null ) ) val evaluateResponse = client.receive() - assertThat(evaluateResponse.result!!.debugYaml) + assertThat(evaluateResponse.result.debugYaml) .isEqualTo( """ - 6 @@ -304,29 +297,28 @@ abstract class AbstractServerTest { @Test fun `glob resource error`() { - val reader = ResourceReaderSpec(scheme = "bird", hasHierarchicalUris = true, isGlobbable = true) + val reader = ResourceReaderSpec("bird", true, true) val evaluatorId = client.sendCreateEvaluatorRequest(resourceReaders = listOf(reader)) client.send( EvaluateRequest( - requestId = 1, - evaluatorId = evaluatorId, - moduleUri = URI("repl:text"), - moduleText = - """ + 1, + evaluatorId, + URI("repl:text"), + """ res = read*("bird:/**.txt").keys """ - .trimIndent(), - expr = "res" + .trimIndent(), + "res" ) ) val listResourcesRequest = client.receive() assertThat(listResourcesRequest.uri.toString()).isEqualTo("bird:/") client.send( ListResourcesResponse( - requestId = listResourcesRequest.requestId, - evaluatorId = listResourcesRequest.evaluatorId, - pathElements = null, - error = "didnt work" + listResourcesRequest.requestId, + listResourcesRequest.evaluatorId, + null, + "didnt work" ) ) val evaluateResponse = client.receive() @@ -352,22 +344,16 @@ abstract class AbstractServerTest { @Test fun `read module`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = true, - isGlobbable = false - ) + val reader = ModuleReaderSpec("bird", true, true, 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" + 1, + evaluatorId, + URI("repl:text"), + """res = import("bird:/pigeon.pkl").value""", + "res" ) ) @@ -375,14 +361,7 @@ abstract class AbstractServerTest { 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 - ) - ) + client.send(ReadModuleResponse(readModuleMsg.requestId, evaluatorId, "value = 5", null)) val evaluateResponse = client.receive() assertThat(evaluateResponse.error).isNull() @@ -393,22 +372,16 @@ abstract class AbstractServerTest { @Test fun `read module error`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = true, - isGlobbable = false - ) + val reader = ModuleReaderSpec("bird", true, true, 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" + 1, + evaluatorId, + URI("repl:text"), + """res = import("bird:/pigeon.pkl").value""", + "res" ) ) @@ -417,12 +390,7 @@ abstract class AbstractServerTest { assertThat(readModuleMsg.evaluatorId).isEqualTo(evaluatorId) client.send( - ReadModuleResponse( - requestId = readModuleMsg.requestId, - evaluatorId = evaluatorId, - contents = null, - error = "Don't know where Pigeon is" - ) + ReadModuleResponse(readModuleMsg.requestId, evaluatorId, null, "Don't know where Pigeon is") ) val evaluateResponse = client.receive() @@ -431,22 +399,16 @@ abstract class AbstractServerTest { @Test fun `glob module`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = true, - isGlobbable = true - ) + val reader = ModuleReaderSpec("bird", true, true, 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" + 1, + evaluatorId, + URI("repl:text"), + """res = import*("bird:/**.pkl").keys""", + "res" ) ) @@ -455,15 +417,14 @@ abstract class AbstractServerTest { 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 + listModulesMsg.requestId, + evaluatorId, + listOf( + PathElement("birds", true), + PathElement("majesticBirds", true), + PathElement("Person.pkl", false) + ), + null ) ) val listModulesMsg2 = client.receive() @@ -471,14 +432,13 @@ abstract class AbstractServerTest { 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 + listModulesMsg2.requestId, + listModulesMsg2.evaluatorId, + listOf( + PathElement("pigeon.pkl", false), + PathElement("parrot.pkl", false), + ), + null ) ) val listModulesMsg3 = client.receive() @@ -486,19 +446,18 @@ abstract class AbstractServerTest { 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 + listModulesMsg3.requestId, + listModulesMsg3.evaluatorId, + listOf( + PathElement("barnOwl.pkl", false), + PathElement("elfOwl.pkl", false), + ), + null ) ) val evaluateResponse = client.receive() - assertThat(evaluateResponse.result!!.debugRendering) + assertThat(evaluateResponse.result.debugRendering) .isEqualTo( """ - 6 @@ -515,36 +474,23 @@ abstract class AbstractServerTest { @Test fun `glob module -- null pathElements and null error`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = true, - isGlobbable = true - ) + val reader = ModuleReaderSpec("bird", true, true, 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" + 1, + evaluatorId, + URI("repl:text"), + """res = import*("bird:/**.pkl").keys""", + "res" ) ) val listModulesMsg = client.receive() - client.send( - ListModulesResponse( - requestId = listModulesMsg.requestId, - evaluatorId = evaluatorId, - pathElements = null, - error = null - ) - ) + client.send(ListModulesResponse(listModulesMsg.requestId, evaluatorId, null, null)) val evaluateResponse = client.receive() - assertThat(evaluateResponse.result!!.debugRendering) + assertThat(evaluateResponse.result.debugRendering) .isEqualTo( """ - 6 @@ -556,36 +502,23 @@ abstract class AbstractServerTest { @Test fun `glob module error`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = true, - isGlobbable = true - ) + val reader = ModuleReaderSpec("bird", true, true, 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" + 1, + evaluatorId, + URI("repl:text"), + """res = import*("bird:/**.pkl").keys""", + "res" ) ) val listModulesMsg = client.receive() assertThat(listModulesMsg.uri.scheme).isEqualTo("bird") assertThat(listModulesMsg.uri.path).isEqualTo("/") - client.send( - ListModulesResponse( - requestId = listModulesMsg.requestId, - evaluatorId = evaluatorId, - pathElements = null, - error = "nope" - ) - ) + client.send(ListModulesResponse(listModulesMsg.requestId, evaluatorId, null, "nope")) val evaluateResponse = client.receive() assertThat(evaluateResponse.error) .isEqualTo( @@ -619,19 +552,13 @@ abstract class AbstractServerTest { 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" - ) + EvaluateRequest(1, evaluatorId, URI("modulepath:/dir1/module.pkl"), null, "output.text") ) val response = client.receive() assertThat(response.error).isNull() val tripleQuote = "\"\"\"" - assertThat(response.result!!.debugYaml) + assertThat(response.result.debugYaml) .isEqualTo( """ | @@ -661,38 +588,31 @@ abstract class AbstractServerTest { @Test fun `import triple-dot path`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = true, - isGlobbable = true - ) + val reader = ModuleReaderSpec("bird", true, true, true) val evaluatorId = client.sendCreateEvaluatorRequest(moduleReaders = listOf(reader)) client.send( EvaluateRequest( - requestId = 1, - evaluatorId = evaluatorId, - moduleUri = URI("bird:/foo/bar/baz.pkl"), - moduleText = - """ + 1, + evaluatorId, + URI("bird:/foo/bar/baz.pkl"), + """ import ".../buz.pkl" res = buz.res """ - .trimIndent(), - expr = "res" + .trimIndent(), + "res" ) ) val readModuleRequest = client.receive() assertThat(readModuleRequest.uri).isEqualTo(URI("bird:/foo/buz.pkl")) client.send( ReadModuleResponse( - requestId = readModuleRequest.requestId, - evaluatorId = readModuleRequest.evaluatorId, - contents = null, - error = "not here" + readModuleRequest.requestId, + readModuleRequest.evaluatorId, + null, + "not here" ) ) @@ -700,30 +620,22 @@ abstract class AbstractServerTest { assertThat(readModuleRequest2.uri).isEqualTo(URI("bird:/buz.pkl")) client.send( ReadModuleResponse( - requestId = readModuleRequest2.requestId, - evaluatorId = readModuleRequest2.evaluatorId, - contents = "res = 1", - error = null + readModuleRequest2.requestId, + readModuleRequest2.evaluatorId, + "res = 1", + null ) ) val evaluatorResponse = client.receive() - assertThat(evaluatorResponse.result!!.debugYaml).isEqualTo("1") + 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" - ) - ) + client.send(EvaluateRequest(1, evaluatorId, URI("repl:text"), """foo = 1""", "foo as String")) val evaluateResponse = client.receive() assertThat(evaluateResponse.requestId).isEqualTo(1) @@ -732,22 +644,16 @@ abstract class AbstractServerTest { @Test fun `evaluate client-provided module reader`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = false, - isGlobbable = false - ) + val reader = ModuleReaderSpec("bird", true, false, 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", + 1, + evaluatorId, + URI("bird:/pigeon.pkl"), + null, + "output.text", ) ) @@ -756,22 +662,21 @@ abstract class AbstractServerTest { client.send( ReadModuleResponse( - requestId = readModuleRequest.requestId, - evaluatorId = evaluatorId, - contents = - """ + readModuleRequest.requestId, + evaluatorId, + """ firstName = "Pigeon" lastName = "Bird" fullName = firstName + " " + lastName """ - .trimIndent(), - error = null + .trimIndent(), + null ) ) val evaluateResponse = client.receive() assertThat(evaluateResponse.result).isNotNull - assertThat(evaluateResponse.result!!.debugYaml) + assertThat(evaluateResponse.result.debugYaml) .isEqualTo( """ | @@ -785,33 +690,19 @@ abstract class AbstractServerTest { @Test fun `concurrent evaluations`() { - val reader = - ModuleReaderSpec( - scheme = "bird", - hasHierarchicalUris = true, - isLocal = false, - isGlobbable = false - ) + val reader = ModuleReaderSpec("bird", true, false, 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", + 1, + evaluatorId, + URI("bird:/pigeon.pkl"), + null, + "output.text", ) ) - client.send( - EvaluateRequest( - requestId = 2, - evaluatorId = evaluatorId, - moduleUri = URI("bird:/parrot.pkl"), - moduleText = null, - expr = "output.text" - ) - ) + client.send(EvaluateRequest(2, evaluatorId, URI("bird:/parrot.pkl"), null, "output.text")) // evaluation is single-threaded; `parrot.pkl` gets evaluated after `pigeon.pkl` completes. val response11 = client.receive() @@ -821,20 +712,19 @@ abstract class AbstractServerTest { ReadModuleResponse( response11.requestId, evaluatorId, - contents = - """ + """ firstName = "Pigeon" lastName = "Bird" fullName = firstName + " " + lastName """ - .trimIndent(), - error = null + .trimIndent(), + null ) ) val response12 = client.receive() assertThat(response12.result).isNotNull - assertThat(response12.result!!.debugYaml) + assertThat(response12.result.debugYaml) .isEqualTo( """ | @@ -852,20 +742,19 @@ abstract class AbstractServerTest { ReadModuleResponse( response21.requestId, evaluatorId, - contents = - """ + """ firstName = "Parrot" lastName = "Bird" fullName = firstName + " " + lastName """ - .trimIndent(), - error = null + .trimIndent(), + null ) ) val response22 = client.receive() assertThat(response22.result).isNotNull - assertThat(response22.result!!.debugYaml) + assertThat(response22.result.debugYaml) .isEqualTo( """ | @@ -959,34 +848,32 @@ abstract class AbstractServerTest { cacheDir = cacheDir, project = Project( - projectFileUri = projectDir.resolve("PklProject").toUri(), - packageUri = null, - dependencies = - mapOf( - "birds" to - RemoteDependency(packageUri = URI("package://localhost:0/birds@0.5.0"), null), - "lib" to - Project( - projectFileUri = libDir.toUri().resolve("PklProject"), - packageUri = URI("package://localhost:0/lib@5.0.0"), - dependencies = emptyMap() - ) - ) + projectDir.resolve("PklProject").toUri(), + null, + mapOf( + "birds" to RemoteDependency(URI("package://localhost:0/birds@0.5.0"), null), + "lib" to + Project( + libDir.toUri().resolve("PklProject"), + URI("package://localhost:0/lib@5.0.0"), + emptyMap() + ) + ) ) ) client.send( EvaluateRequest( - requestId = 1, - evaluatorId = evaluatorId, - moduleUri = module.toUri(), - moduleText = null, - expr = "output.text", + 1, + evaluatorId, + module.toUri(), + null, + "output.text", ) ) val resp2 = client.receive() assertThat(resp2.error).isNull() assertThat(resp2.result).isNotNull() - assertThat(resp2.result!!.debugRendering.trim()) + assertThat(resp2.result.debugRendering.trim()) .isEqualTo( """ | @@ -1018,20 +905,20 @@ abstract class AbstractServerTest { ): 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, - http = http + 123, + listOf(Pattern.compile(".*")), + listOf(Pattern.compile(".*")), + moduleReaders, + resourceReaders, + modulePaths, + mapOf(), + mapOf(), + null, + null, + cacheDir, + null, + project, + http ) send(message) diff --git a/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorSnippetTests.kt b/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorSnippetTests.kt index b530d888f..094a1bb12 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorSnippetTests.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/BinaryEvaluatorSnippetTests.kt @@ -66,3 +66,6 @@ class BinaryEvaluatorSnippetTestEngine : InputOutputTestEngine() { return true to bytes.debugRendering.stripFilePaths() } } + +val ByteArray.debugRendering + get() = MessagePackDebugRenderer(this).output diff --git a/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt index cfdc8b322..86ded6ebe 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/JvmServerTest.kt @@ -19,17 +19,31 @@ import java.io.PipedInputStream import java.io.PipedOutputStream import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach +import org.pkl.core.messaging.MessageTransport +import org.pkl.core.messaging.MessageTransports +import org.pkl.core.util.Pair class JvmServerTest : AbstractServerTest() { private val transports: Pair = run { if (USE_DIRECT_TRANSPORT) { - MessageTransports.direct() + MessageTransports.direct(::log) } else { val in1 = PipedInputStream() val out1 = PipedOutputStream(in1) val in2 = PipedInputStream() val out2 = PipedOutputStream(in2) - MessageTransports.stream(in1, out2) to MessageTransports.stream(in2, out1) + Pair.of( + MessageTransports.stream( + ServerMessagePackDecoder(in1), + ServerMessagePackEncoder(out2), + ::log + ), + MessageTransports.stream( + ServerMessagePackDecoder(in2), + ServerMessagePackEncoder(out1), + ::log + ) + ) } } diff --git a/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt index e8c553a3a..d1c190155 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/MessagePackCodecTest.kt @@ -23,10 +23,12 @@ 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.evaluatorSettings.PklEvaluatorSettings -import org.pkl.core.module.PathElement +import org.pkl.core.messaging.Message +import org.pkl.core.messaging.MessageDecoder +import org.pkl.core.messaging.MessageEncoder +import org.pkl.core.messaging.Messages.* import org.pkl.core.packages.Checksums class MessagePackCodecTest { @@ -36,8 +38,8 @@ class MessagePackCodecTest { init { val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) - encoder = MessagePackEncoder(MessagePack.newDefaultPacker(outputStream)) - decoder = MessagePackDecoder(MessagePack.newDefaultUnpacker(inputStream)) + encoder = ServerMessagePackEncoder(MessagePack.newDefaultPacker(outputStream)) + decoder = ServerMessagePackDecoder(MessagePack.newDefaultUnpacker(inputStream)) } private fun roundtrip(message: Message) { @@ -50,233 +52,78 @@ class MessagePackCodecTest { fun `round-trip CreateEvaluatorRequest`() { val resourceReader1 = ResourceReaderSpec( - scheme = "resourceReader1", - hasHierarchicalUris = true, - isGlobbable = true, + "resourceReader1", + true, + true, ) val resourceReader2 = ResourceReaderSpec( - scheme = "resourceReader2", - hasHierarchicalUris = true, - isGlobbable = false, + "resourceReader2", + true, + false, ) - val moduleReader1 = - ModuleReaderSpec( - scheme = "moduleReader1", - hasHierarchicalUris = true, - isGlobbable = true, - isLocal = true - ) - val moduleReader2 = - ModuleReaderSpec( - scheme = "moduleReader2", - hasHierarchicalUris = true, - isGlobbable = false, - isLocal = false - ) - @Suppress("HttpUrlsUsage") + val moduleReader1 = ModuleReaderSpec("moduleReader1", true, true, true) + val moduleReader2 = ModuleReaderSpec("moduleReader2", true, false, 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:0/foo@1.0.0"), - dependencies = - mapOf( - "bar" to - Project( - projectFileUri = URI("file:///bar"), - packageUri = URI("package://localhost:0/bar@1.1.0"), - dependencies = emptyMap() - ) - ) - ), - "baz" to - RemoteDependency(URI("package://localhost:0/baz@1.1.0"), Checksums("abc123")) - ) - ), - http = - Http( - proxy = PklEvaluatorSettings.Proxy(URI("http://foo.com:1234"), listOf("bar", "baz")), - caCertificates = byteArrayOf(1, 2, 3, 4) + 123, + listOf("pkl", "file", "https").map(Pattern::compile), + listOf("pkl", "file", "https", "resourceReader1", "resourceReader2").map(Pattern::compile), + listOf(moduleReader1, moduleReader2), + listOf(resourceReader1, resourceReader2), + listOf(Path.of("some/path.zip"), Path.of("other/path.zip")), + mapOf("KEY1" to "VALUE1", "KEY2" to "VALUE2"), + mapOf("property1" to "value1", "property2" to "value2"), + Duration.ofSeconds(10), + Path.of("root/dir"), + Path.of("cache/dir"), + "pcf", + Project( + URI("file:///dummy/PklProject"), + null, + mapOf( + "foo" to + Project( + URI("file:///foo"), + URI("package://localhost:0/foo@1.0.0"), + mapOf( + "bar" to + Project(URI("file:///bar"), URI("package://localhost:0/bar@1.1.0"), emptyMap()) + ) + ), + "baz" to RemoteDependency(URI("package://localhost:0/baz@1.1.0"), Checksums("abc123")) ) + ), + Http( + byteArrayOf(1, 2, 3, 4), + PklEvaluatorSettings.Proxy(URI("http://foo.com:1234"), listOf("bar", "baz")) + ) ) ) } @Test fun `round-trip CreateEvaluatorResponse`() { - roundtrip(CreateEvaluatorResponse(requestId = 123, evaluatorId = 456, error = null)) + roundtrip(CreateEvaluatorResponse(123, 456, null)) } @Test fun `round-trip CloseEvaluator`() { - roundtrip(CloseEvaluator(evaluatorId = 123)) + roundtrip(CloseEvaluator(123)) } @Test fun `round-trip EvaluateRequest`() { - roundtrip( - EvaluateRequest( - requestId = 123, - evaluatorId = 456, - moduleUri = URI("some/module.pkl"), - moduleText = null, - expr = "some + expression" - ) - ) + roundtrip(EvaluateRequest(123, 456, URI("some/module.pkl"), null, "some + expression")) } @Test fun `round-trip EvaluateResponse`() { - roundtrip( - EvaluateResponse( - requestId = 123, - evaluatorId = 456, - result = byteArrayOf(1, 2, 3, 4, 5), - error = null - ) - ) + roundtrip(EvaluateResponse(123, 456, byteArrayOf(1, 2, 3, 4, 5), 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 { 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 { decoder.decode() } - assertThat(exception).hasMessage("Malformed message header.") - assertThat(exception).hasRootCauseMessage("Expected Array, but got Integer (02)") + roundtrip(LogMessage(123, 0, "Hello, world!", "file:///some/module.pkl")) } } diff --git a/pkl-server/src/test/kotlin/org/pkl/server/MessagePackDebugRenderer.kt b/pkl-server/src/test/kotlin/org/pkl/server/MessagePackDebugRenderer.kt index c3b90038c..dac6e571e 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/MessagePackDebugRenderer.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/MessagePackDebugRenderer.kt @@ -105,6 +105,3 @@ class MessagePackDebugRenderer(bytes: ByteArray) { sb.toString().removePrefix("\n") } } - -val ByteArray.debugRendering - get() = MessagePackDebugRenderer(this).output diff --git a/pkl-server/src/test/kotlin/org/pkl/server/NativeServerTest.kt b/pkl-server/src/test/kotlin/org/pkl/server/NativeServerTest.kt index c78d10d97..c72b9eb6b 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/NativeServerTest.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/NativeServerTest.kt @@ -18,6 +18,7 @@ package org.pkl.server import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.pkl.commons.test.PklExecutablePaths +import org.pkl.core.messaging.MessageTransports class NativeServerTest : AbstractServerTest() { private lateinit var server: Process @@ -27,7 +28,14 @@ class NativeServerTest : AbstractServerTest() { fun beforeEach() { val executable = PklExecutablePaths.firstExisting.toString() server = ProcessBuilder(executable, "server").start() - client = TestTransport(MessageTransports.stream(server.inputStream, server.outputStream)) + client = + TestTransport( + MessageTransports.stream( + ServerMessagePackDecoder(server.inputStream), + ServerMessagePackEncoder(server.outputStream) + ) { _ -> + } + ) executor.execute { client.start() } } diff --git a/pkl-server/src/test/kotlin/org/pkl/server/TestTransport.kt b/pkl-server/src/test/kotlin/org/pkl/server/TestTransport.kt index a916afaed..91a0eb12c 100644 --- a/pkl-server/src/test/kotlin/org/pkl/server/TestTransport.kt +++ b/pkl-server/src/test/kotlin/org/pkl/server/TestTransport.kt @@ -17,7 +17,9 @@ package org.pkl.server import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.BlockingQueue -import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.* +import org.pkl.core.messaging.Message +import org.pkl.core.messaging.MessageTransport class TestTransport(private val delegate: MessageTransport) : AutoCloseable { val incomingMessages: BlockingQueue = ArrayBlockingQueue(10) @@ -30,15 +32,15 @@ class TestTransport(private val delegate: MessageTransport) : AutoCloseable { delegate.close() } - fun send(message: ClientOneWayMessage) { + fun send(message: Message.Client.OneWay) { delegate.send(message) } - fun send(message: ClientRequestMessage) { + fun send(message: Message.Client.Request) { delegate.send(message) { incomingMessages.put(it) } } - fun send(message: ClientResponseMessage) { + fun send(message: Message.Client.Response) { delegate.send(message) }