diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61386bf7..d9d53e47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'adopt' diff --git a/e2e-tests/README.md b/e2e-tests/README.md index 4c7c8fcf..fd39161a 100644 --- a/e2e-tests/README.md +++ b/e2e-tests/README.md @@ -4,7 +4,7 @@ End-to-end tests for the Restate runtime. Tests are deployed as Docker container ## Requirements -- JVM >= 21 +- JDK 25 (the Gradle toolchain can provision it) - Docker ## Running locally @@ -27,6 +27,31 @@ Run a single test class within a suite: ./gradlew :e2e-tests:run --args='run --test-suite=default --test-name=IngressTest' ``` +## OpenAPI contract checks + +Run the OpenAPI checks against a locally built candidate image (replace the image tag as needed): + +```shell +./gradlew :e2e-tests:run --args='run --test-suite=default --test-name=OpenAPITest --restate-container-image=localhost/restatedev/restate:openapi' +``` + +Omit `--restate-container-image` to test `ghcr.io/restatedev/restate:main`. +Reports and generated files are saved under `/default/OpenAPITest/openapi/`. + +### Updating the Admin baseline + +To update [`infra/src/main/openapi/admin.json`](../infra/src/main/openapi/admin.json), +run from the **Restate** checkout with this repository at `../e2e`: + +```shell +set -o pipefail +cargo xtask generate-rest-api-doc | jq . > ../e2e/infra/src/main/openapi/admin.json +``` + +Review the diff and rerun the checks before committing the updated baseline. The shared Admin +client regenerates automatically. Updating the baseline accepts those changes for future +compatibility comparisons. + ## Available test suites | Suite | Description | diff --git a/e2e-tests/build.gradle.kts b/e2e-tests/build.gradle.kts index 852fe7ba..ecc4f1a2 100644 --- a/e2e-tests/build.gradle.kts +++ b/e2e-tests/build.gradle.kts @@ -48,7 +48,16 @@ dependencies { // Used at runtime by some e2e tests to interact with Kafka implementation("org.apache.kafka:kafka-clients:3.5.0") // Runtime dep for e2e-specific functionality - implementation("org.openapitools:openapi-generator:7.21.0") + implementation(libs.openapi.generator) + + // javac compiles the candidate's generated client during the OpenAPI test. + runtimeOnly(libs.tomcat.annotations) + runtimeOnly(libs.google.findbugs.jsr305) +} + +// Reuse the shared client's checked-in specification as the compatibility baseline. +tasks.processResources { + from(project(":infra").file("src/main/openapi/admin.json")) { into("openapi/baseline") } } allOpen { @@ -60,6 +69,11 @@ allOpen { application { mainClass = "dev.restate.sdktesting.MainKt" } spotless { + java { + target("src/main/resources/openapi/*.java") + googleJavaFormat() + licenseHeaderFile("$rootDir/config/license-header") + } kotlin { ktfmt() licenseHeaderFile("$rootDir/config/license-header") diff --git a/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/ForwardCompatibilityTest.kt b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/ForwardCompatibilityTest.kt index 41f64c7e..b2aa60ba 100644 --- a/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/ForwardCompatibilityTest.kt +++ b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/ForwardCompatibilityTest.kt @@ -8,8 +8,7 @@ // https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE package dev.restate.sdktesting.tests -import dev.restate.admin.api.DeploymentApi -import dev.restate.admin.client.ApiClient +import com.fasterxml.jackson.databind.ObjectMapper import dev.restate.client.Client import dev.restate.client.SendResponse import dev.restate.client.kotlin.* @@ -32,6 +31,9 @@ import dev.restate.sdk.kotlin.state import dev.restate.sdktesting.infra.* import dev.restate.serde.kotlinx.jsonSerde import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse.BodyHandlers import java.nio.file.Files import java.util.UUID import java.util.concurrent.atomic.AtomicInteger @@ -260,40 +262,40 @@ class ForwardCompatibilityTest { @InjectAdminURI adminURI: URI, @InjectLocalEndpointURI localEndpointURI: URI, ) { - // Create Admin API client with the provided admin URI - val adminClient = ApiClient().setHost(adminURI.host).setPort(adminURI.port) - val adminApi = DeploymentApi(adminClient) - - // List all deployments - val deployments = adminApi.listDeployments() + // The Admin client is generated from the current specification and need not support older + // servers. In particular, v1.7 deployment responses lack the `type` discriminator, making + // HTTP/Lambda deserialization ambiguous. Although v1.7.3 supports PATCH, updateDeployment() + // also deserializes its response and would fail even if we ignored the return value. + // Keep both GET and PATCH raw until LAST_COMPATIBLE_RESTATE_SERVER_VERSION is raised to a + // server whose deployment responses include `type` and work with the generated client. + // Then replace these requests with DeploymentApi.listDeployments()/updateDeployment(). + val httpClient = HttpClient.newHttpClient() + val mapper = ObjectMapper() + val listResponse = + httpClient.send( + HttpRequest.newBuilder(adminURI.resolve("deployments")).GET().build(), + BodyHandlers.ofString(), + ) + check(listResponse.statusCode() == 200) { + "listDeployments call failed with: ${listResponse.statusCode()} - ${listResponse.body()}" + } + val deployments = mapper.readTree(listResponse.body()).required("deployments") + check(deployments.isArray && !deployments.isEmpty) { "Expected registered deployments" } LOG.info("Patching all deployments to use endpoint URI: {}", localEndpointURI) - // For each deployment, update its URI. - // NOTE: We use a raw PUT request here instead of adminApi.updateDeployment() because - // the generated client uses PATCH (per the current OpenAPI spec), but this test runs - // against an OLDER server version that only supports PUT on this endpoint. - // When the minimum supported version includes PATCH support, replace this block with: - // adminApi.updateDeployment(deployment.httpDeploymentResponse.id, updateRequest) - val httpClient = java.net.http.HttpClient.newHttpClient() - for (deployment in deployments.deployments) { - val deploymentId = deployment.httpDeploymentResponse.id - val body = """{"uri":"$localEndpointURI"}""" + val body = mapper.writeValueAsString(mapOf("uri" to localEndpointURI.toString())) + for (deployment in deployments) { + val deploymentId = deployment.required("id").asText() val request = - java.net.http.HttpRequest.newBuilder() - .uri( - URI.create( - "http://${adminURI.host}:${adminURI.port}/v2/deployments/$deploymentId" - ) - ) + HttpRequest.newBuilder(adminURI.resolve("deployments/$deploymentId")) .header("Content-Type", "application/json") - .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)) + .method("PATCH", HttpRequest.BodyPublishers.ofString(body)) .build() try { - val response = - httpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()) - check(response.statusCode() / 100 == 2) { + val response = httpClient.send(request, BodyHandlers.ofString()) + check(response.statusCode() == 200) { "updateDeployment call failed with: ${response.statusCode()} - ${response.body()}" } LOG.info( diff --git a/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/OpenAPITest.kt b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/OpenAPITest.kt index 82f125fc..a7a02e1a 100644 --- a/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/OpenAPITest.kt +++ b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/OpenAPITest.kt @@ -8,86 +8,181 @@ // https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE package dev.restate.sdktesting.tests -import dev.restate.sdk.annotation.Handler -import dev.restate.sdk.annotation.Name -import dev.restate.sdk.annotation.Service +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import dev.restate.sdk.annotation.* import dev.restate.sdk.endpoint.Endpoint +import dev.restate.sdk.kotlin.endpoint.journalRetention import dev.restate.sdktesting.infra.* +import dev.restate.sdktesting.tests.openapi.checkCompatibility +import dev.restate.sdktesting.tests.openapi.generateClient import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.time.Duration +import kotlin.time.Duration.Companion.days +import kotlinx.serialization.Serializable import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.BeforeAllCallback import org.junit.jupiter.api.extension.RegisterExtension -import org.openapitools.codegen.DefaultGenerator -import org.openapitools.codegen.config.CodegenConfigurator class OpenAPITest { + @Serializable data class Details(val language: String) + + @Serializable data class Greeting(val name: String, val details: Details) + @Service @Name("GreeterService") class GreeterService { - @Handler - suspend fun greet(name: String): String { - return "Hello, $name!" - } + @Handler suspend fun greet(greeting: Greeting): Greeting = greeting + } + + @VirtualObject + @Name("GreeterObject") + class GreeterObject { + @Handler suspend fun greet(greeting: Greeting): Greeting = greeting + + @Shared suspend fun read(): String = "ready" + } + + @Workflow + @Name("GreeterWorkflow") + class GreeterWorkflow { + @Workflow suspend fun run(greeting: Greeting): Greeting = greeting + + @Shared suspend fun read(): String = "ready" } companion object { + private val mapper = ObjectMapper() + private val http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build() + private lateinit var artifacts: Path + + @RegisterExtension + @JvmField + val reports = BeforeAllCallback { context -> + artifacts = + Files.createDirectories( + Path.of( + context + .getConfigurationParameter( + BaseRestateDeployerExtension.REPORT_DIR_PROPERTY_NAME + ) + .orElse("test_report") + ) + .resolve("OpenAPITest/openapi") + ) + } + @RegisterExtension + @JvmField val deployerExt: RestateDeployerExtension = RestateDeployerExtension { - withEndpoint(Endpoint.bind(GreeterService())) + withEndpoint( + Endpoint.bind(GreeterService()) { it.journalRetention = 1.days } + .bind(GreeterObject()) + .bind(GreeterWorkflow()) + ) } } @Test - fun shouldGenerateValidOpenAPI(@InjectAdminURI adminURI: URI) = runTest { - // Download OpenAPI spec from Admin API - val httpClient = HttpClient.newHttpClient() - val request = - HttpRequest.newBuilder() - .uri(URI.create("${adminURI}services/GreeterService/openapi")) - .GET() - .build() + fun adminClientWorksAgainstCandidate( + @InjectAdminURI adminURI: URI, + @InjectIngressURI ingressURI: URI, + ) { + val spec = download(adminURI.resolve("openapi"), "admin.json") + val invocationId = completedInvocation(ingressURI) + generateClient( + spec, + artifacts.resolve("admin-client"), + "dev.restate.candidate", + "AdminSmoke.java", + ) + .use { client -> + // The Java smoke test is compiled together with the freshly generated client. This checks + // generated method signatures as well as runtime serialization/deserialization. + client + .loadClass("AdminSmoke") + .getMethod("run", URI::class.java, String::class.java) + .invoke(null, adminURI, invocationId) + } + } + + @Test + fun adminContractIsBackwardsCompatible(@InjectAdminURI adminURI: URI) { + val candidate = download(adminURI.resolve("openapi"), "admin-compatibility.json") + val baseline = artifacts.resolve("admin-baseline.json") + checkNotNull(javaClass.getResourceAsStream("/openapi/baseline/admin.json")).use { + Files.copy(it, baseline, StandardCopyOption.REPLACE_EXISTING) + } + checkCompatibility(baseline, candidate, artifacts.resolve("breaking-changes.txt")) + } - val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()) - assertThat(response.statusCode()).isEqualTo(200) - - // Save OpenAPI spec to a temporary file - val tempDir = Files.createTempDirectory("openapi-test") - val openApiFile = tempDir.resolve("openapi.json") - Files.writeString(openApiFile, response.body()) - - // Configure and run OpenAPI Generator - val configurator = - CodegenConfigurator().apply { - setInputSpec(openApiFile.toString()) - setOutputDir(tempDir.resolve("generated").toString()) - setGeneratorName("java") - setApiPackage("dev.restate.generated.api") - setModelPackage("dev.restate.generated.model") - // Using the latest Java client generator features - setAdditionalProperties( - mapOf( - "dateLibrary" to "java8", - "artifactId" to "restate-generated-client", - "artifactVersion" to "1.0.0", - ) + @Test + fun serviceClientsWorkAgainstCandidate( + @InjectAdminURI adminURI: URI, + @InjectIngressURI ingressURI: URI, + ) { + for (service in listOf("GreeterService", "GreeterObject", "GreeterWorkflow")) { + val spec = download(adminURI.resolve("services/$service/openapi"), "$service.json") + generateClient( + spec, + artifacts.resolve("$service-client"), + // Each client has its own classloader, so the common invocation smoke test can use + // the same package while compiling independently against all three documents. + "dev.restate.ingress", + "${service}Smoke.java", + "InvocationSmoke.java", ) - } + .use { client -> + client + .loadClass("${service}Smoke") + .getMethod("run", URI::class.java) + .invoke(null, ingressURI) + } + } + } - // Generate the client code - val generator = DefaultGenerator() - val files = generator.opts(configurator.toClientOptInput()).generate() + private fun completedInvocation(ingressURI: URI): String { + val result = + request(ingressURI.resolve("restate/send/GreeterService/greet"), "POST", payload(), 202) + val id = result.path("invocationId").asText() + assertThat(id).isNotBlank() + request(ingressURI.resolve("restate/attach/$id"), "GET", null, 200) + return id + } - // Verify that files were generated - assertThat(files).isNotEmpty() - assertThat(tempDir.resolve("generated/src/main/java")).exists() + private fun payload() = """{"name":"Ada","details":{"language":"en"}}""" - // Verify specific generated files that should exist - assertThat(tempDir.resolve("generated/src/main/java/dev/restate/generated/api")).exists() - assertThat(tempDir.resolve("generated/src/main/java/dev/restate/generated/model")).exists() + private fun download(uri: URI, name: String): Path { + val document = request(uri, "GET", null, 200) + val path = artifacts.resolve(name) + Files.writeString(path, mapper.writerWithDefaultPrettyPrinter().writeValueAsString(document)) + return path + } + + private fun request(uri: URI, method: String, body: String?, status: Int): JsonNode { + val request = + HttpRequest.newBuilder(uri) + .timeout(Duration.ofSeconds(30)) + .header("accept", "application/json") + .apply { if (body != null) header("content-type", "application/json") } + .method( + method, + body?.let { HttpRequest.BodyPublishers.ofString(it) } + ?: HttpRequest.BodyPublishers.noBody(), + ) + .build() + val response = http.send(request, HttpResponse.BodyHandlers.ofString()) + assertThat(response.statusCode()) + .withFailMessage("%s %s: %s", method, uri, response.body()) + .isEqualTo(status) + return mapper.readTree(response.body()) } } diff --git a/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/openapi/OpenAPIClient.kt b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/openapi/OpenAPIClient.kt new file mode 100644 index 00000000..fbdb8795 --- /dev/null +++ b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/openapi/OpenAPIClient.kt @@ -0,0 +1,87 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +package dev.restate.sdktesting.tests.openapi + +import java.net.URLClassLoader +import java.nio.file.Files +import java.nio.file.Path +import javax.tools.ToolProvider +import org.openapitools.codegen.DefaultGenerator +import org.openapitools.codegen.config.CodegenConfigurator + +/** Validate and compile the document actually served by the candidate, not the baseline client. */ +fun generateClient( + spec: Path, + reportDir: Path, + packageName: String, + vararg smokeSources: String, +): URLClassLoader { + // A reused --report-dir must never let stale generated files satisfy compilation. + val output = Files.createTempDirectory(Files.createDirectories(reportDir), "generated-") + val configurator = + CodegenConfigurator().apply { + setInputSpec(spec.toString()) + setOutputDir(output.toString()) + setGeneratorName("java") + setLibrary("native") + setInvokerPackage("$packageName.client") + setApiPackage("$packageName.api") + setModelPackage("$packageName.model") + setValidateSpec(true) + setGlobalProperties( + mapOf( + "apiTests" to "false", + "modelTests" to "false", + "apiDocs" to "false", + "modelDocs" to "false", + ) + ) + setAdditionalProperties( + mapOf("openApiNullable" to "false", "hideGenerationTimestamp" to "true") + ) + } + val files = DefaultGenerator().opts(configurator.toClientOptInput()).generate() + check(files.isNotEmpty()) { "No client generated for $spec" } + + val sourceDir = output.resolve("src/main/java") + for (smokeSource in smokeSources) { + val resource = checkNotNull(object {}.javaClass.getResourceAsStream("/openapi/$smokeSource")) + resource.use { Files.copy(it, sourceDir.resolve(smokeSource)) } + } + val sources = + Files.walk(sourceDir).use { paths -> + paths.filter { it.toString().endsWith(".java") }.map { it.toFile() }.toList() + } + check(sources.isNotEmpty()) { "No Java sources generated for $spec" } + val classes = Files.createDirectories(output.resolve("classes")) + val compiler = + checkNotNull(ToolProvider.getSystemJavaCompiler()) { "Run OpenAPI tests with a JDK" } + Files.newBufferedWriter(output.resolve("javac.log")).use { log -> + compiler.getStandardFileManager(null, null, null).use { manager -> + val success = + compiler + .getTask( + log, + manager, + null, + listOf( + "-classpath", + System.getProperty("java.class.path"), + "-d", + classes.toString(), + ), + null, + manager.getJavaFileObjectsFromFiles(sources), + ) + .call() + check(success) { "Generated client does not compile: ${output.resolve("javac.log")}" } + } + } + return URLClassLoader(arrayOf(classes.toUri().toURL()), object {}.javaClass.classLoader) +} diff --git a/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/openapi/OpenAPICompatibility.kt b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/openapi/OpenAPICompatibility.kt new file mode 100644 index 00000000..8d12006e --- /dev/null +++ b/e2e-tests/src/main/kotlin/dev/restate/sdktesting/tests/openapi/OpenAPICompatibility.kt @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +package dev.restate.sdktesting.tests.openapi + +import java.nio.file.Files +import java.nio.file.Path +import java.time.Duration +import org.testcontainers.containers.GenericContainer +import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy +import org.testcontainers.utility.MountableFile + +/** Docker is already required by the suite; no separate local oasdiff installation is needed. */ +fun checkCompatibility(baseline: Path, candidate: Path, report: Path) { + // Testcontainers removes a one-shot container after a nonzero exit. Capture its output before + // that cleanup, so a breaking-change failure still leaves a useful report. + val output = StringBuffer() + GenericContainer("tufin/oasdiff:v1.33.0") + .apply { + withLogConsumer { output.append(it.utf8String) } + withCopyFileToContainer(MountableFile.forHostPath(baseline), "/spec/baseline.json") + withCopyFileToContainer(MountableFile.forHostPath(candidate), "/spec/candidate.json") + withCommand( + "breaking", + "--fail-on", + "ERR", + "--format", + "text", + "/spec/baseline.json", + "/spec/candidate.json", + ) + withStartupCheckStrategy(OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(2))) + withStartupAttempts(1) + } + .use { container -> + try { + container.start() + } catch (e: Exception) { + throw AssertionError("OpenAPI compatibility check failed; see $report", e) + } finally { + Files.writeString( + report, + if (container.containerId != null) container.logs else output.toString(), + ) + } + } +} diff --git a/e2e-tests/src/main/resources/openapi/AdminSmoke.java b/e2e-tests/src/main/resources/openapi/AdminSmoke.java new file mode 100644 index 00000000..c8c757af --- /dev/null +++ b/e2e-tests/src/main/resources/openapi/AdminSmoke.java @@ -0,0 +1,88 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +import dev.restate.candidate.api.DeploymentApi; +import dev.restate.candidate.api.InvocationApi; +import dev.restate.candidate.client.ApiClient; +import dev.restate.candidate.client.ApiException; +import dev.restate.candidate.model.HttpDeploymentType; +import dev.restate.candidate.model.HttpDetailedDeploymentResponse; +import dev.restate.candidate.model.UpdateDeploymentRequest; +import dev.restate.candidate.model.UpdateHttpDeploymentRequest; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import org.assertj.core.api.Assertions; + +/** Compiled at test time against the client generated from the candidate's /openapi. */ +public class AdminSmoke { + public static void run(URI admin, String completedInvocationId) throws Exception { + var requestUri = new AtomicReference(); + var client = new ApiClient().setHost(admin.getHost()).setPort(admin.getPort()); + client.setRequestInterceptor(builder -> requestUri.set(builder.build().uri())); + var deployments = new DeploymentApi(client); + var list = deployments.listDeployments().getDeployments(); + Assertions.assertThat(list).hasSize(1); + var http = list.getFirst().getHttpDeploymentResponse(); + Assertions.assertThat(http.getType()).isEqualTo(HttpDeploymentType.HTTP); + Assertions.assertThat(http.getUri()).isNotNull(); + Assertions.assertThat(http.getServices()).hasSize(3); + + var id = http.getId(); + var details = deployments.getDeployment(id).getHttpDetailedDeploymentResponse(); + checkDetails(details, id); + + // Exercise a real update, including re-discovery of the SDK endpoint. + var headers = Map.of("x-openapi-smoke", "updated"); + var update = + new UpdateDeploymentRequest( + new UpdateHttpDeploymentRequest().uri(http.getUri()).additionalHeaders(headers)); + var updated = deployments.updateDeployment(id, update).getHttpDetailedDeploymentResponse(); + checkDetails(updated, id); + Assertions.assertThat(updated.getAdditionalHeaders()).containsAllEntriesOf(headers); + Assertions.assertThat( + deployments + .getDeployment(id) + .getHttpDetailedDeploymentResponse() + .getAdditionalHeaders()) + .containsAllEntriesOf(headers); + + var invocations = new InvocationApi(client); + for (String selector : new String[] {"keep", "latest", id}) { + // The completed invocation must reach the handler and produce its documented conflict, + // rather than failing query deserialization. No suspended invocation fixture is needed. + try { + invocations.resumeInvocation(completedInvocationId, selector); + throw new AssertionError("Resuming a completed invocation should fail"); + } catch (ApiException e) { + Assertions.assertThat(e.getCode()).isEqualTo(409); + } + checkSelector(requestUri.get(), selector); + + var restarted = invocations.restartAsNewInvocation(completedInvocationId, 0, selector); + Assertions.assertThat(restarted.getNewInvocationId()) + .startsWith("inv_") + .isNotEqualTo(completedInvocationId); + checkSelector(requestUri.get(), selector); + } + } + + private static void checkDetails(HttpDetailedDeploymentResponse response, String id) { + Assertions.assertThat(response.getType()).isEqualTo(HttpDeploymentType.HTTP); + Assertions.assertThat(response.getId()).isEqualTo(id); + Assertions.assertThat(response.getServices()).hasSize(3); + Assertions.assertThat(response.getUri()).isNotNull(); + } + + private static void checkSelector(URI uri, String selector) { + Assertions.assertThat(URLDecoder.decode(uri.getRawQuery(), StandardCharsets.UTF_8).split("&")) + .contains("deployment=" + selector); + } +} diff --git a/e2e-tests/src/main/resources/openapi/GreeterObjectSmoke.java b/e2e-tests/src/main/resources/openapi/GreeterObjectSmoke.java new file mode 100644 index 00000000..d2bc5aa1 --- /dev/null +++ b/e2e-tests/src/main/resources/openapi/GreeterObjectSmoke.java @@ -0,0 +1,37 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +import static org.assertj.core.api.Assertions.assertThat; + +import dev.restate.ingress.api.DefaultApi; +import dev.restate.ingress.api.SendToHandlerApi; +import dev.restate.ingress.model.Details; +import dev.restate.ingress.model.GreetRequest; +import java.net.URI; +import java.util.UUID; + +public class GreeterObjectSmoke { + public static void run(URI ingress) throws Exception { + var client = InvocationSmoke.client(ingress); + var calls = new DefaultApi(client); + var sends = new SendToHandlerApi(client); + var greeting = new GreetRequest().name("Ada").details(new Details().language("en")); + var key = UUID.randomUUID().toString(); + + var response = calls.greetWithHttpInfo(key, greeting, null); + assertThat(response.getStatusCode()).isEqualTo(200); + assertThat(response.getData().getName()).isEqualTo("Ada"); + assertThat(response.getData().getDetails().getLanguage()).isEqualTo("en"); + + var idempotencyKey = UUID.randomUUID().toString(); + InvocationSmoke.verify( + client, sends.greetSendWithHttpInfo(key, greeting, idempotencyKey, null)); + assertThat(calls.greet(key, greeting, idempotencyKey)).isEqualTo(response.getData()); + assertThat(calls.read(key, null)).isEqualTo("ready"); + } +} diff --git a/e2e-tests/src/main/resources/openapi/GreeterServiceSmoke.java b/e2e-tests/src/main/resources/openapi/GreeterServiceSmoke.java new file mode 100644 index 00000000..c2dcf944 --- /dev/null +++ b/e2e-tests/src/main/resources/openapi/GreeterServiceSmoke.java @@ -0,0 +1,35 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +import static org.assertj.core.api.Assertions.assertThat; + +import dev.restate.ingress.api.DefaultApi; +import dev.restate.ingress.api.SendToHandlerApi; +import dev.restate.ingress.model.Details; +import dev.restate.ingress.model.GreetRequest; +import java.net.URI; +import java.util.UUID; + +public class GreeterServiceSmoke { + public static void run(URI ingress) throws Exception { + var client = InvocationSmoke.client(ingress); + var calls = new DefaultApi(client); + var sends = new SendToHandlerApi(client); + var greeting = new GreetRequest().name("Ada").details(new Details().language("en")); + + var response = calls.greetWithHttpInfo(greeting, null); + assertThat(response.getStatusCode()).isEqualTo(200); + assertThat(response.getData().getName()).isEqualTo("Ada"); + assertThat(response.getData().getDetails().getLanguage()).isEqualTo("en"); + + var idempotencyKey = UUID.randomUUID().toString(); + InvocationSmoke.verify(client, sends.greetSendWithHttpInfo(greeting, idempotencyKey, null)); + // Calling with the same idempotency key retrieves the sent invocation's typed result. + assertThat(calls.greet(greeting, idempotencyKey)).isEqualTo(response.getData()); + } +} diff --git a/e2e-tests/src/main/resources/openapi/GreeterWorkflowSmoke.java b/e2e-tests/src/main/resources/openapi/GreeterWorkflowSmoke.java new file mode 100644 index 00000000..89ad12b7 --- /dev/null +++ b/e2e-tests/src/main/resources/openapi/GreeterWorkflowSmoke.java @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +import static org.assertj.core.api.Assertions.assertThat; + +import dev.restate.ingress.api.DefaultApi; +import dev.restate.ingress.api.SendToHandlerApi; +import dev.restate.ingress.model.Details; +import dev.restate.ingress.model.RunRequest; +import java.net.URI; +import java.util.UUID; + +public class GreeterWorkflowSmoke { + public static void run(URI ingress) throws Exception { + var client = InvocationSmoke.client(ingress); + var calls = new DefaultApi(client); + var sends = new SendToHandlerApi(client); + var greeting = new RunRequest().name("Ada").details(new Details().language("en")); + + var response = calls.runWithHttpInfo(UUID.randomUUID().toString(), greeting); + assertThat(response.getStatusCode()).isEqualTo(200); + assertThat(response.getData().getName()).isEqualTo("Ada"); + assertThat(response.getData().getDetails().getLanguage()).isEqualTo("en"); + + var sentKey = UUID.randomUUID().toString(); + InvocationSmoke.verify(client, sends.runSendWithHttpInfo(sentKey, greeting, null)); + assertThat(calls.read(sentKey)).isEqualTo("ready"); + } +} diff --git a/e2e-tests/src/main/resources/openapi/InvocationSmoke.java b/e2e-tests/src/main/resources/openapi/InvocationSmoke.java new file mode 100644 index 00000000..53c39dba --- /dev/null +++ b/e2e-tests/src/main/resources/openapi/InvocationSmoke.java @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate SDK Test suite tool, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-test-suite/blob/main/LICENSE +import static org.assertj.core.api.Assertions.assertThat; + +import dev.restate.ingress.api.InvocationsOutputAndStatusApi; +import dev.restate.ingress.client.ApiClient; +import dev.restate.ingress.client.ApiResponse; +import dev.restate.ingress.model.ByInvocationId; +import dev.restate.ingress.model.RestateInvocationStatus; +import dev.restate.ingress.model.RestateInvocationTarget; +import dev.restate.ingress.model.RestateSendResponse; +import java.net.URI; +import java.time.Duration; + +/** Compiled independently against each service's generated invocation API. */ +public class InvocationSmoke { + public static ApiClient client(URI ingress) { + return new ApiClient() + .setHost(ingress.getHost()) + .setPort(ingress.getPort()) + .setReadTimeout(Duration.ofSeconds(30)); + } + + public static void verify(ApiClient client, ApiResponse sent) + throws Exception { + assertThat(sent.getStatusCode()).isEqualTo(202); + assertThat(sent.getData().getStatus()).isEqualTo(RestateSendResponse.StatusEnum.ACCEPTED); + var id = sent.getData().getInvocationId(); + assertThat(id).isNotBlank(); + var target = + new RestateInvocationTarget( + new ByInvocationId().target(ByInvocationId.TargetEnum.INVOCATION).invocationId(id)); + var invocations = new InvocationsOutputAndStatusApi(client); + + // Attach waits for completion before output/status. These generic output operations have no + // typed response schema; typed payload round trips are checked by the handler call operations. + assertThat(invocations.attachInvocationWithHttpInfo(id).getStatusCode()).isEqualTo(200); + assertThat(invocations.attachInvocationByTargetWithHttpInfo(target).getStatusCode()) + .isEqualTo(200); + assertThat(invocations.getInvocationOutputWithHttpInfo(id).getStatusCode()).isEqualTo(200); + assertThat(invocations.getInvocationOutputByTargetWithHttpInfo(target).getStatusCode()) + .isEqualTo(200); + checkCompleted(invocations.getInvocationStatus(id)); + checkCompleted(invocations.getInvocationStatusByTarget(target)); + } + + private static void checkCompleted(RestateInvocationStatus status) { + assertThat(status.getStage()).isEqualTo(RestateInvocationStatus.StageEnum.COMPLETED); + assertThat(status.getError()).isNull(); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b24caeb3..1fc4b5c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,8 +8,10 @@ testcontainers = "2.0.4" awaitility = "4.3.0" grpc = "1.68.1" protobuf = "4.28.2" +openapi = "7.21.0" [libraries] +openapi-generator = { module = "org.openapitools:openapi-generator", version.ref = "openapi" } # Restate SDK restate-sdk-client-kotlin = { module = "dev.restate:client-kotlin", version.ref = "restate" } restate-sdk-kotlin-http = { module = "dev.restate:sdk-kotlin-http", version.ref = "restate" } @@ -71,6 +73,6 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version = "1.10.1" } [plugins] -openapi-generator = { id = "org.openapi.generator", version = "7.5.0" } +openapi-generator = { id = "org.openapi.generator", version.ref = "openapi" } protobuf = { id = "com.google.protobuf", version = "0.10.0" } shadow = { id = "com.gradleup.shadow", version = "8.3.5" } diff --git a/infra/build.gradle.kts b/infra/build.gradle.kts index bc186ed4..82102e4e 100644 --- a/infra/build.gradle.kts +++ b/infra/build.gradle.kts @@ -118,6 +118,11 @@ openApiGenerate { configOptions.put("openApiNullable", "false") } +tasks.named("openApiGenerate") { + // Removed schemas must not survive regeneration as stale Java sources. + doFirst { delete(generatedOpenapi) } +} + tasks { withType().configureEach { dependsOn("generateProto") diff --git a/infra/src/main/openapi/admin.json b/infra/src/main/openapi/admin.json index 0dadbd74..5d0ba7d1 100644 --- a/infra/src/main/openapi/admin.json +++ b/infra/src/main/openapi/admin.json @@ -10,7 +10,7 @@ "name": "MIT", "url": "https://opensource.org/license/mit" }, - "version": "1.8.0-dev" + "version": "1.8.0-rc.1" }, "paths": { "/deployments": { @@ -310,9 +310,6 @@ "required": false, "schema": { "oneOf": [ - { - "type": "null" - }, { "type": "string", "enum": [ @@ -320,6 +317,9 @@ "Kill", "Purge" ] + }, + { + "type": "null" } ] } @@ -375,53 +375,21 @@ "description": "Cancellation request accepted and will be processed asynchronously" }, "400": { - "description": "", + "description": "A request field is invalid.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } }, "404": { - "description": "", + "description": "The invocation was not found.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -431,23 +399,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -457,23 +409,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -505,53 +441,21 @@ "description": "Invocation killed successfully" }, "400": { - "description": "", + "description": "A request field is invalid.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } }, "404": { - "description": "", + "description": "The invocation was not found.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -561,23 +465,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -587,23 +475,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -637,53 +509,21 @@ "description": "Pausing invocation" }, "400": { - "description": "", + "description": "A request field is invalid.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } }, "404": { - "description": "", + "description": "The invocation was not found.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -693,23 +533,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -719,23 +543,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -767,53 +575,21 @@ "description": "Invocation purged successfully" }, "400": { - "description": "", + "description": "A request field is invalid.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } }, "404": { - "description": "", + "description": "The invocation was not found.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -823,23 +599,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -849,23 +609,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -897,53 +641,21 @@ "description": "Invocation journal purged successfully, metadata retained" }, "400": { - "description": "", + "description": "A request field is invalid.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } }, "404": { - "description": "", + "description": "The invocation was not found.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -953,23 +665,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -979,23 +675,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1027,14 +707,12 @@ "description": "From which entry index the invocation should restart from.\nBy default the invocation restarts from the beginning (equivalent to 'from = 0'), retaining only the input of the original invocation.\nWhen greater than 0, the new invocation will copy the old journal prefix up to 'from' included, plus eventual completions for commands in the given prefix.\nIf the journal prefix contains commands that have not been completed, this operation will fail.", "required": false, "schema": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "#/components/schemas/u32" - } - ] + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 } }, { @@ -1045,39 +723,11 @@ "schema": { "oneOf": [ { - "type": "null" + "type": "string", + "description": "Which deployment to use: `keep` to keep the currently pinned deployment, `latest` to use the latest deployment, or a specific deployment ID." }, { - "oneOf": [ - { - "type": "string", - "description": "Keep the currently pinned deployment", - "enum": [ - "Keep" - ] - }, - { - "type": "string", - "description": "Use the latest deployment", - "enum": [ - "Latest" - ] - }, - { - "type": "object", - "description": "Use a specific deployment ID", - "required": [ - "Id" - ], - "properties": { - "Id": { - "type": "string", - "description": "Use a specific deployment ID" - } - } - } - ], - "description": "Specifies which deployment to use when resuming or restarting an invocation." + "type": "null" } ] } @@ -1099,49 +749,17 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } }, "404": { - "description": "", + "description": "The invocation was not found.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1151,23 +769,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1177,23 +779,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1203,23 +789,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1229,23 +799,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1255,23 +809,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1305,39 +843,11 @@ "schema": { "oneOf": [ { - "type": "null" + "type": "string", + "description": "Which deployment to use: `keep` to keep the currently pinned deployment, `latest` to use the latest deployment, or a specific deployment ID." }, { - "oneOf": [ - { - "type": "string", - "description": "Keep the currently pinned deployment", - "enum": [ - "Keep" - ] - }, - { - "type": "string", - "description": "Use the latest deployment", - "enum": [ - "Latest" - ] - }, - { - "type": "object", - "description": "Use a specific deployment ID", - "required": [ - "Id" - ], - "properties": { - "Id": { - "type": "string", - "description": "Use a specific deployment ID" - } - } - } - ], - "description": "Specifies which deployment to use when resuming or restarting an invocation." + "type": "null" } ] } @@ -1352,49 +862,17 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } }, "404": { - "description": "", + "description": "The invocation was not found.", "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1404,23 +882,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1430,23 +892,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -1456,23 +902,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "# Error description response\n\nError details of the response", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string" - }, - "restate_code": { - "type": [ - "string", - "null" - ], - "description": "# Restate code\n\nRestate error code describing this error" - } - } + "$ref": "#/components/schemas/ErrorDescriptionResponse" } } } @@ -2501,7 +1931,7 @@ }, "components": { "schemas": { - "AdvertisedAddress-http-ingress-server_HttpIngressPort": { + "AdvertisedAddress_HttpIngressPort": { "type": "string", "title": "advertised address", "description": "An externally accessible URI address for http-ingress-server. This can be set to unix:restate-data/ingress.sock to advertise the automatically created unix-socket instead of using tcp if needed", @@ -2592,377 +2022,40 @@ "type": "string" }, "DeploymentResponse": { - "anyOf": [ + "oneOf": [ { - "type": "object", - "title": "HttpDeploymentResponse", - "description": "Deployment response for HTTP deployments", - "required": [ - "id", - "uri", - "protocol_type", - "http_version", - "created_at", - "min_protocol_version", - "max_protocol_version", - "services" - ], - "properties": { - "additional_headers": { - "$ref": "#/components/schemas/SerdeableHeaderHashMap", - "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." - }, - "auth": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "#/components/schemas/HttpAuth", - "description": "# Authentication\n\nPer-deployment authentication, if configured." - } - ] - }, - "created_at": { - "type": "string" - }, - "http_version": { - "type": "string", - "description": "# HTTP Version\n\nHTTP Version used to invoke this service deployment." - }, - "id": { - "$ref": "#/components/schemas/DeploymentId", - "description": "# Deployment ID" - }, - "info": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SchemaInfo" - }, - "description": "# Info\n\nList of configuration/deprecation information related to this deployment." - }, - "max_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "metadata": { - "type": "object", - "description": "# Metadata\n\nDeployment metadata.", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - } - }, - "min_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "protocol_type": { - "$ref": "#/components/schemas/ProtocolType", - "description": "# Protocol Type\n\nProtocol type used to invoke this service deployment." - }, - "sdk_version": { - "type": [ - "string", - "null" - ], - "description": "# SDK version\n\nSDK library and version declared during registration." - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceNameRevPair" - }, - "description": "# Services\n\nList of services exposed by this deployment." - }, - "uri": { - "type": "string", - "format": "uri", - "description": "# Deployment URI\n\nURI used to invoke this service deployment." - } - } + "$ref": "#/components/schemas/HttpDeploymentResponse" }, { - "type": "object", - "title": "LambdaDeploymentResponse", - "description": "Deployment response for Lambda deployments", - "required": [ - "id", - "arn", - "created_at", - "min_protocol_version", - "max_protocol_version", - "services" - ], - "properties": { - "additional_headers": { - "$ref": "#/components/schemas/SerdeableHeaderHashMap", - "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." - }, - "arn": { - "$ref": "#/components/schemas/LambdaARN", - "description": "# Lambda ARN\n\nLambda ARN used to invoke this service deployment." - }, - "assume_role_arn": { - "type": [ - "string", - "null" - ], - "description": "# Assume role ARN\n\nAssume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details." - }, - "compression": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "#/components/schemas/EndpointLambdaCompression", - "description": "# Compression\n\nCompression algorithm used for invoking Lambda." - } - ] - }, - "created_at": { - "type": "string" - }, - "id": { - "$ref": "#/components/schemas/DeploymentId", - "description": "# Deployment ID" - }, - "info": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SchemaInfo" - }, - "description": "# Info\n\nList of configuration/deprecation information related to this deployment." - }, - "max_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "metadata": { - "type": "object", - "description": "# Metadata\n\nDeployment metadata.", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - } - }, - "min_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "sdk_version": { - "type": [ - "string", - "null" - ], - "description": "# SDK version\n\nSDK library and version declared during registration." - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceNameRevPair" - }, - "description": "# Services\n\nList of services exposed by this deployment." - } - } + "$ref": "#/components/schemas/LambdaDeploymentResponse" } - ] + ], + "description": "Registered deployment. The `type` field tells HTTP and Lambda deployments apart.", + "discriminator": { + "propertyName": "type", + "mapping": { + "http": "#/components/schemas/HttpDeploymentResponse", + "lambda": "#/components/schemas/LambdaDeploymentResponse" + } + } }, "DetailedDeploymentResponse": { - "anyOf": [ + "oneOf": [ { - "type": "object", - "title": "HttpDetailedDeploymentResponse", - "description": "Detailed deployment response for HTTP deployments", - "required": [ - "id", - "uri", - "protocol_type", - "http_version", - "created_at", - "min_protocol_version", - "max_protocol_version", - "services" - ], - "properties": { - "additional_headers": { - "$ref": "#/components/schemas/SerdeableHeaderHashMap", - "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." - }, - "auth": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "#/components/schemas/HttpAuth", - "description": "# Authentication\n\nPer-deployment authentication, if configured." - } - ] - }, - "created_at": { - "type": "string" - }, - "http_version": { - "type": "string", - "description": "# HTTP Version\n\nHTTP Version used to invoke this service deployment." - }, - "id": { - "$ref": "#/components/schemas/DeploymentId", - "description": "# Deployment ID" - }, - "info": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SchemaInfo" - }, - "description": "# Info\n\nList of configuration/deprecation information related to this deployment." - }, - "max_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "metadata": { - "type": "object", - "description": "# Metadata\n\nDeployment metadata.", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - } - }, - "min_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "protocol_type": { - "$ref": "#/components/schemas/ProtocolType", - "description": "# Protocol Type\n\nProtocol type used to invoke this service deployment." - }, - "sdk_version": { - "type": [ - "string", - "null" - ], - "description": "# SDK version\n\nSDK library and version declared during registration." - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceMetadata" - }, - "description": "# Services\n\nList of services exposed by this deployment." - }, - "uri": { - "type": "string", - "format": "uri", - "description": "# Deployment URI\n\nURI used to invoke this service deployment." - } - } + "$ref": "#/components/schemas/HttpDetailedDeploymentResponse" }, { - "type": "object", - "title": "LambdaDetailedDeploymentResponse", - "description": "Detailed deployment response for Lambda deployments", - "required": [ - "id", - "arn", - "created_at", - "min_protocol_version", - "max_protocol_version", - "services" - ], - "properties": { - "additional_headers": { - "$ref": "#/components/schemas/SerdeableHeaderHashMap", - "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." - }, - "arn": { - "$ref": "#/components/schemas/LambdaARN", - "description": "# Lambda ARN\n\nLambda ARN used to invoke this service deployment." - }, - "assume_role_arn": { - "type": [ - "string", - "null" - ], - "description": "# Assume role ARN\n\nAssume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details." - }, - "compression": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "#/components/schemas/EndpointLambdaCompression", - "description": "# Compression\n\nCompression algorithm used for invoking Lambda." - } - ] - }, - "created_at": { - "type": "string" - }, - "id": { - "$ref": "#/components/schemas/DeploymentId", - "description": "# Deployment ID" - }, - "info": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SchemaInfo" - }, - "description": "# Info\n\nList of configuration/deprecation information related to this deployment." - }, - "max_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "metadata": { - "type": "object", - "description": "# Metadata\n\nDeployment metadata.", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - } - }, - "min_protocol_version": { - "type": "integer", - "format": "int32", - "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." - }, - "sdk_version": { - "type": [ - "string", - "null" - ], - "description": "# SDK version\n\nSDK library and version declared during registration." - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceMetadata" - }, - "description": "# Services\n\nList of services exposed by this deployment." - } - } + "$ref": "#/components/schemas/LambdaDetailedDeploymentResponse" } ], - "description": "Detailed information about Restate deployments" + "description": "Detailed information about a registered deployment, including the metadata of its services.\nThe `type` field tells HTTP and Lambda deployments apart.", + "discriminator": { + "propertyName": "type", + "mapping": { + "http": "#/components/schemas/HttpDetailedDeploymentResponse", + "lambda": "#/components/schemas/LambdaDetailedDeploymentResponse" + } + } }, "EndpointLambdaCompression": { "type": "string", @@ -3112,23 +2205,23 @@ }, "state_preload_policy": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/StatePreloadPolicy", "description": "# State preload policy\n\nWhich state is preloaded (sent eagerly) at the start of an invocation to this handler.\nThis is relevant only for Workflows and Virtual Objects.\n\nIf set, it overrides the service-level policy.\n\n`\"All\"` preloads all state eagerly; `{\"Partial\": [\"key\", ...]}` preloads only the listed\nkeys eagerly (an empty list means fully lazy state)." + }, + { + "type": "null" } ] }, "ty": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/HandlerMetadataType", "description": "# Type\n\nThe handler type." + }, + { + "type": "null" } ] } @@ -3178,12 +2271,12 @@ }, "on_max_attempts": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/OnMaxAttempts", "description": "# On max attempts\n\nBehavior when max attempts are reached." + }, + { + "type": "null" } ] } @@ -3205,6 +2298,210 @@ ], "description": "HTTP authentication details." }, + "HttpDeploymentResponse": { + "type": "object", + "description": "Deployment response for HTTP deployments", + "required": [ + "type", + "id", + "uri", + "protocol_type", + "http_version", + "created_at", + "min_protocol_version", + "max_protocol_version", + "services" + ], + "properties": { + "additional_headers": { + "$ref": "#/components/schemas/SerdeableHeaderHashMap", + "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." + }, + "auth": { + "oneOf": [ + { + "$ref": "#/components/schemas/HttpAuth", + "description": "# Authentication\n\nPer-deployment authentication, if configured." + }, + { + "type": "null" + } + ] + }, + "created_at": { + "type": "string" + }, + "http_version": { + "type": "string", + "description": "# HTTP Version\n\nHTTP Version used to invoke this service deployment." + }, + "id": { + "$ref": "#/components/schemas/DeploymentId", + "description": "# Deployment ID" + }, + "info": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchemaInfo" + }, + "description": "# Info\n\nList of configuration/deprecation information related to this deployment." + }, + "max_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "metadata": { + "type": "object", + "description": "# Metadata\n\nDeployment metadata.", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "min_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "protocol_type": { + "$ref": "#/components/schemas/ProtocolType", + "description": "# Protocol Type\n\nProtocol type used to invoke this service deployment." + }, + "sdk_version": { + "type": [ + "string", + "null" + ], + "description": "# SDK version\n\nSDK library and version declared during registration." + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceNameRevPair" + }, + "description": "# Services\n\nList of services exposed by this deployment." + }, + "type": { + "$ref": "#/components/schemas/HttpDeploymentType", + "description": "# Type\n\nDeployment type discriminator, always `http`." + }, + "uri": { + "type": "string", + "format": "uri", + "description": "# Deployment URI\n\nURI used to invoke this service deployment." + } + } + }, + "HttpDeploymentType": { + "type": "string", + "description": "Discriminator value for HTTP deployments.", + "enum": [ + "http" + ] + }, + "HttpDetailedDeploymentResponse": { + "type": "object", + "description": "Detailed deployment response for HTTP deployments", + "required": [ + "type", + "id", + "uri", + "protocol_type", + "http_version", + "created_at", + "min_protocol_version", + "max_protocol_version", + "services" + ], + "properties": { + "additional_headers": { + "$ref": "#/components/schemas/SerdeableHeaderHashMap", + "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." + }, + "auth": { + "oneOf": [ + { + "$ref": "#/components/schemas/HttpAuth", + "description": "# Authentication\n\nPer-deployment authentication, if configured." + }, + { + "type": "null" + } + ] + }, + "created_at": { + "type": "string" + }, + "http_version": { + "type": "string", + "description": "# HTTP Version\n\nHTTP Version used to invoke this service deployment." + }, + "id": { + "$ref": "#/components/schemas/DeploymentId", + "description": "# Deployment ID" + }, + "info": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchemaInfo" + }, + "description": "# Info\n\nList of configuration/deprecation information related to this deployment." + }, + "max_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "metadata": { + "type": "object", + "description": "# Metadata\n\nDeployment metadata.", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "min_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "protocol_type": { + "$ref": "#/components/schemas/ProtocolType", + "description": "# Protocol Type\n\nProtocol type used to invoke this service deployment." + }, + "sdk_version": { + "type": [ + "string", + "null" + ], + "description": "# SDK version\n\nSDK library and version declared during registration." + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceMetadata" + }, + "description": "# Services\n\nList of services exposed by this deployment." + }, + "type": { + "$ref": "#/components/schemas/HttpDeploymentType", + "description": "# Type\n\nDeployment type discriminator, always `http`." + }, + "uri": { + "type": "string", + "format": "uri", + "description": "# Deployment URI\n\nURI used to invoke this service deployment." + } + } + }, + "InvocationId": { + "type": "string" + }, "KafkaClusterName": { "type": "string", "format": "hostname", @@ -3257,6 +2554,199 @@ "LambdaARN": { "type": "string" }, + "LambdaDeploymentResponse": { + "type": "object", + "description": "Deployment response for Lambda deployments", + "required": [ + "type", + "id", + "arn", + "created_at", + "min_protocol_version", + "max_protocol_version", + "services" + ], + "properties": { + "additional_headers": { + "$ref": "#/components/schemas/SerdeableHeaderHashMap", + "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." + }, + "arn": { + "$ref": "#/components/schemas/LambdaARN", + "description": "# Lambda ARN\n\nLambda ARN used to invoke this service deployment." + }, + "assume_role_arn": { + "type": [ + "string", + "null" + ], + "description": "# Assume role ARN\n\nAssume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details." + }, + "compression": { + "oneOf": [ + { + "$ref": "#/components/schemas/EndpointLambdaCompression", + "description": "# Compression\n\nCompression algorithm used for invoking Lambda." + }, + { + "type": "null" + } + ] + }, + "created_at": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/DeploymentId", + "description": "# Deployment ID" + }, + "info": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchemaInfo" + }, + "description": "# Info\n\nList of configuration/deprecation information related to this deployment." + }, + "max_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "metadata": { + "type": "object", + "description": "# Metadata\n\nDeployment metadata.", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "min_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "sdk_version": { + "type": [ + "string", + "null" + ], + "description": "# SDK version\n\nSDK library and version declared during registration." + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceNameRevPair" + }, + "description": "# Services\n\nList of services exposed by this deployment." + }, + "type": { + "$ref": "#/components/schemas/LambdaDeploymentType", + "description": "# Type\n\nDeployment type discriminator, always `lambda`." + } + } + }, + "LambdaDeploymentType": { + "type": "string", + "description": "Discriminator value for Lambda deployments.", + "enum": [ + "lambda" + ] + }, + "LambdaDetailedDeploymentResponse": { + "type": "object", + "description": "Detailed deployment response for Lambda deployments", + "required": [ + "type", + "id", + "arn", + "created_at", + "min_protocol_version", + "max_protocol_version", + "services" + ], + "properties": { + "additional_headers": { + "$ref": "#/components/schemas/SerdeableHeaderHashMap", + "description": "# Additional headers\n\nAdditional headers used to invoke this service deployment." + }, + "arn": { + "$ref": "#/components/schemas/LambdaARN", + "description": "# Lambda ARN\n\nLambda ARN used to invoke this service deployment." + }, + "assume_role_arn": { + "type": [ + "string", + "null" + ], + "description": "# Assume role ARN\n\nAssume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details." + }, + "compression": { + "oneOf": [ + { + "$ref": "#/components/schemas/EndpointLambdaCompression", + "description": "# Compression\n\nCompression algorithm used for invoking Lambda." + }, + { + "type": "null" + } + ] + }, + "created_at": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/DeploymentId", + "description": "# Deployment ID" + }, + "info": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchemaInfo" + }, + "description": "# Info\n\nList of configuration/deprecation information related to this deployment." + }, + "max_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Maximum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "metadata": { + "type": "object", + "description": "# Metadata\n\nDeployment metadata.", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "min_protocol_version": { + "type": "integer", + "format": "int32", + "description": "# Minimum Service Protocol version\n\nDuring registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version." + }, + "sdk_version": { + "type": [ + "string", + "null" + ], + "description": "# SDK version\n\nSDK library and version declared during registration." + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceMetadata" + }, + "description": "# Services\n\nList of services exposed by this deployment." + }, + "type": { + "$ref": "#/components/schemas/LambdaDeploymentType", + "description": "# Type\n\nDeployment type discriminator, always `lambda`." + } + } + }, "ListDeploymentsResponse": { "type": "object", "description": "List of all registered deployments", @@ -3522,23 +3012,23 @@ "properties": { "additional_headers": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/SerdeableHeaderHashMap", "description": "# Additional headers\n\nAdditional headers added to every discover/invoke request to the deployment.\n\nYou typically want to include here API keys and other tokens required to send requests to deployments." + }, + { + "type": "null" } ] }, "auth": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/HttpAuth", "description": "# Authentication\n\nOptional per-deployment authentication configuration. When set to `GoogleIdToken`,\nRestate mints a Google-signed OIDC ID token for each request and attaches it as\n`X-Serverless-Authorization: Bearer `. Cloud Run validates this header in\nprecedence over `Authorization` and strips it before forwarding to the container, so any\n`Authorization` placed in `additional_headers` passes through to the workload unchanged." + }, + { + "type": "null" } ] }, @@ -3589,12 +3079,12 @@ "properties": { "additional_headers": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/SerdeableHeaderHashMap", "description": "# Additional headers\n\nAdditional headers added to every discover/invoke request to the deployment." + }, + { + "type": "null" } ] }, @@ -3689,7 +3179,7 @@ ], "properties": { "new_invocation_id": { - "$ref": "#/components/schemas/String", + "$ref": "#/components/schemas/InvocationId", "description": "The invocation id of the new invocation." } } @@ -3842,8 +3332,10 @@ "description": "# Retry policy\n\nRetry policy applied to invocations of this service.\n\nIf unset, it returns the default values configured in the Restate configuration." }, "revision": { - "$ref": "#/components/schemas/u32", - "description": "# Revision\n\nLatest revision of the service." + "type": "integer", + "format": "int32", + "description": "# Revision\n\nLatest revision of the service.", + "minimum": 0 }, "state_preload_policy": { "$ref": "#/components/schemas/StatePreloadPolicy", @@ -3873,7 +3365,9 @@ "type": "string" }, "revision": { - "$ref": "#/components/schemas/u32" + "type": "integer", + "format": "int32", + "minimum": 0 } } }, @@ -3983,9 +3477,6 @@ ], "description": "Eager/lazy state preload policy for an invocation.\n\nThis is both the resolved policy handed to the invoker and the per-handler state preload\nconfiguration persisted in the schema (see the schema metadata module)." }, - "String": { - "type": "string" - }, "SubscriptionId": { "type": "string" }, @@ -4028,12 +3519,12 @@ "properties": { "additional_headers": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/SerdeableHeaderHashMap", "description": "# Additional headers\n\nAdditional headers added to the discover/invoke requests to the deployment.\nWhen provided, this will overwrite all the headers previously configured for this deployment." + }, + { + "type": "null" } ] }, @@ -4069,12 +3560,12 @@ "properties": { "additional_headers": { "oneOf": [ - { - "type": "null" - }, { "$ref": "#/components/schemas/SerdeableHeaderHashMap", "description": "# Additional headers\n\nAdditional headers added to the discover/invoke requests to the deployment.\nWhen provided, this will overwrite all the headers previously configured for this deployment." + }, + { + "type": "null" } ] }, @@ -4198,11 +3689,11 @@ "ingress_endpoint": { "oneOf": [ { - "type": "null" + "$ref": "#/components/schemas/AdvertisedAddress_HttpIngressPort", + "description": "# Ingress endpoint\n\nIngress endpoint that the Web UI should use to interact with." }, { - "$ref": "#/components/schemas/AdvertisedAddress-http-ingress-server_HttpIngressPort", - "description": "# Ingress endpoint\n\nIngress endpoint that the Web UI should use to interact with." + "type": "null" } ] }, @@ -4223,11 +3714,6 @@ "description": "# Admin server version\n\nVersion of the admin server" } } - }, - "u32": { - "type": "integer", - "format": "int32", - "minimum": 0 } }, "responses": { @@ -4330,10 +3816,6 @@ "name": "vqueue", "description": "Virtual queue management" }, - { - "name": "cluster_health", - "description": "Cluster health" - }, { "name": "health", "description": "Admin API health"