opendal/services/github/
backend.rs1use 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 super::DEFAULT_SCHEME;
35use crate::raw::*;
36use crate::services::GithubConfig;
37use crate::*;
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 type Config = GithubConfig;
123
124 fn build(self) -> Result<impl Access> {
126 debug!("backend build started: {:?}", &self);
127
128 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
129 debug!("backend use root {}", &root);
130
131 if self.config.owner.is_empty() {
133 return Err(Error::new(ErrorKind::ConfigInvalid, "owner is empty")
134 .with_operation("Builder::build")
135 .with_context("service", Scheme::Github));
136 }
137
138 debug!("backend use owner {}", &self.config.owner);
139
140 if self.config.repo.is_empty() {
142 return Err(Error::new(ErrorKind::ConfigInvalid, "repo is empty")
143 .with_operation("Builder::build")
144 .with_context("service", Scheme::Github));
145 }
146
147 debug!("backend use repo {}", &self.config.repo);
148
149 Ok(GithubBackend {
150 core: Arc::new(GithubCore {
151 info: {
152 let am = AccessorInfo::default();
153 am.set_scheme(DEFAULT_SCHEME)
154 .set_root(&root)
155 .set_native_capability(Capability {
156 stat: true,
157
158 read: true,
159
160 create_dir: true,
161
162 write: true,
163 write_can_empty: true,
164
165 delete: true,
166
167 list: true,
168 list_with_recursive: true,
169
170 shared: true,
171
172 ..Default::default()
173 });
174
175 #[allow(deprecated)]
177 if let Some(client) = self.http_client {
178 am.update_http_client(|_| client);
179 }
180
181 am.into()
182 },
183 root,
184 token: self.config.token.clone(),
185 owner: self.config.owner.clone(),
186 repo: self.config.repo.clone(),
187 }),
188 })
189 }
190}
191
192#[derive(Debug, Clone)]
194pub struct GithubBackend {
195 core: Arc<GithubCore>,
196}
197
198impl Access for GithubBackend {
199 type Reader = HttpBody;
200 type Writer = GithubWriters;
201 type Lister = oio::PageLister<GithubLister>;
202 type Deleter = oio::OneShotDeleter<GithubDeleter>;
203
204 fn info(&self) -> Arc<AccessorInfo> {
205 self.core.info.clone()
206 }
207
208 async fn create_dir(&self, path: &str, _: OpCreateDir) -> Result<RpCreateDir> {
209 let empty_bytes = Buffer::new();
210
211 let resp = self
212 .core
213 .upload(&format!("{path}.gitkeep"), empty_bytes)
214 .await?;
215
216 let status = resp.status();
217
218 match status {
219 StatusCode::OK | StatusCode::CREATED => Ok(RpCreateDir::default()),
220 _ => Err(parse_error(resp)),
221 }
222 }
223
224 async fn stat(&self, path: &str, _args: OpStat) -> Result<RpStat> {
225 let resp = self.core.stat(path).await?;
226
227 let status = resp.status();
228
229 match status {
230 StatusCode::OK => {
231 let body = resp.into_body();
232 let resp: Entry =
233 serde_json::from_reader(body.reader()).map_err(new_json_deserialize_error)?;
234
235 let m = if resp.type_field == "dir" {
236 Metadata::new(EntryMode::DIR)
237 } else {
238 Metadata::new(EntryMode::FILE)
239 .with_content_length(resp.size)
240 .with_etag(resp.sha)
241 };
242
243 Ok(RpStat::new(m))
244 }
245 _ => Err(parse_error(resp)),
246 }
247 }
248
249 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
250 let resp = self.core.get(path, args.range()).await?;
251
252 let status = resp.status();
253
254 match status {
255 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
256 Ok((RpRead::default(), resp.into_body()))
257 }
258 _ => {
259 let (part, mut body) = resp.into_parts();
260 let buf = body.to_buffer().await?;
261 Err(parse_error(Response::from_parts(part, buf)))
262 }
263 }
264 }
265
266 async fn write(&self, path: &str, _args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
267 let writer = GithubWriter::new(self.core.clone(), path.to_string());
268
269 let w = oio::OneShotWriter::new(writer);
270
271 Ok((RpWrite::default(), w))
272 }
273
274 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
275 Ok((
276 RpDelete::default(),
277 oio::OneShotDeleter::new(GithubDeleter::new(self.core.clone())),
278 ))
279 }
280
281 async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
282 let l = GithubLister::new(self.core.clone(), path, args.recursive());
283 Ok((RpList::default(), oio::PageLister::new(l)))
284 }
285}