1use crate::plugin_protocol::v1;
7use futures_util::{future::BoxFuture, stream::BoxStream};
8
9pub 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#[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 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}