uplink 0.6.0

Idiomatic and safe Rust binding for the Storj Lib Uplink
Documentation
//! All the Storj DCS options types related to a Project.

use crate::{helpers, metadata::Custom, Error, Result};

use std::ffi::CString;
use std::time::Duration;

use uplink_sys as ulksys;

/// Options for committing a multipart upload.
pub struct CommitUpload<'a> {
    /// Custom metadata to assign to a multipart upload.
    custom_metadata: &'a mut Custom,
}

impl<'a> CommitUpload<'a> {
    /// Creates an instance of commit upload options.
    ///
    /// It's mutable because converting to a Uplink-C representation requires it.
    pub fn new(custom_metadata: &'a mut Custom) -> Self {
        Self { custom_metadata }
    }

    /// Returns the FFI representation of the options.
    ///
    /// It takes a mutable reference because [`metadata::Custom.to_ffi_c`] requires a mutable
    /// reference.
    #[allow(clippy::wrong_self_convention)]
    pub(crate) fn to_ffi_commit_upload_options(&mut self) -> ulksys::UplinkCommitUploadOptions {
        ulksys::UplinkCommitUploadOptions {
            custom_metadata: self.custom_metadata.to_ffi_custom_metadata(),
        }
    }
}

/// Options for copying objects to a different bucket or/and key without downloading and uploading
/// it.
#[derive(Default)]
pub struct CopyObject {}

impl CopyObject {
    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_copy_object_options(&self) -> ulksys::UplinkCopyObjectOptions {
        ulksys::UplinkCopyObjectOptions {}
    }
}

/// Options for downloading an object.
#[derive(Default)]
pub struct Download {
    /// The initial point of the object's blob to download.
    /// If it's negative, it will start at the suffix of the blob but it's isn't supported to be
    /// negative with a positive `length`.
    pub offset: i64,
    /// The length of the blob starting from `offset` to download.
    /// If it's negative, it will read until the end of the blob.
    pub length: i64,
}

impl Download {
    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_download_options(&self) -> ulksys::UplinkDownloadOptions {
        ulksys::UplinkDownloadOptions {
            offset: self.offset,
            length: self.length,
        }
    }
}

/// Options for listing buckets.
#[derive(Default)]
pub struct ListBuckets {
    /// C representation of `cursor` for providing it to the FFI and guards its lifetime until
    /// `self` gets dropped.
    inner_cursor: CString,
}

impl ListBuckets {
    /// Creates options for listing buckets with the specified cursor value.
    /// It returns an error if `cursor` contains any null byte (0 byte).
    pub fn with_cursor(cursor: &str) -> Result<Self> {
        let inner_cursor = helpers::cstring_from_str_fn_arg("cursor", cursor)?;
        Ok(Self { inner_cursor })
    }

    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_list_buckets_options(&self) -> ulksys::UplinkListBucketsOptions {
        ulksys::UplinkListBucketsOptions {
            cursor: self.inner_cursor.as_ptr(),
        }
    }
}

/// Options for listing objects.
#[derive(Default)]
pub struct ListObjects<'a> {
    /// Only list objects with this key prefix. When not empty, it must ends with slash.
    prefix: &'a str,
    /// C representation of `prefix` for providing it to the FFI and guards its lifetime until
    /// `self` gets dropped.
    inner_prefix: CString,
    /// Specifies the starting position of the iterator by offsetting from the first object of the
    /// list.
    /// The first item of the list is the one after the cursor.
    /// The list of objects depends on the `prefix`.
    cursor: &'a str,
    /// C representation of `cursor` for providing it to the FFI and guards its lifetime until
    /// `self` gets dropped.
    inner_cursor: CString,
    /// Iterate the objects without collapsing prefixes.
    pub recursive: bool,
    /// Include the "system metadata" associated with the objects.
    pub system: bool,
    /// Include the "custom metadata" associated with the objects.
    pub custom: bool,
}

impl<'a> ListObjects<'a> {
    /// Creates options of listing objects options with the specified prefix.
    ///
    /// `prefix` must:
    /// * not be empty.
    /// * end with '/'.
    /// * not contain any null byte (0 byte).
    pub fn with_prefix(prefix: &'a str) -> Result<Self> {
        if !prefix.ends_with('/') {
            return Err(Error::new_invalid_arguments(
                "prefix",
                "cannot be empty and must end with '/'",
            ));
        }

        Self::new(prefix, "")
    }

