From 18c6765f04c69f15a553a468ae63d49fdef36d24 Mon Sep 17 00:00:00 2001 From: Mike Nguyen Date: Sat, 30 May 2026 11:34:22 +0100 Subject: [PATCH 1/5] ci: validate latest rc automatically (#316) Signed-off-by: Mike Nguyen --- .github/workflows/validate-examples-rc.yml | 145 +++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 .github/workflows/validate-examples-rc.yml diff --git a/.github/workflows/validate-examples-rc.yml b/.github/workflows/validate-examples-rc.yml new file mode 100644 index 0000000..5e4c6fb --- /dev/null +++ b/.github/workflows/validate-examples-rc.yml @@ -0,0 +1,145 @@ +name: validate-examples-rc + +on: + schedule: + # Run daily at 08:08 UTC + - cron: "8 8 * * *" + workflow_dispatch: + inputs: + branch: + description: "Branch to run the workflow against" + required: false + default: "main" + dapr_version: + description: "Dapr/Dapr RC version to use (leave empty to auto-detect latest RC)" + required: false + default: "" + daprcli_version: + description: "Dapr/CLI RC version to use (leave empty to auto-detect latest RC)" + required: false + default: "" + +permissions: + contents: read + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + RC_FOUND: ${{ steps.find-rc.outputs.RC_FOUND }} + DAPR_RUNTIME_VERSION: ${{ steps.find-rc.outputs.DAPR_RUNTIME_VERSION }} + DAPR_CLI_VERSION: ${{ steps.find-rc.outputs.DAPR_CLI_VERSION }} + EXAMPLES_MATRIX: ${{ steps.examples.outputs.matrix }} + steps: + - name: Check out code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.inputs.branch || github.ref }} + + - name: Find latest Dapr RC versions + id: find-rc + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Determine Dapr runtime RC version + if [ -n "${{ github.event.inputs.dapr_version }}" ]; then + RUNTIME_VERSION="${{ github.event.inputs.dapr_version }}" + echo "Using provided Dapr runtime version: $RUNTIME_VERSION" + else + RUNTIME_VERSION=$(gh api repos/dapr/dapr/releases --paginate --jq '[.[] | select(.prerelease == true and (.tag_name | test("rc"))) | .tag_name][0]' | head -1 | tr -d 'v') + echo "Latest Dapr runtime RC version: $RUNTIME_VERSION" + fi + + # Determine Dapr CLI RC version + if [ -n "${{ github.event.inputs.daprcli_version }}" ]; then + CLI_VERSION="${{ github.event.inputs.daprcli_version }}" + echo "Using provided Dapr CLI version: $CLI_VERSION" + else + CLI_VERSION=$(gh api repos/dapr/cli/releases --paginate --jq '[.[] | select(.prerelease == true and (.tag_name | test("rc"))) | .tag_name][0]' | head -1 | tr -d 'v') + echo "Latest Dapr CLI RC version: $CLI_VERSION" + fi + + if [ -z "$RUNTIME_VERSION" ]; then + echo "No Dapr runtime RC version found." + echo "RC_FOUND=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ -z "$CLI_VERSION" ]; then + echo "No Dapr CLI RC version found, falling back to latest stable CLI." + CLI_VERSION=$(gh api repos/dapr/cli/releases/latest --jq '.tag_name' | tr -d 'v') + echo "Using latest stable Dapr CLI version: $CLI_VERSION" + fi + + echo "RC_FOUND=true" >> "$GITHUB_OUTPUT" + echo "DAPR_RUNTIME_VERSION=$RUNTIME_VERSION" >> "$GITHUB_OUTPUT" + echo "DAPR_CLI_VERSION=$CLI_VERSION" >> "$GITHUB_OUTPUT" + + - name: Discover examples + id: examples + run: | + EXAMPLES=$(find examples/src -name 'README.md' -exec dirname {} \; \ + | sed 's|^examples/src/||' | sort | jq -Rnc '[inputs]') + echo "matrix=$EXAMPLES" >> "$GITHUB_OUTPUT" + + validate-example: + needs: setup + if: needs.setup.outputs.RC_FOUND == 'true' + runs-on: ubuntu-latest + env: + PYTHON_VER: 3.12 + DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/master/install/install.sh + DAPR_CLI_VERSION: ${{ needs.setup.outputs.DAPR_CLI_VERSION }} + DAPR_RUNTIME_VERSION: ${{ needs.setup.outputs.DAPR_RUNTIME_VERSION }} + RUST_BACKTRACE: full + + strategy: + fail-fast: false + matrix: + examples: ${{ fromJson(needs.setup.outputs.EXAMPLES_MATRIX) }} + steps: + - name: Check out code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.inputs.branch || github.ref }} + + - name: Rust setup + run: rustup toolchain install stable --profile minimal + + - name: Install Protoc + uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0 + with: + version: "24.4" + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Dapr CLI ${{ env.DAPR_CLI_VERSION }} + run: wget -q ${{ env.DAPR_INSTALL_URL }} -O - | /bin/bash -s ${{ env.DAPR_CLI_VERSION }} + + - name: Initialize Dapr runtime ${{ env.DAPR_RUNTIME_VERSION }} + run: | + dapr uninstall --all + dapr init --runtime-version ${{ env.DAPR_RUNTIME_VERSION }} + + - name: List running containers + run: | + docker ps -a + + - name: Set up Python ${{ env.PYTHON_VER }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VER }} + + - name: Install Mechanical Markdown + run: | + python -m pip install --upgrade pip + pip install mechanical-markdown + + - name: Dapr version + run: | + dapr version + docker ps -a + + - name: Check Example + run: | + cd examples + ./validate.sh ${{ matrix.examples }} From ce5ab821f56061e7943e430bced1131c46b9826a Mon Sep 17 00:00:00 2001 From: Mike Nguyen Date: Sat, 30 May 2026 12:53:22 +0100 Subject: [PATCH 2/5] ci: run on PRs to release branches (#318) Signed-off-by: Mike Nguyen --- .github/workflows/validate-examples-rc.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/validate-examples-rc.yml b/.github/workflows/validate-examples-rc.yml index 5e4c6fb..6461c92 100644 --- a/.github/workflows/validate-examples-rc.yml +++ b/.github/workflows/validate-examples-rc.yml @@ -4,6 +4,9 @@ on: schedule: # Run daily at 08:08 UTC - cron: "8 8 * * *" + pull_request: + branches: + - release-* workflow_dispatch: inputs: branch: From 2c30a3c43588c248ee0afd45b66b63e201f93da7 Mon Sep 17 00:00:00 2001 From: Immanuel Tikhonov <122638311+immanuwell@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:04:47 +0400 Subject: [PATCH 3/5] fix: stop connect_with_port from panicking on invalid ports (#323) --- dapr/src/client/mod.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/dapr/src/client/mod.rs b/dapr/src/client/mod.rs index ad61ae7..82e8237 100644 --- a/dapr/src/client/mod.rs +++ b/dapr/src/client/mod.rs @@ -71,13 +71,7 @@ impl Client { note = "Will be removed in 0.20.0. Use Client::new() or Client::from_options()." )] pub async fn connect_with_port(addr: String, port: String) -> Result { - // assert that port is between 1 and 65535 - let port: u16 = match port.parse::() { - Ok(p) => p, - Err(_) => { - panic!("Port must be a number between 1 and 65535"); - } - }; + let port: u16 = port.parse()?; let address = format!("{addr}:{port}"); @@ -1602,3 +1596,20 @@ impl From for ConversationMessage { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + #[allow(deprecated)] + async fn connect_with_port_returns_parse_error_for_invalid_port() { + match Client::::connect_with_port("http://127.0.0.1".into(), "abc".into()) + .await + { + Err(Error::ParseIntError) => {} + Err(err) => panic!("expected ParseIntError, got {err:?}"), + Ok(_) => panic!("invalid port should return an error"), + } + } +} From 2bdb9544b477ae3de1094c2da3746d5cb00d0d85 Mon Sep 17 00:00:00 2001 From: Mike Nguyen Date: Mon, 1 Jun 2026 14:12:20 +0100 Subject: [PATCH 4/5] feat: stable jobs (#320) * feat: jobs stable Signed-off-by: Mike Nguyen * fix: address backwards compatibility issue with 1.17 Signed-off-by: Mike Nguyen * chore: fmt Signed-off-by: Mike Nguyen * feat: implement basic retry for sidecar and actor connections Signed-off-by: Mike Nguyen --------- Signed-off-by: Mike Nguyen --- dapr/src/appcallback.rs | 99 +++- dapr/src/client/mod.rs | 185 ++++++- dapr/src/dapr/dapr.proto.runtime.v1.rs | 505 +++++++++++++++++- dapr/src/dapr/types.bin | Bin 168961 -> 171221 bytes dapr/src/lib.rs | 6 + dapr/src/server/appcallbackalpha.rs | 41 +- dapr/src/server/http.rs | 36 +- examples/src/actors/client.rs | 26 +- examples/src/app-api-token/main.rs | 7 + examples/src/bindings/input.rs | 13 +- examples/src/invoke/grpc/server.rs | 7 + examples/src/jobs-failurepolicy/dapr.yaml | 2 +- .../jobs-failurepolicy/jobs_failurepolicy.rs | 33 +- examples/src/jobs/jobs.rs | 33 +- proto/dapr/proto/runtime/v1/appcallback.proto | 9 +- proto/dapr/proto/runtime/v1/dapr.proto | 37 +- proto/dapr/proto/runtime/v1/jobs.proto | 21 + 17 files changed, 981 insertions(+), 79 deletions(-) diff --git a/dapr/src/appcallback.rs b/dapr/src/appcallback.rs index ad9508d..2ef4720 100644 --- a/dapr/src/appcallback.rs +++ b/dapr/src/appcallback.rs @@ -1,3 +1,4 @@ +use crate::dapr::proto::runtime::v1::app_callback_alpha_server::AppCallbackAlpha; use crate::dapr::proto::runtime::v1::app_callback_server::AppCallback; use crate::dapr::proto::{common, runtime}; use std::collections::HashMap; @@ -40,6 +41,12 @@ pub type TopicEventBulkRequest = runtime::v1::TopicEventBulkRequest; /// It includes the result for each event in the request. pub type TopicEventBulkResponse = runtime::v1::TopicEventBulkResponse; +/// JobEventRequest is the request message for a job event callback. +pub type JobEventRequest = runtime::v1::JobEventRequest; + +/// JobEventResponse is the response from the app when a job is triggered. +pub type JobEventResponse = runtime::v1::JobEventResponse; + impl ListTopicSubscriptionsResponse { /// Create `ListTopicSubscriptionsResponse` with a topic. pub fn topic(pubsub_name: String, topic: String) -> Self { @@ -82,6 +89,7 @@ impl ListInputBindingsResponse { pub struct AppCallbackService { handlers: Vec, + job_handlers: HashMap>, } pub struct Handler { @@ -156,6 +164,52 @@ impl AppCallback for AppCallbackService { ) -> Result, Status> { todo!("on_bulk_topic_event is not implemented yet") } + + async fn on_job_event( + &self, + request: Request, + ) -> Result, Status> { + let request_inner = request.into_inner(); + let job_name = if !request_inner.name.is_empty() { + request_inner.name.clone() + } else if let Some(stripped) = request_inner.method.strip_prefix("job/") { + stripped.to_string() + } else { + return Err(Status::invalid_argument(format!( + "cannot determine job name from request (method={:?})", + request_inner.method, + ))); + }; + + if let Some(handler) = self.job_handlers.get(&job_name) { + let handle_response = handler.handler(request_inner).await; + handle_response.map(Response::new) + } else { + Err(Status::not_found(format!( + "no handler registered for job {:?}", + job_name, + ))) + } + } +} + +// Also implement AppCallbackAlpha so the same service handles +// Dapr ≤ 1.17 runtimes that call OnJobEventAlpha1 / OnBulkTopicEventAlpha1. +#[tonic::async_trait] +impl AppCallbackAlpha for AppCallbackService { + async fn on_bulk_topic_event_alpha1( + &self, + request: Request, + ) -> Result, Status> { + self.on_bulk_topic_event(request).await + } + + async fn on_job_event_alpha1( + &self, + request: Request, + ) -> Result, Status> { + self.on_job_event(request).await + } } impl Default for AppCallbackService { @@ -192,12 +246,19 @@ impl AppCallbackService { /// The actor HTTP server ([`crate::server::DaprHttpServer`]) installs /// the layer automatically. pub fn new() -> AppCallbackService { - AppCallbackService { handlers: vec![] } + AppCallbackService { + handlers: vec![], + job_handlers: HashMap::new(), + } } pub fn add_handler(&mut self, handler: Handler) { self.handlers.push(handler) } + + pub fn add_job_handler(&mut self, job_name: String, handler: Box) { + self.job_handlers.insert(job_name, handler); + } } #[tonic::async_trait] @@ -207,3 +268,39 @@ pub trait HandlerMethod: Send + Sync + 'static { request: runtime::v1::TopicEventRequest, ) -> Result, Status>; } + +#[tonic::async_trait] +pub trait JobHandlerMethod: Send + Sync + 'static { + async fn handler( + &self, + request: runtime::v1::JobEventRequest, + ) -> Result; +} + +#[macro_export] +macro_rules! add_job_handler { + ($app_callback_service:expr, $handler_name:ident, $handler_fn:expr) => { + pub struct $handler_name {} + + #[$crate::reexport::async_trait] + impl $crate::appcallback::JobHandlerMethod for $handler_name { + async fn handler( + &self, + request: $crate::appcallback::JobEventRequest, + ) -> ::std::result::Result<$crate::appcallback::JobEventResponse, ::tonic::Status> + { + $handler_fn(request).await + } + } + + impl $handler_name { + pub fn new() -> Self { + $handler_name {} + } + } + + let handler_name = $handler_name.to_string(); + + $app_callback_service.add_job_handler(handler_name, Box::new($handler_name::new())); + }; +} diff --git a/dapr/src/client/mod.rs b/dapr/src/client/mod.rs index 82e8237..13ede08 100644 --- a/dapr/src/client/mod.rs +++ b/dapr/src/client/mod.rs @@ -20,6 +20,16 @@ use tonic::{Status, Streaming}; pub mod config; pub mod interceptor; +/// Returns `true` when a [`tonic::Status`] indicates the called gRPC method +/// does not exist on the server. Dapr ≤ 1.17 routes unknown methods through +/// its service‑invocation proxy, which fails with code `Unknown` and a +/// characteristic message instead of the standard `Unimplemented` code. +fn is_method_not_found(status: &tonic::Status) -> bool { + matches!(status.code(), tonic::Code::Unimplemented) + || (matches!(status.code(), tonic::Code::Unknown) + && status.message().contains("failed to proxy request")) +} + pub use config::{ API_TOKEN_METADATA_KEY, APP_API_TOKEN_ENV, ClientOptions, DAPR_API_TOKEN_ENV, DAPR_CLIENT_TIMEOUT_SECONDS_ENV, DAPR_GRPC_ENDPOINT_ENV, DAPR_GRPC_PORT_ENV, @@ -611,6 +621,26 @@ impl Client { /// /// * job - The job to schedule /// * overwrite - Optional flag to overwrite an existing job with the same name + pub async fn schedule_job( + &mut self, + job: Job, + overwrite: Option, + ) -> Result { + let request = ScheduleJobRequest { + job: Some(job.clone()), + overwrite: overwrite.unwrap_or(false), + }; + self.0.schedule_job(request).await + } + + /// Schedules a job with the Dapr Distributed Scheduler + /// + /// # Arguments + /// + /// * job - The job to schedule + /// * overwrite - Optional flag to overwrite an existing job with the same name + #[allow(deprecated)] + #[deprecated(note = "Use schedule_job instead")] pub async fn schedule_job_alpha1( &mut self, job: Job, @@ -628,6 +658,20 @@ impl Client { /// # Arguments /// /// * name - The name of the job to retrieve + pub async fn get_job(&mut self, name: &str) -> Result { + let request = GetJobRequest { + name: name.to_string(), + }; + self.0.get_job(request).await + } + + /// Retrieves a scheduled job from the Dapr Distributed Scheduler + /// + /// # Arguments + /// + /// * name - The name of the job to retrieve + #[allow(deprecated)] + #[deprecated(note = "Use get_job instead")] pub async fn get_job_alpha1(&mut self, name: &str) -> Result { let request = GetJobRequest { name: name.to_string(), @@ -640,6 +684,20 @@ impl Client { /// # Arguments /// /// * name - The name of the job to delete + pub async fn delete_job(&mut self, name: &str) -> Result { + let request = DeleteJobRequest { + name: name.to_string(), + }; + self.0.delete_job(request).await + } + + /// Deletes a scheduled job from the Dapr Distributed Scheduler + /// + /// # Arguments + /// + /// * name - The name of the job to delete + #[allow(deprecated)] + #[deprecated(note = "Use delete_job instead")] pub async fn delete_job_alpha1(&mut self, name: &str) -> Result { let request = DeleteJobRequest { name: name.to_string(), @@ -647,6 +705,27 @@ impl Client { self.0.delete_job_alpha1(request).await } + /// Deletes all jobs whose name starts with the given prefix. + /// Pass `None` to delete all jobs for the app. + /// + /// # Arguments + /// + /// * prefix - The name prefix to match jobs against, or `None` to delete all + pub async fn delete_jobs_by_prefix( + &mut self, + prefix: Option<&str>, + ) -> Result { + let request = DeleteJobsByPrefixRequest { + name_prefix: prefix.map(|p| p.to_string()), + }; + self.0.delete_jobs_by_prefix(request).await + } + + /// Lists all scheduled jobs + pub async fn list_jobs(&mut self) -> Result { + self.0.list_jobs(ListJobsRequest {}).await + } + /// Converse with an LLM /// /// # Arguments @@ -673,7 +752,7 @@ impl Client { } #[async_trait] -pub trait DaprInterface: Sized { +pub trait DaprInterface: Sized + Send { async fn connect(addr: String) -> Result; async fn publish_event(&mut self, request: PublishEventRequest) -> Result<(), Error>; async fn invoke_service( @@ -721,18 +800,39 @@ pub trait DaprInterface: Sized { async fn decrypt(&mut self, payload: Vec) -> Result, Status>; + #[allow(deprecated)] + async fn schedule_job( + &mut self, + request: ScheduleJobRequest, + ) -> Result; + #[allow(deprecated)] + async fn get_job(&mut self, request: GetJobRequest) -> Result; + + #[allow(deprecated)] + async fn delete_job(&mut self, request: DeleteJobRequest) -> Result; + + #[deprecated(note = "Use schedule_job instead")] async fn schedule_job_alpha1( &mut self, request: ScheduleJobRequest, ) -> Result; + #[deprecated(note = "Use get_job instead")] async fn get_job_alpha1(&mut self, request: GetJobRequest) -> Result; + #[deprecated(note = "Use delete_job instead")] async fn delete_job_alpha1( &mut self, request: DeleteJobRequest, ) -> Result; + async fn delete_jobs_by_prefix( + &mut self, + _request: DeleteJobsByPrefixRequest, + ) -> Result; + + async fn list_jobs(&mut self, _request: ListJobsRequest) -> Result; + async fn converse_alpha1( &mut self, request: ConversationRequest, @@ -937,6 +1037,58 @@ macro_rules! impl_dapr_interface_for { Ok(data) } + async fn schedule_job( + &mut self, + request: ScheduleJobRequest, + ) -> Result { + let fallback = request.clone(); + match self.schedule_job(request).await { + Ok(resp) => Ok(resp.into_inner()), + Err(status) if is_method_not_found(&status) => + { + #[allow(deprecated)] + Ok(self.schedule_job_alpha1(fallback).await?.into_inner()) + } + Err(status) => Err(status.into()), + } + } + + async fn get_job(&mut self, request: GetJobRequest) -> Result { + let fallback = request.clone(); + match self.get_job(Request::new(request)).await { + Ok(resp) => Ok(resp.into_inner()), + Err(status) if is_method_not_found(&status) => + { + #[allow(deprecated)] + Ok(self + .get_job_alpha1(Request::new(fallback)) + .await? + .into_inner()) + } + Err(status) => Err(status.into()), + } + } + + async fn delete_job( + &mut self, + request: DeleteJobRequest, + ) -> Result { + let fallback = request.clone(); + match self.delete_job(Request::new(request)).await { + Ok(resp) => Ok(resp.into_inner()), + Err(status) if is_method_not_found(&status) => + { + #[allow(deprecated)] + Ok(self + .delete_job_alpha1(Request::new(fallback)) + .await? + .into_inner()) + } + Err(status) => Err(status.into()), + } + } + + #[allow(deprecated)] async fn schedule_job_alpha1( &mut self, request: ScheduleJobRequest, @@ -944,6 +1096,7 @@ macro_rules! impl_dapr_interface_for { Ok(self.schedule_job_alpha1(request).await?.into_inner()) } + #[allow(deprecated)] async fn get_job_alpha1( &mut self, request: GetJobRequest, @@ -954,6 +1107,7 @@ macro_rules! impl_dapr_interface_for { .into_inner()) } + #[allow(deprecated)] async fn delete_job_alpha1( &mut self, request: DeleteJobRequest, @@ -964,6 +1118,23 @@ macro_rules! impl_dapr_interface_for { .into_inner()) } + async fn delete_jobs_by_prefix( + &mut self, + request: DeleteJobsByPrefixRequest, + ) -> Result { + Ok(self + .delete_jobs_by_prefix(Request::new(request)) + .await? + .into_inner()) + } + + async fn list_jobs( + &mut self, + request: ListJobsRequest, + ) -> Result { + Ok(self.list_jobs(Request::new(request)).await?.into_inner()) + } + async fn converse_alpha1( &mut self, request: ConversationRequest, @@ -1237,6 +1408,18 @@ pub type DeleteJobRequest = crate::dapr::proto::runtime::v1::DeleteJobRequest; /// A response from a delete job request pub type DeleteJobResponse = crate::dapr::proto::runtime::v1::DeleteJobResponse; +/// A request to delete jobs by name prefix +pub type DeleteJobsByPrefixRequest = crate::dapr::proto::runtime::v1::DeleteJobsByPrefixRequest; + +/// A response from a delete-jobs-by-prefix request +pub type DeleteJobsByPrefixResponse = crate::dapr::proto::runtime::v1::DeleteJobsByPrefixResponse; + +/// A request to list all scheduled jobs +pub type ListJobsRequest = crate::dapr::proto::runtime::v1::ListJobsRequest; + +/// A response containing the list of scheduled jobs +pub type ListJobsResponse = crate::dapr::proto::runtime::v1::ListJobsResponse; + /// A request to conversate with an LLM pub type ConversationRequest = crate::dapr::proto::runtime::v1::ConversationRequest; diff --git a/dapr/src/dapr/dapr.proto.runtime.v1.rs b/dapr/src/dapr/dapr.proto.runtime.v1.rs index 4fbd6cc..7407c2c 100644 --- a/dapr/src/dapr/dapr.proto.runtime.v1.rs +++ b/dapr/src/dapr/dapr.proto.runtime.v1.rs @@ -1307,6 +1307,33 @@ pub mod app_callback_client { ); self.inner.unary(req, path, codec).await } + /// Sends job back to the app's endpoint at trigger time. + pub async fn on_job_event( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/dapr.proto.runtime.v1.AppCallback/OnJobEvent", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("dapr.proto.runtime.v1.AppCallback", "OnJobEvent"), + ); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -1373,6 +1400,14 @@ pub mod app_callback_server { tonic::Response, tonic::Status, >; + /// Sends job back to the app's endpoint at trigger time. + async fn on_job_event( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// AppCallback V1 allows user application to interact with Dapr runtime. /// User application needs to implement AppCallback service if it needs to @@ -1722,6 +1757,51 @@ pub mod app_callback_server { }; Box::pin(fut) } + "/dapr.proto.runtime.v1.AppCallback/OnJobEvent" => { + #[allow(non_camel_case_types)] + struct OnJobEventSvc(pub Arc); + impl< + T: AppCallback, + > tonic::server::UnaryService + for OnJobEventSvc { + type Response = super::JobEventResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::on_job_event(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = OnJobEventSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { let mut response = http::Response::new( @@ -2195,7 +2275,8 @@ pub mod app_callback_alpha_client { ); self.inner.unary(req, path, codec).await } - /// Sends job back to the app's endpoint at trigger time. + /// Deprecated: Sends job back to the app's endpoint at trigger time. + #[deprecated] pub async fn on_job_event_alpha1( &mut self, request: impl tonic::IntoRequest, @@ -2248,7 +2329,7 @@ pub mod app_callback_alpha_server { tonic::Response, tonic::Status, >; - /// Sends job back to the app's endpoint at trigger time. + /// Deprecated: Sends job back to the app's endpoint at trigger time. async fn on_job_event_alpha1( &self, request: tonic::Request, @@ -3911,6 +3992,17 @@ pub struct DeleteJobsByPrefixRequestAlpha1 { /// Empty #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct DeleteJobsByPrefixResponseAlpha1 {} +/// DeleteJobsByPrefixRequest is the stable message to delete jobs by name prefix. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DeleteJobsByPrefixRequest { + /// name_prefix is the prefix of the job names to delete. If not provided, all + /// jobs associated with this app ID will be deleted. + #[prost(string, optional, tag = "1")] + pub name_prefix: ::core::option::Option<::prost::alloc::string::String>, +} +/// Empty +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DeleteJobsByPrefixResponse {} /// Empty #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct ListJobsRequestAlpha1 {} @@ -3921,6 +4013,16 @@ pub struct ListJobsResponseAlpha1 { #[prost(message, repeated, tag = "1")] pub jobs: ::prost::alloc::vec::Vec, } +/// Empty +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListJobsRequest {} +/// ListJobsResponse is the stable message response containing the list of jobs. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListJobsResponse { + /// The list of jobs. + #[prost(message, repeated, tag = "1")] + pub jobs: ::prost::alloc::vec::Vec, +} /// ShutdownRequest is the request for Shutdown. /// /// Empty @@ -5575,7 +5677,8 @@ pub mod dapr_client { .insert(GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "Shutdown")); self.inner.unary(req, path, codec).await } - /// Create and schedule a job + /// Deprecated: Create and schedule a job + #[deprecated] pub async fn schedule_job_alpha1( &mut self, request: impl tonic::IntoRequest, @@ -5602,7 +5705,33 @@ pub mod dapr_client { ); self.inner.unary(req, path, codec).await } - /// Gets a scheduled job + /// Create and schedule a job + pub async fn schedule_job( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/dapr.proto.runtime.v1.Dapr/ScheduleJob", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "ScheduleJob")); + self.inner.unary(req, path, codec).await + } + /// Deprecated: Gets a scheduled job + #[deprecated] pub async fn get_job_alpha1( &mut self, request: impl tonic::IntoRequest, @@ -5624,7 +5753,30 @@ pub mod dapr_client { .insert(GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "GetJobAlpha1")); self.inner.unary(req, path, codec).await } - /// Delete a job + /// Gets a scheduled job + pub async fn get_job( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/dapr.proto.runtime.v1.Dapr/GetJob", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "GetJob")); + self.inner.unary(req, path, codec).await + } + /// Deprecated: Delete a job + #[deprecated] pub async fn delete_job_alpha1( &mut self, request: impl tonic::IntoRequest, @@ -5651,6 +5803,33 @@ pub mod dapr_client { ); self.inner.unary(req, path, codec).await } + /// Delete a job + pub async fn delete_job( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/dapr.proto.runtime.v1.Dapr/DeleteJob", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "DeleteJob")); + self.inner.unary(req, path, codec).await + } + /// Deprecated: Delete jobs by name prefix + #[deprecated] pub async fn delete_jobs_by_prefix_alpha1( &mut self, request: impl tonic::IntoRequest, @@ -5680,6 +5859,35 @@ pub mod dapr_client { ); self.inner.unary(req, path, codec).await } + /// Delete jobs by name prefix + pub async fn delete_jobs_by_prefix( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/dapr.proto.runtime.v1.Dapr/DeleteJobsByPrefix", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "DeleteJobsByPrefix"), + ); + self.inner.unary(req, path, codec).await + } + /// Deprecated: List all jobs + #[deprecated] pub async fn list_jobs_alpha1( &mut self, request: impl tonic::IntoRequest, @@ -5704,6 +5912,31 @@ pub mod dapr_client { .insert(GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "ListJobsAlpha1")); self.inner.unary(req, path, codec).await } + /// List all jobs + pub async fn list_jobs( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/dapr.proto.runtime.v1.Dapr/ListJobs", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("dapr.proto.runtime.v1.Dapr", "ListJobs")); + self.inner.unary(req, path, codec).await + } /// Converse with a LLM service pub async fn converse_alpha1( &mut self, @@ -6210,7 +6443,7 @@ pub mod dapr_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; - /// Create and schedule a job + /// Deprecated: Create and schedule a job async fn schedule_job_alpha1( &self, request: tonic::Request, @@ -6218,12 +6451,25 @@ pub mod dapr_server { tonic::Response, tonic::Status, >; - /// Gets a scheduled job + /// Create and schedule a job + async fn schedule_job( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Deprecated: Gets a scheduled job async fn get_job_alpha1( &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; - /// Delete a job + /// Gets a scheduled job + async fn get_job( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Deprecated: Delete a job async fn delete_job_alpha1( &self, request: tonic::Request, @@ -6231,6 +6477,15 @@ pub mod dapr_server { tonic::Response, tonic::Status, >; + /// Delete a job + async fn delete_job( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Deprecated: Delete jobs by name prefix async fn delete_jobs_by_prefix_alpha1( &self, request: tonic::Request, @@ -6238,6 +6493,15 @@ pub mod dapr_server { tonic::Response, tonic::Status, >; + /// Delete jobs by name prefix + async fn delete_jobs_by_prefix( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Deprecated: List all jobs async fn list_jobs_alpha1( &self, request: tonic::Request, @@ -6245,6 +6509,14 @@ pub mod dapr_server { tonic::Response, tonic::Status, >; + /// List all jobs + async fn list_jobs( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Converse with a LLM service async fn converse_alpha1( &self, @@ -9042,6 +9314,49 @@ pub mod dapr_server { }; Box::pin(fut) } + "/dapr.proto.runtime.v1.Dapr/ScheduleJob" => { + #[allow(non_camel_case_types)] + struct ScheduleJobSvc(pub Arc); + impl tonic::server::UnaryService + for ScheduleJobSvc { + type Response = super::ScheduleJobResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::schedule_job(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ScheduleJobSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/dapr.proto.runtime.v1.Dapr/GetJobAlpha1" => { #[allow(non_camel_case_types)] struct GetJobAlpha1Svc(pub Arc); @@ -9085,6 +9400,49 @@ pub mod dapr_server { }; Box::pin(fut) } + "/dapr.proto.runtime.v1.Dapr/GetJob" => { + #[allow(non_camel_case_types)] + struct GetJobSvc(pub Arc); + impl tonic::server::UnaryService + for GetJobSvc { + type Response = super::GetJobResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_job(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetJobSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/dapr.proto.runtime.v1.Dapr/DeleteJobAlpha1" => { #[allow(non_camel_case_types)] struct DeleteJobAlpha1Svc(pub Arc); @@ -9128,6 +9486,49 @@ pub mod dapr_server { }; Box::pin(fut) } + "/dapr.proto.runtime.v1.Dapr/DeleteJob" => { + #[allow(non_camel_case_types)] + struct DeleteJobSvc(pub Arc); + impl tonic::server::UnaryService + for DeleteJobSvc { + type Response = super::DeleteJobResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_job(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DeleteJobSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/dapr.proto.runtime.v1.Dapr/DeleteJobsByPrefixAlpha1" => { #[allow(non_camel_case_types)] struct DeleteJobsByPrefixAlpha1Svc(pub Arc); @@ -9176,6 +9577,51 @@ pub mod dapr_server { }; Box::pin(fut) } + "/dapr.proto.runtime.v1.Dapr/DeleteJobsByPrefix" => { + #[allow(non_camel_case_types)] + struct DeleteJobsByPrefixSvc(pub Arc); + impl< + T: Dapr, + > tonic::server::UnaryService + for DeleteJobsByPrefixSvc { + type Response = super::DeleteJobsByPrefixResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_jobs_by_prefix(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DeleteJobsByPrefixSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/dapr.proto.runtime.v1.Dapr/ListJobsAlpha1" => { #[allow(non_camel_case_types)] struct ListJobsAlpha1Svc(pub Arc); @@ -9221,6 +9667,49 @@ pub mod dapr_server { }; Box::pin(fut) } + "/dapr.proto.runtime.v1.Dapr/ListJobs" => { + #[allow(non_camel_case_types)] + struct ListJobsSvc(pub Arc); + impl tonic::server::UnaryService + for ListJobsSvc { + type Response = super::ListJobsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_jobs(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListJobsSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/dapr.proto.runtime.v1.Dapr/ConverseAlpha1" => { #[allow(non_camel_case_types)] struct ConverseAlpha1Svc(pub Arc); diff --git a/dapr/src/dapr/types.bin b/dapr/src/dapr/types.bin index f6bca4a3dfff649d1012d140789c08235d8b05bc..9aeb76a28daf6a230bde20d42028f3781352ca8f 100644 GIT binary patch delta 6459 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 O>wa!YJ;Q%&{oWhAxtU&}o12ePC(YB- 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