ipfs_api_prelude/
global_opts.rs

1// Copyright 2021 rust-ipfs-api Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7//
8
9use crate::{request::ApiRequest, Backend, BoxStream};
10use async_trait::async_trait;
11use bytes::Bytes;
12use common_multipart_rfc7578::client::multipart;
13use serde::{Serialize, Serializer};
14use std::time::Duration;
15
16/// Options valid on any IPFS Api request
17///
18/// Can be set on a client using [BackendWithGlobalOptions]
19///
20#[cfg_attr(feature = "with-builder", derive(TypedBuilder))]
21#[derive(Serialize, Default)]
22#[serde(rename_all = "kebab-case")]
23pub struct GlobalOptions {
24    #[cfg_attr(feature = "with-builder", builder(default, setter(strip_option)))]
25    pub offline: Option<bool>,
26
27    #[cfg_attr(feature = "with-builder", builder(default, setter(strip_option)))]
28    #[serde(serialize_with = "duration_as_secs_ns")]
29    pub timeout: Option<Duration>,
30}
31
32fn duration_as_secs_ns<S>(duration: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
33where
34    S: Serializer,
35{
36    match duration {
37        Some(duration) => serializer.serialize_str(&format!(
38            "{}s{}ns",
39            duration.as_secs(),
40            duration.subsec_nanos(),
41        )),
42        None => serializer.serialize_none(),
43    }
44}
45
46/// A wrapper for [Backend] / [IpfsApi](crate::api::IpfsApi) that adds global options
47pub struct BackendWithGlobalOptions<Back: Backend> {
48    backend: Back,
49    options: GlobalOptions,
50}
51
52#[derive(Serialize)]
53struct OptCombiner<'a, Req>
54where
55    Req: ApiRequest,
56{
57    #[serde(flatten)]
58    global: &'a GlobalOptions,
59
60    #[serde(flatten)]
61    request: Req,
62}
63
64impl<Back: Backend> BackendWithGlobalOptions<Back> {
65    /// Return the wrapped [Backend]
66    pub fn into_inner(self) -> Back {
67        self.backend
68    }
69
70    /// Construct
71    ///
72    /// While possible, it is not recommended to wrap a [Backend] twice.
73    pub fn new(backend: Back, options: GlobalOptions) -> Self {
74        Self { backend, options }
75    }
76
77    fn with_credentials<U, P>(self, username: U, password: P) -> Self
78    where
79        U: Into<String>,
80        P: Into<String>,
81    {
82        Self {
83            backend: self.backend.with_credentials(username, password),
84            options: self.options,
85        }
86    }
87
88    fn combine<Req>(&self, req: Req) -> OptCombiner<Req>
89    where
90        Req: ApiRequest,
91    {
92        OptCombiner {
93            global: &self.options,
94            request: req,
95        }
96    }
97}
98
99impl<'a, Req> ApiRequest for OptCombiner<'a, Req>
100where
101    Req: ApiRequest,
102{
103    const PATH: &'static str = <Req as ApiRequest>::PATH;
104
105    const METHOD: http::Method = http::Method::POST;
106}
107
108#[cfg(feature = "with-send-sync")]
109#[async_trait]
110impl<Back: Backend + Send + Sync> Backend for BackendWithGlobalOptions<Back> {
111    type HttpRequest = Back::HttpRequest;
112
113    type HttpResponse = Back::HttpResponse;
114
115    type Error = Back::Error;
116
117    fn with_credentials<U, P>(self, username: U, password: P) -> Self
118    where
119        U: Into<String>,
120        P: Into<String>,
121    {
122        (self as BackendWithGlobalOptions<Back>).with_credentials(username, password)
123    }
124
125    fn build_base_request<Req>(
126        &self,
127        req: Req,
128        form: Option<multipart::Form<'static>>,
129    ) -> Result<Self::HttpRequest, Self::Error>
130    where
131        Req: ApiRequest,
132    {
133        self.backend.build_base_request(self.combine(req), form)
134    }
135
136    fn get_header(
137        res: &Self::HttpResponse,
138        key: http::header::HeaderName,
139    ) -> Option<&http::HeaderValue> {
140        Back::get_header(res, key)
141    }
142
143    async fn request_raw<Req>(
144        &self,
145        req: Req,
146        form: Option<multipart::Form<'static>>,
147    ) -> Result<(http::StatusCode, bytes::Bytes), Self::Error>
148    where
149        Req: ApiRequest,
150    {
151        self.backend.request_raw(self.combine(req), form).await
152    }
153
154    fn response_to_byte_stream(res: Self::HttpResponse) -> BoxStream<Bytes, Self::Error> {
155        Back::response_to_byte_stream(res)
156    }
157
158    fn request_stream<Res, F>(
159        &self,
160        req: Self::HttpRequest,
161        process: F,
162    ) -> BoxStream<Res, Self::Error>
163    where
164        F: 'static + Send + Fn(Self::HttpResponse) -> BoxStream<Res, Self::Error>,
165    {
166        self.backend.request_stream(req, process)
167    }
168}
169
170#[cfg(not(feature = "with-send-sync"))]
171#[async_trait(?Send)]
172impl<Back: Backend> Backend for BackendWithGlobalOptions<Back> {
173    type HttpRequest = Back::HttpRequest;
174
175    type HttpResponse = Back::HttpResponse;
176
177    type Error = Back::Error;
178
179    fn with_credentials<U, P>(self, username: U, password: P) -> Self
180    where
181        U: Into<String>,
182        P: Into<String>,
183    {
184        (self as BackendWithGlobalOptions<Back>).with_credentials(username, password)
185    }
186
187    fn build_base_request<Req>(
188        &self,
189        req: Req,
190        form: Option<multipart::Form<'static>>,
191    ) -> Result<Self::HttpRequest, Self::Error>
192    where
193        Req: ApiRequest,
194    {
195        self.backend.build_base_request(self.combine(req), form)
196    }
197
198    fn get_header(
199        res: &Self::HttpResponse,
200        key: http::header::HeaderName,
201    ) -> Option<&http::HeaderValue> {
202        Back::get_header(res, key)
203    }
204
205    async fn request_raw<Req>(
206        &self,
207        req: Req,
208        form: Option<multipart::Form<'static>>,
209    ) -> Result<(http::StatusCode, bytes::Bytes), Self::Error>
210    where
211        Req: ApiRequest,
212    {
213        self.backend.request_raw(self.combine(req), form).await
214    }
215
216    fn response_to_byte_stream(res: Self::HttpResponse) -> BoxStream<Bytes, Self::Error> {
217        Back::response_to_byte_stream(res)
218    }
219
220    fn request_stream<Res, F>(
221        &self,
222        req: Self::HttpRequest,
223        process: F,
224    ) -> BoxStream<Res, Self::Error>
225    where
226        F: 'static + Send + Fn(Self::HttpResponse) -> BoxStream<Res, Self::Error>,
227    {
228        self.backend.request_stream(req, process)
229    }
230}