opendal/services/github/
backend.rs
1use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::sync::Arc;
21
22use bytes::Buf;
23use http::Response;
24use http::StatusCode;
25use log::debug;
26
27use super::core::Entry;
28use super::core::GithubCore;
29use super::delete::GithubDeleter;
30use super::error::parse_error;
31use super::lister::GithubLister;
32use super::writer::GithubWriter;
33use super::writer::GithubWriters;
34use crate::raw::*;
35use crate::services::GithubConfig;
36use crate::*;
37
38impl Configurator for GithubConfig {
39 type Builder = GithubBuilder;
40
41 #[allow(deprecated)]
42 fn into_builder(self) -> Self::Builder {
43 GithubBuilder {
44 config: self,
45 http_client: None,
46 }
47 }
48}
49
50#[doc = include_str!("docs.md")]
52#[derive(Default)]
53pub struct GithubBuilder {
54 config: GithubConfig,
55
56 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
57 http_client: Option<HttpClient>,
58}
59
60impl Debug for GithubBuilder {
61 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
62 let mut d = f.debug_struct("GithubBuilder");
63
64 d.field("config", &self.config);
65 d.finish_non_exhaustive()
66 }
67}
68
69impl GithubBuilder {
70 pub fn root(mut self, root: &str) -> Self {
74 self.config.root = if root.is_empty() {
75 None
76 } else {
77 Some(root.to_string())
78 };
79
80 self
81 }
82
83 pub fn token(mut self, token: &str) -> Self {
87 if !token.is_empty() {
88 self.config.token = Some(token.to_string());
89 }
90 self
91 }
92
93 pub fn owner(mut self, owner: &str) -> Self {
95 self.config.owner = owner.to_string();
96
97 self
98 }
99
100 pub fn repo(mut self, repo: &str) -> Self {
102 self.config.repo = repo.to_string();
103
104 self
105 }
106
107 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
114 #[allow(deprecated)]
115 pub fn http_client(mut self, client: HttpClient) -> Self {
116 self.http_client = Some(client);
117 self
118 }
119}
120
121impl Builder for GithubBuilder {
122 const SCHEME: Scheme = Scheme::Github;
123 type Config = GithubConfig;
124
125 fn build(self) -> Result<impl Access> {
127 debug!("backend build started: {:?}", &self);
128
129 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
130 debug!("backend use root {}", &root);
131
132 if self.config.owner.is_empty() {
134 return Err(Error::new(ErrorKind::ConfigInvalid, "owner is empty")
135 .with_operation("Builder::build")
136 .with_context("service", Scheme::Github));
137 }
138
139 debug!("backend use owner {}", &self.config.owner);
140
141 if self.config.repo.is_empty() {
143 return Err(Error::new(ErrorKind::ConfigInvalid, "repo is empty")
144 .with_operation("Builder::build")
145 .with_context("service", Scheme::Github));
146 }
147
148 debug!("backend use repo {}", &self.config.repo);
149
150 Ok(GithubBackend {
151 core: Arc::new(GithubCore {
152 info: {
153 let am = AccessorInfo::default();
154 am.set_scheme(Scheme::Github)
155 .set_root(&root)
156 .set_native_capability(Capability {
157 stat: true,
158 stat_has_content_length: true,
159 stat_has_etag: true,
160
161 read: true,
162
163 create_dir: true,
164
165 write: true,
166 write_can_empty: true,
167
168 delete: true,
169
170 list: true,
171 list_with_recursive: true,
172 list_has_content_length: true,
173 list_has_etag: true,
174
175 shared: true,
176
177 ..Default::default()
178 });
179
180 #[allow(deprecated)]
182 if let Some(client) = self.http_client {
183 am.update_http_client(|_| client);
184 }
185
186 am.into()
187 },
188 root,
189 token: self.config.token.clone(),
190 owner: self.config.owner.clone(),
191 repo: self.config.repo.clone(),
192 }),
193 })
194 }
195}
196
197#[derive(Debug, Clone)]
199pub struct GithubBackend {
200 core: Arc<GithubCore>,
201}
202
203impl Access for GithubBackend {
204 type Reader = HttpBody;
205 type Writer = GithubWriters;
206 type Lister = oio::PageLister<GithubLister>;
207 type Deleter = oio::OneShotDeleter<GithubDeleter>;
208 type BlockingReader = ();
209 type BlockingWriter = ();
210 type BlockingLister = ();
211 type BlockingDeleter = ();
212
213 fn info(&self) -> Arc<AccessorInfo> {
214 self.core.info.clone()
215 }
216
217 async fn create_dir(&self, path: &str, _: OpCreateDir) -> Result<RpCreateDir> {
218 let empty_bytes = Buffer::new();
219
220 let resp = self
221 .core
222 .upload(&format!("{}.gitkeep", path), empty_bytes)
223 .await?;
224
225 let status = resp.status();
226
227 match status {
228 StatusCode::OK | StatusCode::CREATED => Ok(RpCreateDir::default()),
229 _ => Err(parse_error(resp)),
230 }
231 }
232
233 async fn stat(&self, path: &str, _args: OpStat) -> Result<RpStat> {
234 let resp = self.core.stat(path).await?;
235
236 let status = resp.status();
237
238 match status {
239 StatusCode::OK => {
240 let body = resp.into_body();
241 let resp: Entry =
242 serde_json::from_reader(body.reader()).map_err(new_json_deserialize_error)?;
243
244 let m = if resp.type_field == "dir" {
245 Metadata::new(EntryMode::DIR)
246 } else {
247 Metadata::new(EntryMode::FILE)
248 .with_content_length(resp.size)
249 .with_etag(resp.sha)
250 };
251
252 Ok(RpStat::new(m))
253 }
254 _ => Err(parse_error(resp)),
255 }
256 }
257
258 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
259 let resp = self.core.get(path, args.range()).await?;
260
261 let status = resp.status();
262
263 match status {
264 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
265 Ok((RpRead::default(), resp.into_body()))
266 }
267 _ => {
268 let (part, mut body) = resp.into_parts();
269 let buf = body.to_buffer().await?;
270 Err(parse_error(Response::from_parts(part, buf)))
271 }
272 }
273 }
274
275 async fn write(&self, path: &str, _args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
276 let writer = GithubWriter::new(self.core.clone(), path.to_string());
277
278 let w = oio::OneShotWriter::new(writer);
279
280 Ok((RpWrite::default(), w))
281 }
282
283 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
284 Ok((
285 RpDelete::default(),
286 oio::OneShotDeleter::new(GithubDeleter::new(self.core.clone())),
287 ))
288 }
289
290 async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
291 let l = GithubLister::new(self.core.clone(), path, args.recursive());
292 Ok((RpList::default(), oio::PageLister::new(l)))
293 }
294}