blob: e56380e0a0a8f9af6e7bba8f3ffb8ee24788c240 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#[macro_use]
extern crate napi_derive;
use std::collections::HashMap;
use std::fmt::Display;
use std::io::Read;
use std::time::Duration;
use futures::AsyncReadExt;
use futures::TryStreamExt;
use napi::bindgen_prelude::*;
use opendal::options::{
DeleteOptions, ListOptions, ReadOptions, ReaderOptions, StatOptions, WriteOptions,
};
use crate::layer::Layer;
mod capability;
mod layer;
mod options;
#[napi]
pub struct Operator {
async_op: opendal::Operator,
blocking_op: opendal::blocking::Operator,
}
#[napi]
impl Operator {
/// @see For the full list of scheme, see https://docs.rs/opendal/latest/opendal/services/index.html
/// And the options,
/// please refer to the documentation of the corresponding service for the corresponding parameters.
/// Note that the current options key is snake_case.
#[napi(constructor, async_runtime)]
pub fn new(scheme: String, options: Option<HashMap<String, String>>) -> Result<Self> {
let options = options.unwrap_or_default();
let async_op = opendal::Operator::via_iter(scheme, options).map_err(format_napi_error)?;
let blocking_op =
opendal::blocking::Operator::new(async_op.clone()).map_err(format_napi_error)?;
Ok(Operator {
async_op,
blocking_op,
})
}
/// Get current operator(service)'s full capability.
#[napi]
pub fn capability(&self) -> Result<capability::Capability> {
Ok(capability::Capability::new(
self.async_op.info().full_capability(),
))
}
/// Get current path's metadata **without cache** directly.
///
/// ### Notes
/// Use stat if you:
///
/// - Want to detect the outside changes of a path.
/// - Don’t want to read from cached metadata.
///
/// You may want to use `metadata` if you are working with entries returned by `Lister`. It’s highly possible that metadata you want has already been cached.
///
/// ### Example
/// ```javascript
/// const meta = await op.stat("test");
/// if (meta.isDir) {
/// // do something
/// }
/// ```
#[napi]
pub async fn stat(
&self,
path: String,
options: Option<options::StatOptions>,
) -> Result<Metadata> {
let options = options.map_or_else(StatOptions::default, StatOptions::from);
let meta = self
.async_op
.stat_options(&path, options)
.await
.map_err(format_napi_error)?;
Ok(Metadata(meta))
}
/// Get current path's metadata **without cache** directly and synchronously.
///
/// ### Example
/// ```javascript
/// const meta = op.statSync("test");
/// if (meta.isDir) {
/// // do something
/// }
/// ```
#[napi]
pub fn stat_sync(
&self,
path: String,
options: Option<options::StatOptions>,
) -> Result<Metadata> {
let options = options.map_or_else(StatOptions::default, StatOptions::from);
let meta = self
.blocking_op
.stat_options(&path, options)
.map_err(format_napi_error)?;
Ok(Metadata(meta))
}
/// Check if this operator can work correctly.
///
/// We will send a `list` request to the given path and return any errors we met.
///
/// ### Example
/// ```javascript
/// await op.check();
/// ```
#[napi]
pub async fn check(&self) -> Result<()> {
self.async_op.check().await.map_err(format_napi_error)
}
/// Check the op synchronously.
///
/// ### Example
/// ```javascript
/// op.checkSync();
/// ```
#[napi]
pub fn check_sync(&self) -> Result<()> {
self.blocking_op.check().map_err(format_napi_error)
}
/// Check if this path exists or not.
///
/// ### Example
/// ```javascript
/// await op.isExist("test");
/// ```
#[napi]
pub async fn exists(&self, path: String) -> Result<bool> {
self.async_op.exists(&path).await.map_err(format_napi_error)
}
/// Check if this path exists or not synchronously.
///
/// ### Example
/// ```javascript
/// op.isExistSync("test");
/// ```
#[napi]
pub fn exists_sync(&self, path: String) -> Result<bool> {
self.blocking_op.exists(&path).map_err(format_napi_error)
}
/// Create dir with a given path.
///
/// ### Example
/// ```javascript
/// await op.createDir("path/to/dir/");
/// ```
#[napi]
pub async fn create_dir(&self, path: String) -> Result<()> {
self.async_op
.create_dir(&path)
.await
.map_err(format_napi_error)
}
/// Create dir with a given path synchronously.
///
/// ### Example
/// ```javascript
/// op.createDirSync("path/to/dir/");
/// ```
#[napi]
pub fn create_dir_sync(&self, path: String) -> Result<()> {
self.blocking_op
.create_dir(&path)
.map_err(format_napi_error)
}
/// Read the whole path into a buffer.
///
/// ### Example
/// ```javascript
/// const buf = await op.read("path/to/file");
/// ```
#[napi]
pub async fn read(
&self,
path: String,
options: Option<options::ReadOptions>,
) -> Result<Buffer> {
let options = options.map_or_else(ReadOptions::default, ReadOptions::from);
let res = self
.async_op
.read_options(&path, options)
.await
.map_err(format_napi_error)?
.to_vec();
Ok(res.into())
}
/// Create a reader to read the given path.
///
/// It could be used to read large file in a streaming way.
#[napi]
pub async fn reader(
&self,
path: String,
options: Option<options::ReaderOptions>,
) -> Result<Reader> {
let options = options.map_or_else(ReaderOptions::default, ReaderOptions::from);
let r = self
.async_op
.reader_options(&path, options)
.await
.map_err(format_napi_error)?;
Ok(Reader {
inner: r
.into_futures_async_read(std::ops::RangeFull)
.await
.map_err(format_napi_error)?,
})
}
/// Read the whole path into a buffer synchronously.
///
/// ### Example
/// ```javascript
/// const buf = op.readSync("path/to/file");
/// ```
#[napi]
pub fn read_sync(&self, path: String, options: Option<options::ReadOptions>) -> Result<Buffer> {
let options = options.map_or_else(ReadOptions::default, ReadOptions::from);
let res = self
.blocking_op
.read_options(&path, options)
.map_err(format_napi_error)?
.to_vec();
Ok(res.into())
}
/// Create a reader to read the given path synchronously.
///
/// It could be used to read large file in a streaming way.
#[napi]
pub fn reader_sync(
&self,
path: String,
options: Option<options::ReaderOptions>,
) -> Result<BlockingReader> {
let options = options.map_or_else(ReaderOptions::default, ReaderOptions::from);
let r = self
.blocking_op
.reader_options(&path, options)
.map_err(format_napi_error)?;
Ok(BlockingReader {
inner: r.into_std_read(..).map_err(format_napi_error)?,
})
}
//noinspection DuplicatedCode
/// Write bytes into a path.
///
/// ### Example
/// ```javascript
/// await op.write("path/to/file", Buffer.from("hello world"));
/// // or
/// await op.write("path/to/file", "hello world");
/// // or
/// await op.write("path/to/file", Buffer.from("hello world"), { contentType: "text/plain" });
/// ```
#[napi]
pub async fn write(
&self,
path: String,
content: Either<Buffer, String>,
options: Option<options::WriteOptions>,
) -> Result<Metadata> {
let c = match content {
Either::A(buf) => buf.as_ref().to_owned(),
Either::B(s) => s.into_bytes(),
};
let options = options.map_or_else(WriteOptions::default, WriteOptions::from);
let metadata = self
.async_op
.write_options(&path, c, options)
.await
.map_err(format_napi_error)?;
Ok(Metadata(metadata))
}
//noinspection DuplicatedCode
/// Write multiple bytes into a path.
///
/// It could be used to write large file in a streaming way.
#[napi]
pub async fn writer(
&self,
path: String,
options: Option<options::WriteOptions>,
) -> Result<Writer> {
let options = options.unwrap_or_default();
let writer = self
.async_op
.writer_options(&path, options.into())
.await
.map_err(format_napi_error)?;
Ok(Writer(writer))
}
/// Write multiple bytes into a path synchronously.
///
/// It could be used to write large file in a streaming way.
#[napi]
pub fn writer_sync(
&self,
path: String,
options: Option<options::WriteOptions>,
) -> Result<BlockingWriter> {
let options = options.unwrap_or_default();
let writer = self
.blocking_op
.writer_options(&path, options.into())
.map_err(format_napi_error)?;
Ok(BlockingWriter(writer))
}
//noinspection DuplicatedCode
/// Write bytes into a path synchronously.
///
/// ### Example
/// ```javascript
/// op.writeSync("path/to/file", Buffer.from("hello world"));
/// // or
/// op.writeSync("path/to/file", "hello world");
/// // or
/// op.writeSync("path/to/file", Buffer.from("hello world"), { contentType: "text/plain" });
/// ```
#[napi]
pub fn write_sync(
&self,
path: String,
content: Either<Buffer, String>,
options: Option<options::WriteOptions>,
) -> Result<Metadata> {
let c = match content {
Either::A(buf) => buf.as_ref().to_owned(),
Either::B(s) => s.into_bytes(),
};
let options = options.map_or_else(WriteOptions::default, WriteOptions::from);
let metadata = self
.blocking_op
.write_options(&path, c, options)
.map_err(format_napi_error)?;
Ok(Metadata(metadata))
}
/// Copy file according to given `from` and `to` path.
///
/// ### Example
/// ```javascript
/// await op.copy("path/to/file", "path/to/dest");
/// ```
#[napi]
pub async fn copy(&self, from: String, to: String) -> Result<()> {
self.async_op
.copy(&from, &to)
.await
.map_err(format_napi_error)
}
/// Copy file according to given `from` and `to` path synchronously.
///
/// ### Example
/// ```javascript
/// op.copySync("path/to/file", "path/to/dest");
/// ```
#[napi]
pub fn copy_sync(&self, from: String, to: String) -> Result<()> {
self.blocking_op.copy(&from, &to).map_err(format_napi_error)
}
/// Rename file according to given `from` and `to` path.
///
/// It's similar to `mv` command.
///
/// ### Example
/// ```javascript
/// await op.rename("path/to/file", "path/to/dest");
/// ```
#[napi]
pub async fn rename(&self, from: String, to: String) -> Result<()> {
self.async_op
.rename(&from, &to)
.await
.map_err(format_napi_error)
}
/// Rename file according to given `from` and `to` path synchronously.
///
/// It's similar to `mv` command.
///
/// ### Example
/// ```javascript
/// op.renameSync("path/to/file", "path/to/dest");
/// ```
#[napi]
pub fn rename_sync(&self, from: String, to: String) -> Result<()> {
self.blocking_op
.rename(&from, &to)
.map_err(format_napi_error)
}
/// Delete the given path.
///
/// ### Notes
/// Delete not existing error won’t return errors.
///
/// ### Example
/// ```javascript
/// await op.delete("test");
/// ```
#[napi]
pub async fn delete(
&self,
path: String,
options: Option<options::DeleteOptions>,
) -> Result<()> {
let options = options.map_or_else(DeleteOptions::default, DeleteOptions::from);
self.async_op
.delete_options(&path, options)
.await
.map_err(format_napi_error)
}
/// Delete the given path synchronously.
///
/// ### Example
/// ```javascript
/// op.deleteSync("test");
/// ```
#[napi]
pub fn delete_sync(&self, path: String, options: Option<options::DeleteOptions>) -> Result<()> {
let options = options.map_or_else(DeleteOptions::default, DeleteOptions::from);
self.blocking_op
.delete_options(&path, options)
.map_err(format_napi_error)
}
/// Remove given paths.
///
/// ### Notes
/// If underlying services support delete in batch, we will use batch delete instead.
///
/// ### Examples
/// ```javascript
/// await op.remove(["abc", "def"]);
/// ```
#[napi]
pub async fn remove(&self, paths: Vec<String>) -> Result<()> {
self.async_op
.delete_iter(paths)
.await
.map_err(format_napi_error)
}
/// Remove given paths.
///
/// ### Notes
/// If underlying services support delete in batch, we will use batch delete instead.
///
/// ### Examples
/// ```javascript
/// op.removeSync(["abc", "def"]);
/// ```
#[napi]
pub fn remove_sync(&self, paths: Vec<String>) -> Result<()> {
self.blocking_op
.delete_iter(paths)
.map_err(format_napi_error)
}
/// Remove the path and all nested dirs and files recursively.
///
/// ### Notes
/// If underlying services support delete in batch, we will use batch delete instead.
///
/// ### Examples
/// ```javascript
/// await op.removeAll("path/to/dir/");
/// ```
#[napi]
pub async fn remove_all(&self, path: String) -> Result<()> {
self.async_op
.delete_with(&path)
.recursive(true)
.await
.map_err(format_napi_error)
}
/// Remove the path and all nested dirs and files recursively.
///
/// ### Notes
/// If underlying services support delete in batch, we will use batch delete instead.
///
/// ### Examples
/// ```javascript
/// op.removeAllSync("path/to/dir/");
/// ```
#[napi]
pub fn remove_all_sync(&self, path: String) -> Result<()> {
use opendal::options::DeleteOptions;
self.blocking_op
.delete_options(
&path,
DeleteOptions {
recursive: true,
..Default::default()
},
)
.map_err(format_napi_error)
}
/// List the given path.
///
/// This function will return an array of entries.
///
/// An error will be returned if given path doesn't end with `/`.
///
/// ### Example
///
/// ```javascript
/// const list = await op.list("path/to/dir/");
/// for (let entry of list) {
/// let meta = await op.stat(entry.path);
/// if (meta.isFile) {
/// // do something
/// }
/// }
/// ```
///
/// #### List recursively
///
/// With `recursive` option, you can list recursively.
///
/// ```javascript
/// const list = await op.list("path/to/dir/", { recursive: true });
/// for (let entry of list) {
/// let meta = await op.stat(entry.path);
/// if (meta.isFile) {
/// // do something
/// }
/// }
/// ```
#[napi]
pub async fn list(
&self,
path: String,
options: Option<options::ListOptions>,
) -> Result<Vec<Entry>> {
let options = options.map_or(ListOptions::default(), ListOptions::from);
let l = self
.async_op
.list_options(&path, options)
.await
.map_err(format_napi_error)?;
Ok(l.into_iter().map(Entry).collect())
}
/// List the given path synchronously.
///
/// This function will return an array of entries.
///
/// An error will be returned if given path doesn't end with `/`.
///
/// ### Example
///
/// ```javascript
/// const list = op.listSync("path/to/dir/");
/// for (let entry of list) {
/// let meta = op.statSync(entry.path);
/// if (meta.isFile) {
/// // do something
/// }
/// }
/// ```
///
/// #### List recursively
///
/// With `recursive` option, you can list recursively.
///
/// ```javascript
/// const list = op.listSync("path/to/dir/", { recursive: true });
/// for (let entry of list) {
/// let path = entry.path();
/// let meta = entry.metadata();
/// if (meta.isFile) {
/// // do something
/// }
/// }
/// ```
#[napi]
pub fn list_sync(
&self,
path: String,
options: Option<options::ListOptions>,
) -> Result<Vec<Entry>> {
let options = options.map_or(ListOptions::default(), ListOptions::from);
let l = self
.blocking_op
.list_options(&path, options)
.map_err(format_napi_error)?;
Ok(l.into_iter().map(Entry).collect())
}
/// Create a lister to list entries at given path.
///
/// This function returns a Lister that can be used to iterate over entries
/// in a streaming manner, which is more memory-efficient for large directories.
///
/// An error will be returned if given path doesn't end with `/`.
///
/// ### Example
///
/// ```javascript
/// const lister = await op.lister("path/to/dir/");
/// let entry;
/// while ((entry = await lister.next()) !== null) {
/// console.log(entry.path());
/// }
/// ```
///
/// #### List recursively
///
/// With `recursive` option, you can list recursively.
///
/// ```javascript
/// const lister = await op.lister("path/to/dir/", { recursive: true });
/// let entry;
/// while ((entry = await lister.next()) !== null) {
/// console.log(entry.path());
/// }
/// ```
#[napi]
pub async fn lister(
&self,
path: String,
options: Option<options::ListOptions>,
) -> Result<Lister> {
let options = options.map_or(ListOptions::default(), ListOptions::from);
let l = self
.async_op
.lister_options(&path, options)
.await
.map_err(format_napi_error)?;
Ok(Lister(l))
}
/// Create a lister to list entries at given path synchronously.
///
/// This function returns a BlockingLister that can be used to iterate over entries
/// in a streaming manner, which is more memory-efficient for large directories.
///
/// An error will be returned if given path doesn't end with `/`.
///
/// ### Example
///
/// ```javascript
/// const lister = op.listerSync("path/to/dir/");
/// let entry;
/// while ((entry = lister.next()) !== null) {
/// console.log(entry.path());
/// }
/// ```
///
/// #### List recursively
///
/// With `recursive` option, you can list recursively.
///
/// ```javascript
/// const lister = op.listerSync("path/to/dir/", { recursive: true });
/// let entry;
/// while ((entry = lister.next()) !== null) {
/// console.log(entry.path());
/// }
/// ```
#[napi]
pub fn lister_sync(
&self,
path: String,
options: Option<options::ListOptions>,
) -> Result<BlockingLister> {
let options = options.map_or(ListOptions::default(), ListOptions::from);
let l = self
.blocking_op
.lister_options(&path, options)
.map_err(format_napi_error)?;
Ok(BlockingLister(l))
}
/// Get a presigned request for read.
///
/// Unit of `expires` is seconds.
///
/// ### Example
///
/// ```javascript
/// const req = await op.presignRead(path, parseInt(expires));
///
/// console.log("method: ", req.method);
/// console.log("url: ", req.url);
/// console.log("headers: ", req.headers);
/// ```
#[napi]
pub async fn presign_read(&self, path: String, expires: u32) -> Result<PresignedRequest> {
let res = self
.async_op
.presign_read(&path, Duration::from_secs(expires as u64))
.await
.map_err(format_napi_error)?;
Ok(PresignedRequest::new(res))
}
/// Get a presigned request for `write`.
///
/// Unit of `expires` is seconds.
///
/// ### Example
///
/// ```javascript
/// const req = await op.presignWrite(path, parseInt(expires));
///
/// console.log("method: ", req.method);
/// console.log("url: ", req.url);
/// console.log("headers: ", req.headers);
/// ```
#[napi]
pub async fn presign_write(&self, path: String, expires: u32) -> Result<PresignedRequest> {
let res = self
.async_op
.presign_write(&path, Duration::from_secs(expires as u64))
.await
.map_err(format_napi_error)?;
Ok(PresignedRequest::new(res))
}
/// Get a presigned request for stat.
///
/// Unit of `expires` is seconds.
///
/// ### Example
///
/// ```javascript
/// const req = await op.presignStat(path, parseInt(expires));
///
/// console.log("method: ", req.method);
/// console.log("url: ", req.url);
/// console.log("headers: ", req.headers);
/// ```
#[napi]
pub async fn presign_stat(&self, path: String, expires: u32) -> Result<PresignedRequest> {
let res = self
.async_op
.presign_stat(&path, Duration::from_secs(expires as u64))
.await
.map_err(format_napi_error)?;
Ok(PresignedRequest::new(res))
}
}
/// Entry returned by Lister or BlockingLister to represent a path, and it's a relative metadata.
#[napi]
pub struct Entry(opendal::Entry);
#[napi]
impl Entry {
/// Return the path of this entry.
#[napi]
pub fn path(&self) -> String {
self.0.path().to_string()
}
/// Return the metadata of this entry.
#[napi]
pub fn metadata(&self) -> Metadata {
Metadata(self.0.metadata().clone())
}
}
#[napi]
pub enum EntryMode {
/// FILE means the path has data to read.
FILE,
/// DIR means the path can be listed.
DIR,
/// Unknown means we don't know what we can do on this path.
Unknown,
}
impl From<opendal::EntryMode> for EntryMode {
fn from(mode: opendal::EntryMode) -> Self {
match mode {
opendal::EntryMode::FILE => EntryMode::FILE,
opendal::EntryMode::DIR => EntryMode::DIR,
opendal::EntryMode::Unknown => EntryMode::Unknown,
}
}
}
/// Metadata carries all metadata associated with a path.
#[napi]
pub struct Metadata(opendal::Metadata);
#[napi]
impl Metadata {
/// Returns true if the <op.stat> object describes a file system directory.
#[napi]
pub fn is_directory(&self) -> bool {
self.0.is_dir()
}
/// Returns true if the <op.stat> object describes a regular file.
#[napi]
pub fn is_file(&self) -> bool {
self.0.is_file()
}
/// This function returns `true` if the file represented by this metadata has been marked for
/// deletion or has been permanently deleted.
#[napi]
pub fn is_deleted(&self) -> bool {
self.0.is_deleted()
}
/// Cache-Control of this object.
#[napi(getter)]
pub fn cache_control(&self) -> Option<String> {
self.0.cache_control().map(|s| s.to_string())
}
/// Content-Disposition of this object
#[napi(getter)]
pub fn content_disposition(&self) -> Option<String> {
self.0.content_disposition().map(|s| s.to_string())
}
/// Content Length of this object
#[napi(getter)]
pub fn content_length(&self) -> Option<u64> {
self.0.content_length().into()
}
/// Content Encoding of this object
#[napi(getter)]
pub fn content_encoding(&self) -> Option<String> {
self.0.content_encoding().map(|s| s.to_string())
}
/// Content MD5 of this object.
#[napi(getter)]
pub fn content_md5(&self) -> Option<String> {
self.0.content_md5().map(|s| s.to_string())
}
/// Content Type of this object.
#[napi(getter)]
pub fn content_type(&self) -> Option<String> {
self.0.content_type().map(|s| s.to_string())
}
/// User Metadata of this object.
#[napi(getter)]
pub fn user_metadata(&self) -> Option<HashMap<String, String>> {
self.0.user_metadata().cloned()
}
/// ETag of this object.
#[napi(getter)]
pub fn etag(&self) -> Option<String> {
self.0.etag().map(|s| s.to_string())
}
/// Last Modified of this object.
///
/// We will output this time in RFC3339 format like `1996-12-19T16:39:57+08:00`.
#[napi(getter)]
pub fn last_modified(&self) -> Option<String> {
self.0.last_modified().map(|ta| ta.to_string())
}
/// mode represent this entry's mode.
#[napi(getter)]
pub fn mode(&self) -> Option<EntryMode> {
Some(self.0.mode().into())
}
/// Retrieves the `version` of the file, if available.
#[napi(getter)]
pub fn version(&self) -> Option<String> {
self.0.version().map(|v| v.to_string())
}
}
/// BlockingReader is designed to read data from a given path in a blocking
/// manner.
#[napi]
pub struct BlockingReader {
inner: opendal::blocking::StdReader,
}
#[napi]
impl BlockingReader {
#[napi]
pub fn read(&mut self, mut buf: Buffer) -> Result<usize> {
let buf = buf.as_mut();
let n = self.inner.read(buf).map_err(format_napi_error)?;
Ok(n)
}
}
/// Reader is designed to read data from a given path in an asynchronous
/// manner.
#[napi]
pub struct Reader {
inner: opendal::FuturesAsyncReader,
}
#[napi]
impl Reader {
/// # Safety
///
/// > &mut self in async napi methods should be marked as unsafe
///
/// Read bytes from this reader into given buffer.
#[napi]
pub async unsafe fn read(&mut self, mut buf: Buffer) -> Result<usize> {
let buf = buf.as_mut();
let n = self.inner.read(buf).await.map_err(format_napi_error)?;
Ok(n)
}
}
/// BlockingWriter is designed to write data into a given path in a blocking
/// manner.
#[napi]
pub struct BlockingWriter(opendal::blocking::Writer);
#[napi]
impl BlockingWriter {
/// # Safety
///
/// > &mut self in async napi methods should be marked as unsafe
///
/// Write bytes into this writer.
///
/// ### Example
/// ```javascript
/// const writer = await op.writer("path/to/file");
/// await writer.write(Buffer.from("hello world"));
/// await writer.close();
/// ```
#[napi]
pub unsafe fn write(&mut self, content: Either<Buffer, String>) -> Result<()> {
let c = match content {
Either::A(buf) => buf.as_ref().to_owned(),
Either::B(s) => s.into_bytes(),
};
self.0.write(c).map_err(format_napi_error)
}
/// # Safety
///
/// > &mut self in async napi methods should be marked as unsafe
///
/// Close this writer.
///
/// ### Example
///
/// ```javascript
/// const writer = op.writerSync("path/to/file");
/// writer.write(Buffer.from("hello world"));
/// writer.close();
/// ```
#[napi]
pub unsafe fn close(&mut self) -> Result<()> {
self.0.close().map(|_| ()).map_err(format_napi_error)
}
}
/// Writer is designed to write data into a given path in an asynchronous
/// manner.
#[napi]
pub struct Writer(opendal::Writer);
#[napi]
impl Writer {
/// # Safety
///
/// > &mut self in async napi methods should be marked as unsafe
///
/// Write bytes into this writer.
///
/// ### Example
/// ```javascript
/// const writer = await op.writer("path/to/file");
/// await writer.write(Buffer.from("hello world"));
/// await writer.close();
/// ```
#[napi]
pub async unsafe fn write(&mut self, content: Either<Buffer, String>) -> Result<()> {
let c = match content {
Either::A(buf) => buf.as_ref().to_owned(),
Either::B(s) => s.into_bytes(),
};
self.0.write(c).await.map_err(format_napi_error)
}
/// # Safety
///
/// > &mut self in async napi methods should be marked as unsafe
///
/// Close this writer.
///
/// ### Example
/// ```javascript
/// const writer = await op.writer("path/to/file");
/// await writer.write(Buffer.from("hello world"));
/// await writer.close();
/// ```
#[napi]
pub async unsafe fn close(&mut self) -> Result<()> {
self.0.close().await.map(|_| ()).map_err(format_napi_error)
}
}
/// Lister is designed to list entries at a given path in an asynchronous
/// manner.
#[napi]
pub struct Lister(opendal::Lister);
#[napi]
impl Lister {
/// # Safety
///
/// > &mut self in async napi methods should be marked as unsafe
///
/// napi will make sure the function is safe, and we didn't do unsafe
/// things internally.
#[napi]
pub async unsafe fn next(&mut self) -> Result<Option<Entry>> {
Ok(self
.0
.try_next()
.await
.map_err(format_napi_error)?
.map(Entry))
}
}
/// BlockingLister is designed to list entries at a given path in a blocking
/// manner.
#[napi]
pub struct BlockingLister(opendal::blocking::Lister);
/// Method `next` can be confused for the standard trait method `std::iter::Iterator::next`.
/// But in JavaScript, it is also customary to use the next method directly to get the next element.
/// Therefore, disable this clippy.
/// It can be removed after a complete implementation of `Generator`.
/// FYI: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator>
#[napi]
#[allow(clippy::should_implement_trait)]
impl BlockingLister {
#[napi]
pub fn next(&mut self) -> Result<Option<Entry>> {
match self.0.next() {
Some(Ok(entry)) => Ok(Some(Entry(entry))),
Some(Err(e)) => Err(format_napi_error(e)),
None => Ok(None),
}
}
}
/// PresignedRequest is a presigned request return by `presign`.
#[napi(object)]
pub struct PresignedRequest {
/// HTTP method of this request.
pub method: String,
/// URL of this request.
pub url: String,
/// HTTP headers of this request.
pub headers: HashMap<String, String>,
}
impl PresignedRequest {
pub fn new(req: opendal::raw::PresignedRequest) -> Self {
let method = req.method().to_string();
let url = req.uri().to_string();
let headers = req
.header()
.iter()
.map(|(k, v)| {
(
k.as_str().to_string(),
v.to_str()
.expect("header value contains non visible ascii characters")
.to_string(),
)
})
.collect();
Self {
method,
url,
headers,
}
}
}
#[napi]
impl Operator {
/// Add a layer to this operator.
#[napi(async_runtime)]
pub fn layer(&self, layer: &External<Layer>) -> Result<Self> {
let async_op = layer.inner.layer(self.async_op.clone());
let blocking_op =
opendal::blocking::Operator::new(async_op.clone()).map_err(format_napi_error)?;
Ok(Self {
async_op,
blocking_op,
})
}
}
/// Format opendal error to napi error.
///
/// FIXME: handle error correctly.
fn format_napi_error(err: impl Display) -> Error {
Error::from_reason(format!("{err}"))
}