izihawa_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 combine<Req>(&self, req: Req) -> OptCombiner<Req>
78    where
79        Req: ApiRequest,
80    {
81        OptCombiner {
82            global: &self.options,
83            request: req,
84        }
85    }
86}
87
88impl<'a, Req> ApiRequest for OptCombiner<'a, Req>
89where
90    Req: ApiRequest,
91{
92    const PATH: &'static str = <Req as ApiRequest>::PATH;
93
94    const METHOD: http::Method = http::Method::POST;
95}
96
97#[cfg(feature = "with-send-sync")]
98#[async_trait]
99impl<Back: Backend + Send + Sync> Backend for BackendWithGlobalOptions<Back> {
100    type HttpRequest = Back::HttpRequest;
101
102    type HttpResponse = Back::HttpResponse;
103
104    type Error = Back::Error;
105
106    fn build_base_request<Req>(
107        &self,
108        req: Req,
109        form: Option<multipart::Form<'static>>,
110    ) -> Result<Self::HttpRequest, Self::Error>
111    where
112        Req: ApiRequest,
113    {
114        self.backend.build_base_request(self.combine(req), form)
115    }
116
117    fn get_header(
118        res: &Self::HttpResponse,
119        key: http::header::HeaderName,
120    ) -> Option<&http::HeaderValue> {
121        Back::get_header(res, key)
122    }
123
124    async fn request_raw<Req>(
125        &self,
126        req: Req,
127        form: Option<multipart::Form<'static>>,
128    ) -> Result<(http::StatusCode, bytes::Bytes), Self::Error>
129    where
130        Req: ApiRequest,
131    {
132        self.backend.request_raw(self.combine(req), form).await
133    }
134
135    fn response_to_byte_stream(res: Self::HttpResponse) -> BoxStream<Bytes, Self::Error> {
136        Back::response_to_byte_stream(res)
137    }
138
139    fn request_stream<Res, F>(
140        &self,
141        req: Self::HttpRequest,
142        process: F,
143    ) -> BoxStream<Res, Self::Error>
144    where
145        F: 'static + Send + Fn(Self::HttpResponse) -> BoxStream<Res, Self::Error>,
146    {
147        self.backend.request_stream(req, process)
148    }
149}
150
151#[cfg(not(feature = "with-send-sync"))]
152#[async_trait(?Send)]
153impl<Back: Backend> Backend for BackendWithGlobalOptions<Back> {
154    type HttpRequest = Back::HttpRequest;
155
156    type HttpResponse = Back::HttpResponse;
157
158    type Error = Back::Error;
159
160    fn build_base_request<Req>(
161        &self,
162        req: Req,
163        form: Option<multipart::Form<'static>>,
164    ) -> Result<Self::HttpRequest, Self::Error>
165    where
166        Req: ApiRequest,
167    {
168        self.backend.build_base_request(self.combine(req), form)
169    }
170
171    fn get_header(
172        res: &Self::HttpResponse,
173        key: http::header::HeaderName,
174    ) -> Option<&http::HeaderValue> {
175        Back::get_header(res, key)
176    }
177
178    async fn request_raw<Req>(
179        &self,
180        req: Req,
181        form: Option<multipart::Form<'static>>,
182    ) -> Result<(http::StatusCode, bytes::Bytes), Self::Error>
183    where
184        Req: ApiRequest,
185    {
186        self.backend.request_raw(self.combine(req), form).await
187    }
188
189    fn response_to_byte_stream(res: Self::HttpResponse) -> BoxStream<Bytes, Self::Error> {
190        Back::response_to_byte_stream(res)
191    }
192
193    fn request_stream<Res, F>(
194        &self,
195        req: Self::HttpRequest,
196        process: F,
197    ) -> BoxStream<Res, Self::Error>
198    where
199        F: 'static + Send + Fn(Self::HttpResponse) -> BoxStream<Res, Self::Error>,
200    {
201        self.backend.request_stream(req, process)
202    }
203}