    /// Creates options of listing objects options with the specified cursor.
    ///
    /// `cursor` must:
    /// * not be empty.
    /// * not contain any null byte (0 byte).
    pub fn with_cursor(cursor: &'a str) -> Result<Self> {
        if cursor.is_empty() {
            return Err(Error::new_invalid_arguments("cursor", "cannot be empty"));
        }

        Self::new("", cursor)
    }

    /// Creates options of listing objects options with the specified prefix and cursor.
    ///
    /// `prefix` and `cursor` must:
    /// * not be empty.
    /// * not contain any null byte (0 byte).
    ///
    /// `prefix` must also end with '/'.
    pub fn with_prefix_and_cursor(prefix: &'a str, cursor: &'a str) -> Result<Self> {
        if !prefix.ends_with('/') {
            return Err(Error::new_invalid_arguments(
                "prefix",
                "cannot be empty and must end with '/'",
            ));
        }

        if cursor.is_empty() {
            return Err(Error::new_invalid_arguments("cursor", "cannot be empty"));
        }

        Self::new(prefix, cursor)
    }

    /// Creates options for listing objects with only verifying that `prefix` and `cursor` don't
    /// contain any null byte (0 byte), which are essential for working fine with the FFI.
    ///
    /// This is a convenient constructor to be used by the public constructors which impose more
    /// contains  on `prefix` and `cursor`.
    fn new(prefix: &'a str, cursor: &'a str) -> Result<Self> {
        let inner_prefix = helpers::cstring_from_str_fn_arg("prefix", prefix)?;
        let inner_cursor = helpers::cstring_from_str_fn_arg("cursor", cursor)?;

        Ok(Self {
            prefix,
            inner_prefix,
            cursor,
            inner_cursor,
            ..Default::default()
        })
    }

    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_list_objects_options(&self) -> ulksys::UplinkListObjectsOptions {
        ulksys::UplinkListObjectsOptions {
            prefix: self.inner_prefix.as_ptr(),
            cursor: self.inner_cursor.as_ptr(),
            recursive: self.recursive,
            system: self.system,
            custom: self.custom,
        }
    }
}

/// Options for listing uncommitted uploads.
#[derive(Default)]
pub struct ListUploads<'a> {
    /// Only list uncommitted uploads with this key prefix. When not empty, it must ends with slash.
    prefix: &'a str,
    /// C representation of `prefix` for providing it to the FFI and guards its lifetime until
    /// `self` gets dropped.
    inner_prefix: CString,
    /// Specifies the starting position of the iterator by offsetting from the first object of the
    /// list.
    /// The first item of the list is the one after the cursor.
    /// The list of objects depends on the `prefix`.
    cursor: &'a str,
    /// C representation of `cursor` for providing it to the FFI and guards its lifetime until
    /// `self` gets dropped.
    inner_cursor: CString,
    /// Iterate the objects without collapsing prefixes.
    pub recursive: bool,
    /// Include the "system metadata" associated with the objects.
    pub system: bool,
    /// Include the "custom metadata" associated with the objects.
    pub custom: bool,
}

impl<'a> ListUploads<'a> {
    /// Creates options of listing uncommitted uploads options with the specified prefix.
    ///
    /// `prefix` must:
    /// * not be empty.
    /// * end with '/'.
    /// * not contain any null byte (0 byte).
    pub fn with_prefix(prefix: &'a str) -> Result<Self> {
        if !prefix.ends_with('/') {
            return Err(Error::new_invalid_arguments(
                "prefix",
                "cannot be empty and must end with '/'",
            ));
        }

        Self::new(prefix, "")
    }

    /// Creates options of listing uncommitted uploads options with the specified cursor.
    ///
    /// `cursor` must:
    /// * not be empty.
    /// * not contain any null byte (0 byte).
    pub fn with_cursor(cursor: &'a str) -> Result<Self> {
        if cursor.is_empty() {
            return Err(Error::new_invalid_arguments("cursor", "cannot be empty"));
        }

        Self::new("", cursor)
    }

