Skip to main content

cloud_sdk/client/
execution.rs

1use super::{
2    ClientExecutionError, ClientKernel, ClientOperation, ClientResponse, ClientResult,
3    ClientWorkspaceLease,
4};
5use crate::authentication::{
6    AsyncAuthenticatedTransport, BlockingAuthenticatedTransport, LocalAsyncAuthenticatedTransport,
7};
8use crate::diagnostics::{
9    DiagnosticContext, DiagnosticErrorCategory, DiagnosticEvent, DiagnosticObserver,
10    NoopDiagnosticObserver,
11};
12use crate::operation::{PreparationStorage, PreparedExecutionError};
13use crate::transport::BoundTransport;
14
15const NOOP_OBSERVER: NoopDiagnosticObserver = NoopDiagnosticObserver;
16
17impl<T> ClientKernel<T>
18where
19    T: BlockingAuthenticatedTransport + BoundTransport,
20{
21    /// Prepares, authenticates, sends once, and checked-decodes synchronously.
22    pub fn execute_blocking<O, const N: usize>(
23        &self,
24        operation: &O,
25        lease: ClientWorkspaceLease<'_, '_, N>,
26    ) -> ClientResult<O::Output, O::Error, T::Error, O::DecodeError>
27    where
28        O: ClientOperation,
29    {
30        self.execute_blocking_observed(operation, lease, &NOOP_OBSERVER)
31    }
32
33    /// Executes synchronously while emitting opt-in payload-free lifecycle events.
34    pub fn execute_blocking_observed<O, V, const N: usize>(
35        &self,
36        operation: &O,
37        mut lease: ClientWorkspaceLease<'_, '_, N>,
38        observer: &V,
39    ) -> ClientResult<O::Output, O::Error, T::Error, O::DecodeError>
40    where
41        O: ClientOperation,
42        V: DiagnosticObserver + ?Sized,
43    {
44        let mut parts = lease.parts_mut();
45        parts.clear();
46        notify(observer, DiagnosticEvent::PreparationStarted);
47        let prepared =
48            match operation.prepare(PreparationStorage::new(parts.target, parts.request_body)) {
49                Ok(prepared) => prepared,
50                Err(error) => {
51                    notify(observer, preparation_failed());
52                    return Err(ClientExecutionError::Preparation(error));
53                }
54            };
55        let context = DiagnosticContext::from_prepared(&prepared);
56        notify(observer, DiagnosticEvent::RequestPrepared { context });
57        notify(observer, DiagnosticEvent::DispatchStarted { context });
58        let response = match prepared.send_blocking(
59            &self.transport,
60            parts.response_body,
61            parts.response_headers,
62        ) {
63            Ok(response) => response,
64            Err(error) => {
65                notify(observer, execution_failed(context, &error));
66                return Err(ClientExecutionError::Execution(error));
67            }
68        };
69        decode_observed(operation, prepared, response, context, observer)
70    }
71}
72
73impl<T> ClientKernel<T>
74where
75    T: AsyncAuthenticatedTransport + BoundTransport + Sync,
76{
77    /// Prepares, authenticates, sends once, and checked-decodes with a Send transport.
78    // The explicit opaque return makes the cross-thread execution guarantee
79    // part of the public API instead of relying on async-future inference.
80    #[allow(clippy::manual_async_fn)]
81    pub fn execute_async<O, const N: usize>(
82        &self,
83        operation: &O,
84        lease: ClientWorkspaceLease<'_, '_, N>,
85    ) -> impl core::future::Future<
86        Output = ClientResult<O::Output, O::Error, T::Error, O::DecodeError>,
87    > + Send
88    where
89        O: ClientOperation + Sync,
90        O::Output: Send,
91        O::Error: Send,
92        O::DecodeError: Send,
93        T::Error: Send,
94    {
95        self.execute_async_observed(operation, lease, &NOOP_OBSERVER)
96    }
97
98    /// Executes with a Send transport and opt-in payload-free lifecycle events.
99    #[allow(clippy::manual_async_fn)]
100    pub fn execute_async_observed<O, V, const N: usize>(
101        &self,
102        operation: &O,
103        mut lease: ClientWorkspaceLease<'_, '_, N>,
104        observer: &V,
105    ) -> impl core::future::Future<
106        Output = ClientResult<O::Output, O::Error, T::Error, O::DecodeError>,
107    > + Send
108    where
109        O: ClientOperation + Sync,
110        O::Output: Send,
111        O::Error: Send,
112        O::DecodeError: Send,
113        T::Error: Send,
114        V: DiagnosticObserver + Sync + ?Sized,
115    {
116        async move {
117            let mut parts = lease.parts_mut();
118            parts.clear();
119            notify(observer, DiagnosticEvent::PreparationStarted);
120            let prepared = match operation
121                .prepare(PreparationStorage::new(parts.target, parts.request_body))
122            {
123                Ok(prepared) => prepared,
124                Err(error) => {
125                    notify(observer, preparation_failed());
126                    return Err(ClientExecutionError::Preparation(error));
127                }
128            };
129            let context = DiagnosticContext::from_prepared(&prepared);
130            notify(observer, DiagnosticEvent::RequestPrepared { context });
131            notify(observer, DiagnosticEvent::DispatchStarted { context });
132            let response = match prepared
133                .send_async(&self.transport, parts.response_body, parts.response_headers)
134                .await
135            {
136                Ok(response) => response,
137                Err(error) => {
138                    notify(observer, execution_failed(context, &error));
139                    return Err(ClientExecutionError::Execution(error));
140                }
141            };
142            decode_observed(operation, prepared, response, context, observer)
143        }
144    }
145}
146
147impl<T> ClientKernel<T>
148where
149    T: LocalAsyncAuthenticatedTransport + BoundTransport,
150{
151    /// Prepares, authenticates, sends once, and checked-decodes locally.
152    pub async fn execute_local_async<O, const N: usize>(
153        &self,
154        operation: &O,
155        lease: ClientWorkspaceLease<'_, '_, N>,
156    ) -> ClientResult<O::Output, O::Error, T::Error, O::DecodeError>
157    where
158        O: ClientOperation,
159    {
160        self.execute_local_async_observed(operation, lease, &NOOP_OBSERVER)
161            .await
162    }
163
164    /// Executes locally while emitting opt-in payload-free lifecycle events.
165    pub async fn execute_local_async_observed<O, V, const N: usize>(
166        &self,
167        operation: &O,
168        mut lease: ClientWorkspaceLease<'_, '_, N>,
169        observer: &V,
170    ) -> ClientResult<O::Output, O::Error, T::Error, O::DecodeError>
171    where
172        O: ClientOperation,
173        V: DiagnosticObserver + ?Sized,
174    {
175        let mut parts = lease.parts_mut();
176        parts.clear();
177        notify(observer, DiagnosticEvent::PreparationStarted);
178        let prepared =
179            match operation.prepare(PreparationStorage::new(parts.target, parts.request_body)) {
180                Ok(prepared) => prepared,
181                Err(error) => {
182                    notify(observer, preparation_failed());
183                    return Err(ClientExecutionError::Preparation(error));
184                }
185            };
186        let context = DiagnosticContext::from_prepared(&prepared);
187        notify(observer, DiagnosticEvent::RequestPrepared { context });
188        notify(observer, DiagnosticEvent::DispatchStarted { context });
189        let response = match prepared
190            .send_local_async(&self.transport, parts.response_body, parts.response_headers)
191            .await
192        {
193            Ok(response) => response,
194            Err(error) => {
195                notify(observer, execution_failed(context, &error));
196                return Err(ClientExecutionError::Execution(error));
197            }
198        };
199        decode_observed(operation, prepared, response, context, observer)
200    }
201}
202
203#[allow(clippy::large_types_passed_by_value)]
204fn decode_observed<O, V, T>(
205    operation: &O,
206    prepared: crate::operation::PreparedRequest<'_>,
207    response: crate::transport::ResponseBuffer<'_>,
208    context: DiagnosticContext,
209    observer: &V,
210) -> ClientResult<O::Output, O::Error, T, O::DecodeError>
211where
212    O: ClientOperation,
213    V: DiagnosticObserver + ?Sized,
214{
215    let response = ClientResponse::new(prepared, response);
216    let diagnostic = response.diagnostic_response().ok();
217    if let Some(response) = diagnostic {
218        notify(
219            observer,
220            DiagnosticEvent::ResponseReceived { context, response },
221        );
222    }
223    match operation.decode_response(response) {
224        Ok(output) => {
225            notify(
226                observer,
227                DiagnosticEvent::Completed {
228                    context,
229                    response: diagnostic,
230                },
231            );
232            Ok(output)
233        }
234        Err(error) => {
235            notify(
236                observer,
237                DiagnosticEvent::DecodeFailed {
238                    context,
239                    response: diagnostic,
240                    error: DiagnosticErrorCategory::Decode,
241                },
242            );
243            Err(ClientExecutionError::Decode(error))
244        }
245    }
246}
247
248fn notify<O>(observer: &O, event: DiagnosticEvent)
249where
250    O: DiagnosticObserver + ?Sized,
251{
252    crate::diagnostics::notify(observer, event);
253}
254
255const fn preparation_failed() -> DiagnosticEvent {
256    DiagnosticEvent::PreparationFailed {
257        error: DiagnosticErrorCategory::Preparation,
258    }
259}
260
261fn execution_failed<E>(
262    context: DiagnosticContext,
263    error: &PreparedExecutionError<E>,
264) -> DiagnosticEvent {
265    let error = match error {
266        PreparedExecutionError::AuthorizationRequired
267        | PreparedExecutionError::AuthorizationInvalid(_) => DiagnosticErrorCategory::Authorization,
268        PreparedExecutionError::EndpointIdentity(_) | PreparedExecutionError::EndpointMismatch => {
269            DiagnosticErrorCategory::Endpoint
270        }
271        PreparedExecutionError::Transport(_) => DiagnosticErrorCategory::Transport,
272        PreparedExecutionError::ResponseWriter(_) => DiagnosticErrorCategory::ResponseTransaction,
273        PreparedExecutionError::ResponsePolicy(_) => DiagnosticErrorCategory::ResponsePolicy,
274    };
275    DiagnosticEvent::ExecutionFailed { context, error }
276}