Skip to main content

edc_connector_client/
client.rs

1mod edc_connector_api_version;
2
3pub use edc_connector_api_version::EdcConnectorApiVersion;
4use reqwest::{Client, RequestBuilder, Response};
5use serde::{de::DeserializeOwned, Serialize};
6use std::{future::Future, sync::Arc};
7
8use crate::{
9    api::{
10        AssetApi, CatalogApi, CommonExpressionLanguageApi, ContractAgreementApi,
11        ContractDefinitionApi, ContractNegotiationApi, DataPlaneApi, EdrApi, ParticipantContextApi,
12        ParticipantContextConfigApi, PolicyApi, SecretsApi, TransferProcessApi,
13    },
14    error::{
15        BuilderError, ManagementApiError, ManagementApiErrorDetail, ManagementApiErrorDetailKind,
16    },
17    types::context::WithContextRef,
18    Auth, EdcResult, Error,
19};
20
21#[derive(Clone)]
22pub struct EdcConnectorClient(Arc<EdcConnectorClientInternal>);
23
24#[allow(unused)]
25pub enum ApiTarget {
26    Participant,
27    Admin,
28}
29
30pub(crate) struct EdcConnectorClientInternal {
31    client: Client,
32    pub(crate) management_url: String,
33    pub(crate) auth: Auth,
34    pub(crate) participant_context: Option<String>,
35}
36
37impl EdcConnectorClientInternal {
38    pub(crate) fn new(
39        client: Client,
40        management_url: String,
41        auth: Auth,
42        participant_context: Option<String>,
43    ) -> Self {
44        Self {
45            client,
46            management_url,
47            auth,
48            participant_context,
49        }
50    }
51
52    pub(crate) async fn get<R: DeserializeOwned>(&self, path: impl AsRef<str>) -> EdcResult<R> {
53        let response = self
54            .client
55            .get(path.as_ref())
56            .authenticated(&self.auth)
57            .await?
58            .send()
59            .await?;
60
61        self.handle_response(response, as_json).await
62    }
63
64    pub(crate) async fn put(&self, path: impl AsRef<str>, body: &impl Serialize) -> EdcResult<()> {
65        let response = self
66            .client
67            .put(path.as_ref())
68            .json(body)
69            .authenticated(&self.auth)
70            .await?
71            .send()
72            .await?;
73
74        self.handle_response(response, empty).await
75    }
76
77    pub(crate) async fn del(&self, path: impl AsRef<str>) -> EdcResult<()> {
78        let response = self
79            .client
80            .delete(path.as_ref())
81            .authenticated(&self.auth)
82            .await?
83            .send()
84            .await?;
85
86        self.handle_response(response, empty).await
87    }
88
89    pub(crate) async fn post<I: Serialize, R: DeserializeOwned>(
90        &self,
91        path: impl AsRef<str>,
92        body: &I,
93    ) -> EdcResult<R> {
94        self.internal_post(path, body, as_json).await
95    }
96
97    pub(crate) async fn put_no_response<I: Serialize>(
98        &self,
99        path: impl AsRef<str>,
100        body: &I,
101    ) -> EdcResult<()> {
102        self.internal_put(path, body, empty).await
103    }
104
105    pub(crate) async fn post_no_response<I: Serialize>(
106        &self,
107        path: impl AsRef<str>,
108        body: &I,
109    ) -> EdcResult<()> {
110        self.internal_post(path, body, empty).await
111    }
112
113    async fn internal_put<I, F, Fut, R>(
114        &self,
115        path: impl AsRef<str>,
116        body: &I,
117        handler: F,
118    ) -> EdcResult<R>
119    where
120        I: Serialize,
121        F: Fn(Response) -> Fut,
122        Fut: Future<Output = EdcResult<R>>,
123    {
124        let response = self
125            .client
126            .put(path.as_ref())
127            .json(body)
128            .authenticated(&self.auth)
129            .await?
130            .send()
131            .await?;
132
133        self.handle_response(response, handler).await
134    }
135
136    async fn internal_post<I, F, Fut, R>(
137        &self,
138        path: impl AsRef<str>,
139        body: &I,
140        handler: F,
141    ) -> EdcResult<R>
142    where
143        I: Serialize,
144        F: Fn(Response) -> Fut,
145        Fut: Future<Output = EdcResult<R>>,
146    {
147        let response = self
148            .client
149            .post(path.as_ref())
150            .json(body)
151            .authenticated(&self.auth)
152            .await?
153            .send()
154            .await?;
155
156        self.handle_response(response, handler).await
157    }
158
159    async fn handle_response<F, Fut, R>(&self, response: Response, handler: F) -> EdcResult<R>
160    where
161        F: Fn(Response) -> Fut,
162        Fut: Future<Output = EdcResult<R>>,
163    {
164        if response.status().is_success() {
165            handler(response).await
166        } else {
167            let status = response.status();
168            let text = response.text().await?;
169
170            let err = match serde_json::from_str::<Vec<ManagementApiErrorDetail>>(&text) {
171                Ok(parsed) => ManagementApiErrorDetailKind::Parsed(parsed),
172                Err(_) => ManagementApiErrorDetailKind::Raw(text),
173            };
174
175            Err(Error::ManagementApi(ManagementApiError {
176                status_code: status,
177                error_detail: err,
178            }))
179        }
180    }
181
182    pub(crate) fn path_for(&self, version: EdcConnectorApiVersion, paths: &[&str]) -> String {
183        self.path_for_target(ApiTarget::Participant, version, paths)
184    }
185
186    pub(crate) fn path_for_target(
187        &self,
188        target: ApiTarget,
189        mut version: EdcConnectorApiVersion,
190        paths: &[&str],
191    ) -> String {
192        let base: &[&str] = if let Some(participant_context) = &self.participant_context {
193            version = EdcConnectorApiVersion::V4Alpha;
194
195            match target {
196                ApiTarget::Participant => &[
197                    self.management_url.as_str(),
198                    version.as_str(),
199                    "participants",
200                    participant_context.as_str(),
201                ],
202                ApiTarget::Admin => &[self.management_url.as_str(), version.as_str()],
203            }
204        } else {
205            &[self.management_url.as_str(), version.as_str()]
206        };
207
208        base.iter()
209            .chain(paths.iter())
210            .copied()
211            .collect::<Vec<_>>()
212            .join("/")
213    }
214
215    pub(crate) fn context_for<'a, T>(
216        &'a self,
217        version: EdcConnectorApiVersion,
218        body: &'a T,
219    ) -> WithContextRef<'a, T> {
220        self.context_for_with_opts(version, body, false)
221    }
222
223    pub(crate) fn context_for_with_opts<'a, T>(
224        &'a self,
225        version: EdcConnectorApiVersion,
226        body: &'a T,
227        include_odrl: bool,
228    ) -> WithContextRef<'a, T> {
229        match version {
230            EdcConnectorApiVersion::V3 => {
231                if include_odrl {
232                    WithContextRef::odrl_context(body)
233                } else {
234                    WithContextRef::default_context(body)
235                }
236            }
237            EdcConnectorApiVersion::V4Alpha => WithContextRef::edc_v4_context(body),
238            EdcConnectorApiVersion::V4 => WithContextRef::edc_v4_context(body),
239            EdcConnectorApiVersion::V5Beta => WithContextRef::edc_v4_context(body),
240        }
241    }
242}
243
244async fn as_json<R: DeserializeOwned>(response: Response) -> EdcResult<R> {
245    response.json().await.map(Ok)?
246}
247
248async fn empty(_response: Response) -> EdcResult<()> {
249    Ok(())
250}
251
252impl EdcConnectorClient {
253    pub(crate) fn new(
254        client: Client,
255        management_url: String,
256        auth: Auth,
257        participant_context: Option<String>,
258    ) -> Self {
259        Self(Arc::new(EdcConnectorClientInternal::new(
260            client,
261            management_url,
262            auth,
263            participant_context,
264        )))
265    }
266
267    pub fn builder() -> EdcClientConnectorBuilder {
268        EdcClientConnectorBuilder::default()
269    }
270
271    pub fn assets(&self, version: EdcConnectorApiVersion) -> AssetApi<'_> {
272        AssetApi::new(&self.0, version)
273    }
274
275    pub fn policies(&self, version: EdcConnectorApiVersion) -> PolicyApi<'_> {
276        PolicyApi::new(&self.0, version)
277    }
278
279    pub fn contract_definitions(
280        &self,
281        version: EdcConnectorApiVersion,
282    ) -> ContractDefinitionApi<'_> {
283        ContractDefinitionApi::new(&self.0, version)
284    }
285
286    pub fn catalogue(&self, version: EdcConnectorApiVersion) -> CatalogApi<'_> {
287        CatalogApi::new(&self.0, version)
288    }
289
290    pub fn contract_negotiations(
291        &self,
292        version: EdcConnectorApiVersion,
293    ) -> ContractNegotiationApi<'_> {
294        ContractNegotiationApi::new(&self.0, version)
295    }
296
297    pub fn contract_agreements(&self, version: EdcConnectorApiVersion) -> ContractAgreementApi<'_> {
298        ContractAgreementApi::new(&self.0, version)
299    }
300
301    pub fn transfer_processes(&self, version: EdcConnectorApiVersion) -> TransferProcessApi<'_> {
302        TransferProcessApi::new(&self.0, version)
303    }
304
305    pub fn data_planes(&self, version: EdcConnectorApiVersion) -> DataPlaneApi<'_> {
306        DataPlaneApi::new(&self.0, version)
307    }
308
309    pub fn edrs(&self, version: EdcConnectorApiVersion) -> EdrApi<'_> {
310        EdrApi::new(&self.0, version)
311    }
312
313    pub fn secrets(&self, version: EdcConnectorApiVersion) -> SecretsApi<'_> {
314        SecretsApi::new(&self.0, version)
315    }
316
317    pub fn participants(&self, version: EdcConnectorApiVersion) -> ParticipantContextApi<'_> {
318        ParticipantContextApi::new(&self.0, version)
319    }
320
321    pub fn participant_configs(
322        &self,
323        version: EdcConnectorApiVersion,
324    ) -> ParticipantContextConfigApi<'_> {
325        ParticipantContextConfigApi::new(&self.0, version)
326    }
327
328    pub fn common_expression_language(
329        &self,
330        version: EdcConnectorApiVersion,
331    ) -> CommonExpressionLanguageApi<'_> {
332        CommonExpressionLanguageApi::new(&self.0, version)
333    }
334}
335
336pub struct EdcClientConnectorBuilder {
337    management_url: Option<String>,
338    auth: Auth,
339    participant_context: Option<String>,
340}
341
342impl EdcClientConnectorBuilder {
343    pub fn management_url(mut self, url: impl Into<String>) -> Self {
344        self.management_url = Some(url.into());
345        self
346    }
347
348    pub fn with_auth(mut self, auth: Auth) -> Self {
349        self.auth = auth;
350        self
351    }
352
353    pub fn participant_context(mut self, participant_context: impl Into<String>) -> Self {
354        self.participant_context = Some(participant_context.into());
355        self
356    }
357
358    pub fn maybe_participant_context(
359        mut self,
360        participant_context: Option<impl Into<String>>,
361    ) -> Self {
362        self.participant_context = participant_context.map(|s| s.into());
363        self
364    }
365
366    pub fn build(self) -> Result<EdcConnectorClient, BuilderError> {
367        let url = self
368            .management_url
369            .ok_or_else(|| BuilderError::missing_property("management_url"))?;
370        let client = Client::new();
371
372        Ok(EdcConnectorClient::new(
373            client,
374            url,
375            self.auth,
376            self.participant_context,
377        ))
378    }
379}
380
381impl Default for EdcClientConnectorBuilder {
382    fn default() -> Self {
383        Self {
384            management_url: Default::default(),
385            auth: Auth::NoAuth,
386            participant_context: None,
387        }
388    }
389}
390
391trait BuilderExt: Sized {
392    fn authenticated(self, auth: &Auth) -> impl Future<Output = EdcResult<Self>>;
393}
394
395impl BuilderExt for RequestBuilder {
396    async fn authenticated(self, auth: &Auth) -> EdcResult<Self> {
397        match auth {
398            Auth::NoAuth => Ok(self),
399            Auth::ApiToken(token) => Ok(self.header("X-Api-Key", token)),
400            Auth::OAuth2(client) => {
401                Ok(self.header("Authorization", format!("Bearer {}", client.token().await?)))
402            }
403            Auth::BearerToken(token) => {
404                Ok(self.header("Authorization", format!("Bearer {}", token)))
405            }
406        }
407    }
408}