    /// Creates options of listing uncommitted options with the specified prefix and cursor.
    ///
    /// `prefix` and `cursor` must:
    /// * not be empty.
    /// * not contain any null byte (0 byte).
    ///
    /// `prefix` must also end with '/'.
    pub fn with_prefix_and_cursor(prefix: &'a str, cursor: &'a str) -> Result<Self> {
        if !prefix.ends_with('/') {
            return Err(Error::new_invalid_arguments(
                "prefix",
                "cannot be empty and must end with '/'",
            ));
        }

        if cursor.is_empty() {
            return Err(Error::new_invalid_arguments("cursor", "cannot be empty"));
        }

        Self::new(prefix, cursor)
    }

    /// Creates options for listing uncommitted with only verify that `prefix` and `cursor` don't
    /// contain any null byte (0 byte), which are essential for working fine with the FFI.
    ///
    /// This is a convenient constructor to be used by the public constructors which impose more
    /// contains  on `prefix` and `cursor`.
    fn new(prefix: &'a str, cursor: &'a str) -> Result<Self> {
        let inner_prefix = helpers::cstring_from_str_fn_arg("prefix", prefix)?;
        let inner_cursor = helpers::cstring_from_str_fn_arg("cursor", cursor)?;

        Ok(Self {
            prefix,
            inner_prefix,
            cursor,
            inner_cursor,
            ..Default::default()
        })
    }

    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_list_uploads_options(&self) -> ulksys::UplinkListUploadsOptions {
        ulksys::UplinkListUploadsOptions {
            prefix: self.inner_prefix.as_ptr(),
            cursor: self.inner_cursor.as_ptr(),
            recursive: self.recursive,
            system: self.system,
            custom: self.custom,
        }
    }
}

/// Options for listing uploads parts.
#[derive(Default)]
pub struct ListUploadParts {
    /// Specifies the starting position of the iterator by offsetting from the first object of the
    /// list.
    ///
    /// The first item of the list is the one after the cursor.
    /// Parts start with index 1.
    pub cursor: u32,
}

impl ListUploadParts {
    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_list_upload_parts_options(&self) -> ulksys::UplinkListUploadPartsOptions {
        ulksys::UplinkListUploadPartsOptions {
            cursor: self.cursor,
        }
    }
}

/// Options for moving objects to a different bucket or/and key.
#[derive(Default)]
pub struct MoveObject {}

impl MoveObject {
    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_move_object_options(&self) -> ulksys::UplinkMoveObjectOptions {
        ulksys::UplinkMoveObjectOptions {}
    }
}

/// Options for uploading objects.
#[derive(Default)]
pub struct Upload {
    /// Determine when the object expires.
    ///
    /// The time is measured with the number of seconds since the Unix Epoch time. 0 is never and
    /// it's the same as `None`.
    pub expires: Option<Duration>,
}

impl Upload {
    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_upload_options(&self) -> ulksys::UplinkUploadOptions {
        let expires = self.expires.unwrap_or(Duration::ZERO);

        ulksys::UplinkUploadOptions {
            expires: expires.as_secs() as i64,
        }
    }
}

/// Options for updating object's metadata.
///
/// Reserved for future use.
#[derive(Default)]
pub struct UploadObjectMetadata {}

impl UploadObjectMetadata {
    /// Returns the FFI representation of the options.
    pub(crate) fn as_ffi_upload_object_metadata_options(
        &self,
    ) -> ulksys::UplinkUploadObjectMetadataOptions {
        ulksys::UplinkUploadObjectMetadataOptions {}
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn test_list_buckets_with_cursor() {
        // TODO: implement test for checking OK and error cases.
    }

    #[test]
    fn test_list_objects_with_prefix() {
        // TODO: implement test for checking OK and error cases.
    }

    #[test]
    fn test_list_objects_with_cursor() {
        // TODO: implement test for checking OK and error cases.
    }

    #[test]
    fn test_list_objects_with_prefix_and_cursor() {
        // TODO: implement test for checking OK and error cases.
    }

    #[test]
    fn test_list_uploads_with_prefix() {
        // TODO: implement test for checking OK and error cases.
    }

    #[test]
    fn test_list_uploads_with_cursor() {
        // TODO: implement test for checking OK and error cases.
    }

    #[test]
    fn test_list_uploads_with_prefix_and_cursor() {
        // TODO: implement test for checking OK and error cases.
    }
}