Skip to main content

nxd_core/ports/
provider.rs

1//! Provider-runtime failures after transport/protocol decoding.
2//!
3//! This is an application-facing port type, deliberately independent of the
4//! generated protobuf representation and every concrete provider.
5
6use crate::plugin_protocol::v1;
7use futures_util::{future::BoxFuture, stream::BoxStream};
8
9/// Asynchronous provider boundary shared by linked first-party implementations
10/// and the external protocol adapter.
11///
12/// The versioned wire models remain the stable provider-owned envelopes; the
13/// in-process path avoids transport without inventing a second DTO hierarchy.
14pub trait Provider: Send + Sync {
15	fn get_metadata(
16		&self,
17		request: v1::GetMetadataRequest,
18	) -> BoxFuture<'_, Result<v1::GetMetadataResponse, tonic::Status>>;
19	fn observe(
20		&self,
21		request: v1::ObserveRequest,
22	) -> BoxFuture<'_, Result<v1::ObserveResponse, tonic::Status>>;
23	fn capture(
24		&self,
25		request: v1::CaptureRequest,
26	) -> BoxFuture<'_, Result<v1::CaptureResponse, tonic::Status>>;
27	fn plan(
28		&self,
29		request: v1::PlanRequest,
30	) -> BoxFuture<'_, Result<v1::PlanResponse, tonic::Status>>;
31	fn apply(
32		&self,
33		request: v1::ApplyRequest,
34	) -> BoxFuture<
35		'_,
36		Result<BoxStream<'static, Result<v1::ApplyResponse, tonic::Status>>, tonic::Status>,
37	>;
38	fn verify(
39		&self,
40		request: v1::VerifyRequest,
41	) -> BoxFuture<'_, Result<v1::VerifyResponse, tonic::Status>>;
42	fn cancel(
43		&self,
44		request: v1::CancelRequest,
45	) -> BoxFuture<'_, Result<v1::CancelResponse, tonic::Status>>;
46}
47
48/// Implement the direct port for an existing versioned provider service
49/// without duplicating its request, response, progress, or error mapping.
50#[macro_export]
51macro_rules! impl_provider_port {
52	($provider:ty) => {
53		impl nxd_core::ports::provider::Provider for $provider {
54			fn get_metadata(
55				&self,
56				request: nxd_plugin_protocol::v1::GetMetadataRequest,
57			) -> futures_util::future::BoxFuture<
58				'_,
59				Result<nxd_plugin_protocol::v1::GetMetadataResponse, tonic::Status>,
60			> {
61				Box::pin(async move {
62					<Self as nxd_plugin_protocol::v1::provider_service_server::ProviderService>::get_metadata(
63						self,
64						tonic::Request::new(request),
65					)
66					.await
67					.map(tonic::Response::into_inner)
68				})
69			}
70
71			fn observe(
72				&self,
73				request: nxd_plugin_protocol::v1::ObserveRequest,
74			) -> futures_util::future::BoxFuture<
75				'_,
76				Result<nxd_plugin_protocol::v1::ObserveResponse, tonic::Status>,
77			> {
78				Box::pin(async move {
79					<Self as nxd_plugin_protocol::v1::provider_service_server::ProviderService>::observe(
80						self,
81						tonic::Request::new(request),
82					)
83					.await
84					.map(tonic::Response::into_inner)
85				})
86			}
87
88			fn capture(
89				&self,
90				request: nxd_plugin_protocol::v1::CaptureRequest,
91			) -> futures_util::future::BoxFuture<
92				'_,
93				Result<nxd_plugin_protocol::v1::CaptureResponse, tonic::Status>,
94			> {
95				Box::pin(async move {
96					<Self as nxd_plugin_protocol::v1::provider_service_server::ProviderService>::capture(
97						self,
98						tonic::Request::new(request),
99					)
100					.await
101					.map(tonic::Response::into_inner)
102				})
103			}
104
105			fn plan(
106				&self,
107				request: nxd_plugin_protocol::v1::PlanRequest,
108			) -> futures_util::future::BoxFuture<
109				'_,
110				Result<nxd_plugin_protocol::v1::PlanResponse, tonic::Status>,
111			> {
112				Box::pin(async move {
113					<Self as nxd_plugin_protocol::v1::provider_service_server::ProviderService>::plan(
114						self,
115						tonic::Request::new(request),
116					)
117					.await
118					.map(tonic::Response::into_inner)
119				})
120			}
121
122			fn apply(
123				&self,
124				request: nxd_plugin_protocol::v1::ApplyRequest,
125			) -> futures_util::future::BoxFuture<
126				'_,
127				Result<
128					futures_util::stream::BoxStream<
129						'static,
130						Result<nxd_plugin_protocol::v1::ApplyResponse, tonic::Status>,
131					>,
132					tonic::Status,
133				>,
134			> {
135				use futures_util::StreamExt as _;
136				Box::pin(async move {
137					<Self as nxd_plugin_protocol::v1::provider_service_server::ProviderService>::apply(
138						self,
139						tonic::Request::new(request),
140					)
141					.await
142					.map(|response| response.into_inner().boxed())
143				})
144			}
145
146			fn verify(
147				&self,
148				request: nxd_plugin_protocol::v1::VerifyRequest,
149			) -> futures_util::future::BoxFuture<
150				'_,
151				Result<nxd_plugin_protocol::v1::VerifyResponse, tonic::Status>,
152			> {
153				Box::pin(async move {
154					<Self as nxd_plugin_protocol::v1::provider_service_server::ProviderService>::verify(
155						self,
156						tonic::Request::new(request),
157					)
158					.await
159					.map(tonic::Response::into_inner)
160				})
161			}
162
163			fn cancel(
164				&self,
165				request: nxd_plugin_protocol::v1::CancelRequest,
166			) -> futures_util::future::BoxFuture<
167				'_,
168				Result<nxd_plugin_protocol::v1::CancelResponse, tonic::Status>,
169			> {
170				Box::pin(async move {
171					<Self as nxd_plugin_protocol::v1::provider_service_server::ProviderService>::cancel(
172						self,
173						tonic::Request::new(request),
174					)
175					.await
176					.map(tonic::Response::into_inner)
177				})
178			}
179		}
180	};
181}
182
183#[derive(Clone, Copy, Debug, PartialEq, Eq)]
184pub enum ProviderErrorCategory {
185	InvalidRequest,
186	Incompatible,
187	Timeout,
188	Cancelled,
189	Provider,
190	Transport,
191	Internal,
192}
193
194#[derive(Clone, Debug, PartialEq, Eq)]
195pub struct ProviderFailure {
196	pub category: ProviderErrorCategory,
197	pub retryable: bool,
198	pub ambiguous: bool,
199	pub safe_message: String,
200}
201
202impl ProviderFailure {
203	/// A provider may nominate a failure for retry, but core retains a closed
204	/// policy over categories and never retries ambiguous identity/state.
205	pub const fn may_retry(&self) -> bool {
206		self.retryable
207			&& !self.ambiguous
208			&& matches!(
209				self.category,
210				ProviderErrorCategory::Timeout
211					| ProviderErrorCategory::Provider
212					| ProviderErrorCategory::Transport
213			)
214	}
215}