1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 🐻‍❄️🧶 remi-rs: Robust, and simple asynchronous Rust crate to handle storage-related communications with different storage providers
// Copyright (c) 2022-2024 Noelware, LLC. <team@noelware.org>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#![doc(html_logo_url = "https://cdn.floofy.dev/images/trans.png")]
#![doc = include_str!("../README.md")]

use std::path::Path;

// re-export (just in case!~)
#[doc(hidden)]
pub use async_trait::async_trait;

#[doc(hidden)]
pub use bytes::Bytes;

mod blob;
mod options;

pub use blob::*;
pub use options::*;

/// A storage service is a base primitive of `remi-rs`: it is the way to interact
/// with the storage providers in ways that you would commonly use files: open, deleting,
/// listing, etc.
#[async_trait]
pub trait StorageService: Send + Sync {
    /// Represents a generic error to use for errors that could be emitted
    /// when calling any function.
    type Error;

    /// The name of the storage service.
    const NAME: &'static str;

    /// Returns the name of this [`StorageService`].
    #[deprecated(
        since = "0.5.0",
        note = "use Self::NAME instead of the name() function, this will be removed in 0.7.0"
    )]
    fn name(&self) -> &'static str {
        Self::NAME
    }

    /// Optionally initialize this [`StorageService`] if it requires initialization,
    /// like creating a directory if it doesn't exist.
    ///
    /// * since 0.1.0
    async fn init(&self) -> Result<(), Self::Error> {
        Ok(())
    }

    /// Opens a file in the specified `path` and returns the contents as [`Bytes`] if it existed, otherwise
    /// `None` will be returned to indicate that file doesn't exist.
    ///
    /// * since 0.1.0
    async fn open<P: AsRef<Path> + Send>(&self, path: P) -> Result<Option<Bytes>, Self::Error>;

    /// Open a file in the given `path` and returns a [`Blob`] structure if the path existed, otherwise
    /// `None` will be returned to indiciate that a file doesn't exist.
    ///
    /// * since 0.1.0
    async fn blob<P: AsRef<Path> + Send>(&self, path: P) -> Result<Option<Blob>, Self::Error>;

    /// Iterate over a list of files from a storage service and returns a [`Vec`] of [`Blob`]s.
    ///
    /// * since 0.1.0
    async fn blobs<P: AsRef<Path> + Send>(
        &self,
        path: Option<P>,
        options: Option<ListBlobsRequest>,
    ) -> Result<Vec<Blob>, Self::Error>;

    /// Deletes a file in a specified `path`. At the moment, `()` is returned but `bool` might be
    /// returned to indicate if it actually deleted itself or not.
    ///
    /// * since 0.1.0
    async fn delete<P: AsRef<Path> + Send>(&self, path: P) -> Result<(), Self::Error>;

    /// Checks the existence of the file by the specified path.
    ///
    /// * since: 0.1.0
    async fn exists<P: AsRef<Path> + Send>(&self, path: P) -> Result<bool, Self::Error>;

    /// Does a file upload where it writes the byte array as one call and does not do chunking. Use the [`StorageService::multipart_upload`]
    /// method to upload chunks by a specific size.
    ///
    /// * since: 0.1.0
    async fn upload<P: AsRef<Path> + Send>(&self, path: P, options: UploadRequest) -> Result<(), Self::Error>;

    // /// Does a multipart upload, where it uploads chunks of data bit by bit. By default, this will in an
    // /// unimplemented state and some storage services don't support chunk uploading.
    // ///
    // /// * since
    // async fn multipart_upload<P: AsRef<Path> + Send>(&self, _path: P) -> Result<(), Self::Error> {
    //     unimplemented!()
    // }

    /// Attempt to find a blob from a [`Blob`] where it returns the first blob that was found. A default
    /// implementation is given which just queries all blobs via [`StorageService::blobs`] and uses the
    /// [`find`][Iterator::find] method.
    ///
    /// * since: 0.6.0
    async fn find<P: AsRef<Path> + Send, F: FnMut(&Blob) -> bool + Send>(
        &self,
        path: Option<P>,
        options: Option<ListBlobsRequest>,
        finder: F,
    ) -> Result<Option<Blob>, Self::Error> {
        self.blobs(path, options)
            .await
            .map(|blobs| blobs.into_iter().find(finder))
    }
}