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}