opendal_obs/services/chainsafe/
core.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19use std::fmt::Formatter;
20
21use bytes::Bytes;
22use http::header;
23use http::Request;
24use http::Response;
25use serde::Deserialize;
26use serde_json::json;
27
28use crate::raw::*;
29use crate::*;
30
31/// Core of [chainsafe](https://storage.chainsafe.io/) services support.
32#[derive(Clone)]
33pub struct ChainsafeCore {
34    /// The root of this core.
35    pub root: String,
36    /// The api_key of this core.
37    pub api_key: String,
38    /// The bucket id of this backend.
39    pub bucket_id: String,
40
41    pub client: HttpClient,
42}
43
44impl Debug for ChainsafeCore {
45    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
46        f.debug_struct("Backend")
47            .field("root", &self.root)
48            .field("bucket_id", &self.bucket_id)
49            .finish_non_exhaustive()
50    }
51}
52
53impl ChainsafeCore {
54    #[inline]
55    pub async fn send(&self, req: Request<Buffer>) -> Result<Response<Buffer>> {
56        self.client.send(req).await
57    }
58}
59
60impl ChainsafeCore {
61    pub async fn download_object(
62        &self,
63        path: &str,
64        range: BytesRange,
65    ) -> Result<Response<HttpBody>> {
66        let path = build_abs_path(&self.root, path);
67
68        let url = format!(
69            "https://api.chainsafe.io/api/v1/bucket/{}/download",
70            self.bucket_id
71        );
72
73        let req_body = &json!({
74            "path": path,
75        });
76        let body = Buffer::from(Bytes::from(req_body.to_string()));
77
78        let req = Request::post(url)
79            .header(
80                header::AUTHORIZATION,
81                format_authorization_by_bearer(&self.api_key)?,
82            )
83            .header(header::RANGE, range.to_header())
84            .header(header::CONTENT_TYPE, "application/json")
85            .body(body)
86            .map_err(new_request_build_error)?;
87
88        self.client.fetch(req).await
89    }
90
91    pub async fn object_info(&self, path: &str) -> Result<Response<Buffer>> {
92        let path = build_abs_path(&self.root, path);
93
94        let url = format!(
95            "https://api.chainsafe.io/api/v1/bucket/{}/file",
96            self.bucket_id
97        );
98
99        let req_body = &json!({
100            "path": path,
101        });
102
103        let body = Buffer::from(Bytes::from(req_body.to_string()));
104
105        let req = Request::post(url)
106            .header(
107                header::AUTHORIZATION,
108                format_authorization_by_bearer(&self.api_key)?,
109            )
110            .header(header::CONTENT_TYPE, "application/json")
111            .body(body)
112            .map_err(new_request_build_error)?;
113
114        self.send(req).await
115    }
116
117    pub async fn delete_object(&self, path: &str) -> Result<Response<Buffer>> {
118        let path = build_abs_path(&self.root, path);
119
120        let url = format!(
121            "https://api.chainsafe.io/api/v1/bucket/{}/rm",
122            self.bucket_id
123        );
124
125        let req_body = &json!({
126            "paths": vec![path],
127        });
128
129        let body = Buffer::from(Bytes::from(req_body.to_string()));
130
131        let req = Request::post(url)
132            .header(
133                header::AUTHORIZATION,
134                format_authorization_by_bearer(&self.api_key)?,
135            )
136            .header(header::CONTENT_TYPE, "application/json")
137            .body(body)
138            .map_err(new_request_build_error)?;
139
140        self.send(req).await
141    }
142
143    pub async fn upload_object(&self, path: &str, bs: Buffer) -> Result<Response<Buffer>> {
144        let path = build_abs_path(&self.root, path);
145
146        let url = format!(
147            "https://api.chainsafe.io/api/v1/bucket/{}/upload",
148            self.bucket_id
149        );
150
151        let file_part = FormDataPart::new("file").content(bs);
152
153        let multipart = Multipart::new()
154            .part(file_part)
155            .part(FormDataPart::new("path").content(path));
156
157        let req = Request::post(url).header(
158            header::AUTHORIZATION,
159            format_authorization_by_bearer(&self.api_key)?,
160        );
161
162        let req = multipart.apply(req)?;
163
164        self.send(req).await
165    }
166
167    pub async fn list_objects(&self, path: &str) -> Result<Response<Buffer>> {
168        let path = build_abs_path(&self.root, path);
169
170        let url = format!(
171            "https://api.chainsafe.io/api/v1/bucket/{}/ls",
172            self.bucket_id
173        );
174
175        let req_body = &json!({
176            "path": path,
177        });
178
179        let body = Buffer::from(Bytes::from(req_body.to_string()));
180
181        let req = Request::post(url)
182            .header(
183                header::AUTHORIZATION,
184                format_authorization_by_bearer(&self.api_key)?,
185            )
186            .header(header::CONTENT_TYPE, "application/json")
187            .body(body)
188            .map_err(new_request_build_error)?;
189
190        self.send(req).await
191    }
192
193    pub async fn create_dir(&self, path: &str) -> Result<Response<Buffer>> {
194        let path = build_abs_path(&self.root, path);
195
196        let url = format!(
197            "https://api.chainsafe.io/api/v1/bucket/{}/mkdir",
198            self.bucket_id
199        );
200
201        let req_body = &json!({
202            "path": path,
203        });
204
205        let body = Buffer::from(Bytes::from(req_body.to_string()));
206
207        let req = Request::post(url)
208            .header(
209                header::AUTHORIZATION,
210                format_authorization_by_bearer(&self.api_key)?,
211            )
212            .header(header::CONTENT_TYPE, "application/json")
213            .body(body)
214            .map_err(new_request_build_error)?;
215
216        self.send(req).await
217    }
218}
219
220#[derive(Debug, Deserialize)]
221pub struct Info {
222    pub name: String,
223    pub content_type: String,
224    pub size: u64,
225}
226
227#[derive(Deserialize)]
228pub struct ObjectInfoResponse {
229    pub content: Info,
230}
231
232pub(super) fn parse_info(info: Info) -> Metadata {
233    let mode = if info.content_type == "application/chainsafe-files-directory" {
234        EntryMode::DIR
235    } else {
236        EntryMode::FILE
237    };
238
239    let mut md = Metadata::new(mode);
240
241    md.set_content_length(info.size)
242        .set_content_type(&info.content_type);
243
244    md
245}