harmos_sdk/operations/serving/
owned.rs1use super::{Answer, Finalization, Finished, Prepare, Prepared, Route, ServingError, WireTable, guarded};
22use crate::Message;
23use crate::QualifiedId;
24use crate::operations::{Carrier, Dependencies, EndpointSet, ExecutionContext, OperationDeclaration};
25
26struct State<T: Carrier> {
35 physical: Option<Physical<T>>,
36
37 #[cfg(feature = "streaming")]
38 streams: Option<super::WireStreams>,
39
40 initialization: Initialization,
41 closing: bool,
42 finalization: Option<Finalization<Physical<T>>>,
43
44 #[cfg(feature = "grpc")]
45 protobuf: Option<super::protobuf::Codec>,
46
47 grant: Option<crate::plugin::Grant>,
49}
50
51struct Physical<T: Carrier> {
52 dependencies: Dependencies,
53 operation: Option<T>,
54 request: <T::Declaration as OperationDeclaration>::Request,
55 endpoints: T::StreamEndpoints,
56 stopped: bool,
57}
58
59struct StreamEndpoints<E> {
60 endpoints: E,
61
62 #[cfg(feature = "streaming")]
63 streams: Option<super::WireStreams>,
64}
65
66enum Initialization {
69 Pending,
70 Started,
71 Ready,
72}
73
74impl WireTable {
79 pub(crate) fn install<D, T>(&mut self, dependencies: Dependencies, contract: crate::operations::Contract) -> Result<(), ServingError>
80 where
81 D: OperationDeclaration,
82 T: Carrier<Declaration = D>,
83 {
84 #[cfg(feature = "grpc")]
85 let protobuf = D::GRPC.is_some().then(super::protobuf::Codec::of::<D>);
86 #[cfg(feature = "streaming")]
87 let limits = self.stream_limits;
88 #[cfg(feature = "streaming")]
89 if T::StreamEndpoints::item_types() != (None, None) && limits.is_none() {
90 return Err(ServingError::StreamLimits);
91 }
92 #[cfg(not(feature = "streaming"))]
93 if T::StreamEndpoints::item_types() != (None, None) {
94 return Err(ServingError::StreamUnsupported);
95 }
96 let observe = std::any::TypeId::of::<D>() == std::any::TypeId::of::<crate::observers::Control>();
97 self.bind_owned_contract::<D, T>(
98 dependencies,
99 contract,
100 false,
101 observe,
102 move || {
103 if let Some(endpoints) = T::StreamEndpoints::empty() {
104 return Ok(StreamEndpoints {
105 endpoints,
106 #[cfg(feature = "streaming")]
107 streams: None,
108 });
109 }
110 #[cfg(feature = "streaming")]
111 {
112 let (endpoints, streams) = T::StreamEndpoints::bind_wire(limits.ok_or(ServingError::StreamLimits)?)?;
113 Ok(StreamEndpoints { endpoints, streams: Some(streams) })
114 }
115 #[cfg(not(feature = "streaming"))]
116 Err(ServingError::StreamUnsupported)
117 },
118 #[cfg(feature = "grpc")]
119 protobuf,
120 )?;
121 if observe {
122 let id = QualifiedId::qualify(&self.owner, D::ID).map_err(|_| ServingError::Catalog)?;
123 self.handlers.get_mut(id.as_str()).expect("installed observe control").resident = true;
124 }
125 Ok(())
126 }
127
128 pub fn install_lifecycle<D, T>(
129 &mut self, dependencies: Dependencies, descriptor: harmos_domain::model::operations::lifecycle::Descriptor,
130 ) -> Result<(), ServingError>
131 where
132 D: crate::OperationDeclaration,
133 T: Carrier<Declaration = D, StreamEndpoints = ()>,
134 {
135 if self.closed || self.ordinary_closed {
136 return Err(ServingError::Closing);
137 }
138 if descriptor.action().id() != D::ID
139 || descriptor.contract().fingerprint()
140 != (harmos_domain::operations::Entry::of::<D>().fingerprint)().map_err(|_| ServingError::Catalog)?
141 {
142 return Err(ServingError::Catalog);
143 }
144 self.bind_owned_contract::<D, T>(
145 dependencies,
146 descriptor.into(),
147 true,
148 true,
149 || {
150 Ok(StreamEndpoints {
151 endpoints: (),
152 #[cfg(feature = "streaming")]
153 streams: None,
154 })
155 },
156 #[cfg(feature = "grpc")]
157 None,
158 )
159 }
160
161 fn bind_owned_contract<D, T>(
162 &mut self, dependencies: Dependencies, contract: crate::operations::Contract, lifecycle: bool, builtin: bool,
163 endpoints: impl Fn() -> Result<StreamEndpoints<T::StreamEndpoints>, ServingError> + Send + Sync + 'static,
164 #[cfg(feature = "grpc")] protobuf: Option<super::protobuf::Codec>,
165 ) -> Result<(), ServingError>
166 where
167 D: OperationDeclaration,
168 T: Carrier<Declaration = D>,
169 {
170 let qualified = QualifiedId::qualify(&self.owner, D::ID).map_err(|_| ServingError::Catalog)?;
171 let id = qualified.as_str();
172 let (input, output) = T::StreamEndpoints::item_types();
173 if (input, output) != crate::operations::item_types::<D>()
174 || input.is_some() != contract.input().is_some()
175 || output.is_some() != contract.output().is_some()
176 {
177 return Err(ServingError::Catalog);
178 }
179 if self.handlers.contains_key(id) {
180 return Err(crate::Error::DuplicateOperation { id: id.to_owned() }.into());
181 }
182 let marked = contract.grpc().is_some();
183 #[cfg(not(feature = "grpc"))]
184 if marked {
185 return Err(ServingError::Codec(crate::MessageError::borrowed("gRPC operation requires provider protobuf support")));
186 }
187 #[cfg(feature = "grpc")]
188 if marked && protobuf.is_none() {
189 return Err(ServingError::Codec(crate::MessageError::borrowed("gRPC operation has no provider protobuf projection")));
190 }
191 let prepare: Prepare = Box::new(move |payload, grpc| {
192 if grpc && !marked {
193 return Err(ServingError::Codec(crate::MessageError::borrowed("operation has no gRPC contract")));
194 }
195 #[cfg(not(feature = "grpc"))]
196 if grpc {
197 return Err(ServingError::Codec(crate::MessageError::borrowed("provider protobuf codec is disabled")));
198 }
199 #[cfg(feature = "grpc")]
200 let selected = if grpc {
201 Some(protobuf.ok_or_else(|| ServingError::Codec(crate::MessageError::borrowed("provider has no protobuf codec")))?)
202 } else {
203 None
204 };
205 #[cfg(feature = "grpc")]
206 let request = if let Some(codec) = selected {
207 let mut request = None::<<T::Declaration as OperationDeclaration>::Request>;
208 (codec.decode)(&payload, &mut request).map_err(ServingError::Codec)?;
209 request.ok_or(ServingError::Catalog)?
210 } else {
211 <<T::Declaration as OperationDeclaration>::Request as crate::Message>::decode(&payload).map_err(ServingError::Codec)?
212 };
213 #[cfg(not(feature = "grpc"))]
214 let request =
215 <<T::Declaration as OperationDeclaration>::Request as crate::Message>::decode(&payload).map_err(ServingError::Codec)?;
216 let grant = <D as OperationDeclaration>::grant(&request);
217 let endpoints = endpoints()?;
218 #[cfg(all(feature = "grpc", feature = "streaming"))]
219 let endpoints = StreamEndpoints {
220 endpoints: endpoints.endpoints,
221 streams: match (endpoints.streams, selected) {
222 (Some(streams), Some(codec)) => Some(streams.protobuf(codec)?),
223 (streams, _) => streams,
224 },
225 };
226 let state = State::<T> {
227 #[cfg(feature = "streaming")]
228 streams: endpoints.streams,
229 physical: Some(Physical {
230 dependencies: dependencies.clone(),
231 operation: None,
232 request,
233 endpoints: endpoints.endpoints,
234 stopped: false,
235 }),
236 initialization: Initialization::Pending,
237 closing: false,
238 finalization: None,
239 #[cfg(feature = "grpc")]
240 protobuf: selected,
241 grant,
242 };
243 Ok(Box::new(state))
244 });
245 let handler = Route {
246 callees: D::OPERATIONS,
247 generation: None,
248 lifecycle,
249 builtin,
250 resident: false,
251 closing: false,
252 definition: contract.operation().clone(),
253 contract: Some(contract),
254 prepare,
255 };
256 self.handlers.insert(id.to_owned(), handler);
257 Ok(())
258 }
259}
260
261impl<T: Carrier> Prepared for State<T> {
262 #[cfg(feature = "streaming")]
263 fn streams(&self) -> Option<super::WireStreams> {
264 self.streams.clone()
265 }
266
267 fn grant(&self) -> Option<crate::plugin::Grant> {
268 self.grant.clone()
269 }
270
271 fn bind<'a>(&'a mut self, context: &'a ExecutionContext) -> Finished<'a> {
272 Box::pin(async move {
273 if self.closing {
274 return Err(ServingError::Closing);
275 }
276 match self.initialization {
277 Initialization::Ready => return Ok(()),
278 Initialization::Started => return Err(ServingError::InitializationStarted),
279 Initialization::Pending => self.initialization = Initialization::Started,
280 }
281 let physical = self.physical.as_mut().expect("original operation before cleanup");
282 let context = context.for_registration(physical.dependencies.clone());
283 let operation = guarded(async { T::bind(context).await.map_err(operation_error::<T>) }).await?;
284 physical.operation = Some(operation);
285 self.initialization = Initialization::Ready;
286 Ok(())
287 })
288 }
289
290 fn attempt<'a>(&'a mut self, context: &'a ExecutionContext) -> Answer<'a> {
291 Box::pin(async move {
292 if self.closing {
293 return Err(ServingError::Closing);
294 }
295 if !matches!(self.initialization, Initialization::Ready) {
296 return Err(ServingError::Uninitialized);
297 }
298 let physical = self.physical.as_mut().expect("original initialized operation");
299 guarded(async {
300 let operation = physical.operation.as_mut().expect("initialized operation");
301 let response = operation
302 .attempt(context.for_registration(physical.dependencies.clone()), &mut physical.request, &mut physical.endpoints)
303 .await
304 .map_err(operation_error::<T>)?;
305 #[cfg(feature = "grpc")]
306 if let Some(codec) = self.protobuf {
307 return (codec.encode)(&*response).map_err(ServingError::Codec);
308 }
309 Ok(response.encode())
310 })
311 .await
312 })
313 }
314
315 fn finalize(&mut self) -> Finished<'_> {
316 self.closing = true;
317 if self.finalization.is_none() {
318 let mut physical = self.physical.take().expect("original operation for cleanup");
319 let future = async move {
320 let result = guarded(async {
321 let Some(operation) = physical.operation.as_mut() else {
323 return Ok(());
324 };
325 if !physical.stopped {
326 operation.stop().await.map_err(operation_error::<T>)?;
327 physical.stopped = true;
328 }
329 operation.cleanup().await.map_err(operation_error::<T>)
330 })
331 .await;
332 (physical, result)
333 };
334 self.finalization = Some(Box::pin(future));
335 }
336 Box::pin(async move {
337 let (physical, result) = self.finalization.as_mut().expect("original cleanup future").await;
338 self.physical = Some(physical);
339 self.finalization = None;
340 result
341 })
342 }
343}
344
345fn operation_error<T: Carrier>(error: T::Error) -> ServingError {
352 let failure = crate::Failure::originate(T::classify(&error), &error, crate::failure::Origin::Plugin);
353 ServingError::Handler(failure)
354}
355
356