Skip to main content

restate_sdk/context/
request.rs

1use super::DurableFuture;
2
3use crate::endpoint::ContextInternal;
4use crate::errors::TerminalError;
5use crate::serde::{Deserialize, Serialize};
6use futures::FutureExt;
7use futures::future::BoxFuture;
8use std::fmt;
9use std::future::{Future, IntoFuture};
10use std::marker::PhantomData;
11use std::time::Duration;
12
13/// Target of a request to a Restate service.
14#[derive(Debug, Clone)]
15pub enum RequestTarget {
16    Service {
17        name: String,
18        handler: String,
19    },
20    Object {
21        name: String,
22        key: String,
23        handler: String,
24    },
25    Workflow {
26        name: String,
27        key: String,
28        handler: String,
29    },
30}
31
32impl RequestTarget {
33    pub fn service(name: impl Into<String>, handler: impl Into<String>) -> Self {
34        Self::Service {
35            name: name.into(),
36            handler: handler.into(),
37        }
38    }
39
40    pub fn object(
41        name: impl Into<String>,
42        key: impl Into<String>,
43        handler: impl Into<String>,
44    ) -> Self {
45        Self::Object {
46            name: name.into(),
47            key: key.into(),
48            handler: handler.into(),
49        }
50    }
51
52    pub fn workflow(
53        name: impl Into<String>,
54        key: impl Into<String>,
55        handler: impl Into<String>,
56    ) -> Self {
57        Self::Workflow {
58            name: name.into(),
59            key: key.into(),
60            handler: handler.into(),
61        }
62    }
63}
64
65impl fmt::Display for RequestTarget {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            RequestTarget::Service { name, handler } => write!(f, "{name}/{handler}"),
69            RequestTarget::Object { name, key, handler } => write!(f, "{name}/{key}/{handler}"),
70            RequestTarget::Workflow { name, key, handler } => write!(f, "{name}/{key}/{handler}"),
71        }
72    }
73}
74
75/// This struct encapsulates the parameters for a request to a service.
76pub struct Request<'a, Req, Res = ()> {
77    ctx: &'a ContextInternal,
78    request_target: RequestTarget,
79    idempotency_key: Option<String>,
80    scope: Option<String>,
81    limit_key: Option<String>,
82    headers: Vec<(String, String)>,
83    req: Req,
84    res: PhantomData<Res>,
85}
86
87impl<'a, Req, Res> Request<'a, Req, Res> {
88    pub(crate) fn new(ctx: &'a ContextInternal, request_target: RequestTarget, req: Req) -> Self {
89        Self {
90            ctx,
91            request_target,
92            idempotency_key: None,
93            scope: None,
94            limit_key: None,
95            headers: vec![],
96            req,
97            res: PhantomData,
98        }
99    }
100
101    pub fn header(mut self, key: String, value: String) -> Self {
102        self.headers.push((key, value));
103        self
104    }
105
106    /// Add idempotency key to the request
107    pub fn idempotency_key(mut self, idempotency_key: impl Into<String>) -> Self {
108        self.idempotency_key = Some(idempotency_key.into());
109        self
110    }
111
112    /// Route this request within the given scope.
113    ///
114    /// A scope is a sub-grouping of resources (invocations, virtual object instances, workflow
115    /// executions). Under the hood the scope contributes to the partition key, so all resources
116    /// in a scope get co-located by the restate-server. Idempotency keys are scoped, so the same
117    /// idempotency key in two different scopes refers to two distinct requests.
118    pub fn scope(mut self, scope: impl Into<String>) -> Self {
119        self.scope = Some(scope.into());
120        self
121    }
122
123    /// Add a concurrency limit key to the request.
124    ///
125    /// The limit key enforces hierarchical concurrency limits on invocations sharing the same
126    /// scope. It can only be used in conjunction with a scope (see [`Request::scope`]).
127    pub fn limit_key(mut self, limit_key: impl Into<String>) -> Self {
128        self.limit_key = Some(limit_key.into());
129        self
130    }
131
132    /// Call a service. This returns a future encapsulating the response.
133    pub fn call(self) -> impl CallFuture<Response = Res> + Send
134    where
135        Req: Serialize + 'static,
136        Res: Deserialize + 'static,
137    {
138        self.ctx.call(
139            self.request_target,
140            self.idempotency_key,
141            self.scope,
142            self.limit_key,
143            self.headers,
144            self.req,
145        )
146    }
147
148    /// Send the request to the service, without waiting for the response.
149    ///
150    /// The request is sent eagerly; you can drop the returned [`SendHandle`] to fire-and-forget,
151    /// or `.await` it to obtain an [`InvocationHandle`] to the newly created invocation.
152    pub fn send(self) -> SendHandle
153    where
154        Req: Serialize + 'static,
155    {
156        self.ctx.send(
157            self.request_target,
158            self.idempotency_key,
159            self.scope,
160            self.limit_key,
161            self.headers,
162            self.req,
163            None,
164        )
165    }
166
167    /// Schedule the request to the service, without waiting for the response.
168    ///
169    /// Same as [`Request::send`], but the invocation is executed after the given `delay`.
170    pub fn send_after(self, delay: Duration) -> SendHandle
171    where
172        Req: Serialize + 'static,
173    {
174        self.ctx.send(
175            self.request_target,
176            self.idempotency_key,
177            self.scope,
178            self.limit_key,
179            self.headers,
180            self.req,
181            Some(delay),
182        )
183    }
184}
185
186/// A handle to an invocation.
187///
188/// You can obtain an [`InvocationHandle`] in three ways:
189///
190/// * From [`ContextClient::invocation_handle`][crate::context::ContextClient::invocation_handle],
191///   when you already know the invocation id.
192/// * By `.await`-ing the [`SendHandle`] returned by [`Request::send`]/[`Request::send_after`].
193/// * By `.await`-ing [`CallFuture::invocation_handle`].
194///
195/// Once obtained, the invocation id is known synchronously via [`InvocationHandle::invocation_id`].
196pub struct InvocationHandle {
197    ctx: ContextInternal,
198    invocation_id: String,
199}
200
201impl InvocationHandle {
202    pub(crate) fn new(ctx: ContextInternal, invocation_id: String) -> Self {
203        Self { ctx, invocation_id }
204    }
205
206    /// The invocation id of the target invocation.
207    pub fn invocation_id(&self) -> &str {
208        &self.invocation_id
209    }
210
211    /// Cancel the invocation.
212    pub fn cancel(&self) {
213        self.ctx.cancel_invocation(&self.invocation_id)
214    }
215
216    /// Attach to the invocation, returning a future that resolves with its output/result.
217    pub fn attach<T: Deserialize + 'static>(
218        &self,
219    ) -> impl DurableFuture<Output = Result<T, TerminalError>> + Send + use<T> {
220        self.ctx.attach_invocation(self.invocation_id.clone())
221    }
222
223    /// Get a handle to a named [signal][crate::context::ContextSignals] on this invocation,
224    /// which you can [resolve][SignalHandle::resolve] or [reject][SignalHandle::reject].
225    pub fn signal(&self, name: impl Into<String>) -> SignalHandle {
226        SignalHandle {
227            ctx: self.ctx.clone(),
228            invocation_id: self.invocation_id.clone(),
229            name: name.into(),
230        }
231    }
232}
233
234/// A handle to a named signal on a target invocation, obtained via [`InvocationHandle::signal`].
235///
236/// Use it to complete a signal the target invocation is (or will be) awaiting via
237/// [`ContextSignals::signal`][crate::context::ContextSignals::signal].
238pub struct SignalHandle {
239    ctx: ContextInternal,
240    invocation_id: String,
241    name: String,
242}
243
244impl SignalHandle {
245    /// Resolve the signal with the given value.
246    pub fn resolve<T: Serialize + 'static>(self, value: T) {
247        self.ctx
248            .resolve_signal(&self.invocation_id, &self.name, value)
249    }
250
251    /// Reject the signal. The awaiting handler observes a terminal error.
252    pub fn reject(self, failure: TerminalError) {
253        self.ctx
254            .reject_signal(&self.invocation_id, &self.name, failure)
255    }
256}
257
258/// Handle returned by [`Request::send`]/[`Request::send_after`].
259///
260/// The request is already sent; `.await` this handle to obtain an [`InvocationHandle`] to the
261/// created invocation, or drop it to fire-and-forget.
262pub struct SendHandle {
263    invocation_id_future: BoxFuture<'static, Result<String, TerminalError>>,
264    ctx: ContextInternal,
265}
266
267impl SendHandle {
268    pub(crate) fn new(
269        ctx: ContextInternal,
270        invocation_id_future: BoxFuture<'static, Result<String, TerminalError>>,
271    ) -> Self {
272        Self {
273            invocation_id_future,
274            ctx,
275        }
276    }
277}
278
279impl IntoFuture for SendHandle {
280    type Output = Result<InvocationHandle, TerminalError>;
281    type IntoFuture = BoxFuture<'static, Result<InvocationHandle, TerminalError>>;
282
283    fn into_future(self) -> Self::IntoFuture {
284        let ctx = self.ctx;
285        async move {
286            let invocation_id = self.invocation_id_future.await?;
287            Ok(InvocationHandle::new(ctx, invocation_id))
288        }
289        .boxed()
290    }
291}
292
293pub trait CallFuture: DurableFuture<Output = Result<Self::Response, TerminalError>> {
294    type Response;
295
296    /// Returns a future that resolves with an [`InvocationHandle`] to this call's invocation,
297    /// without consuming or awaiting the response.
298    fn invocation_handle(
299        &self,
300    ) -> impl Future<Output = Result<InvocationHandle, TerminalError>> + Send;
301
302    /// Returns the invocation id of this call.
303    #[deprecated(
304        since = "0.11.0",
305        note = "use `invocation_handle().await?.invocation_id()` instead"
306    )]
307    fn invocation_id(&self) -> impl Future<Output = Result<String, TerminalError>> + Send;
308}