drive-v3 0.5.1

A library for interacting the Google Drive API
Documentation
#[macro_export]
#[doc(hidden)]
macro_rules! request_builder {
    (
        // Struct definition with url query parameters
        $(#[$struct_attrs:meta])*
        pub $struct_name:ident {
            $(
                $(#[$parameter_attr_name:ident $($parameter_attr_values:tt)*])*
                $parameter_name:ident: Option<$parameter_type:ty>
            ),* $(,)?
        },

        // HTTP Method
        $method:expr,

        // API endpoint and fragments
        ( $url:literal $(,)? $($url_fragment_name:ident),* ),

        // Other optional fields that are not query parameters
        $(
            $(#[$field_attr_name:ident $($field_attr_values:tt)*])*
            $field_name:ident: Option<$field_type:ty>
        ),* $(,)?

        // Callback pointers
        $((
            $(#[$callback_attr_name:ident $($callback_attr_values:tt)*])*
            $callback_name:ident: Option<$callback_type:ty>
        )),* $(,)?
    ) => {

        $(#[$struct_attrs])*
        #[derive(Debug, Clone)]
        pub struct $struct_name {
            credentials: $crate::Credentials,
            method: ::reqwest::Method,
            url: String,
            fields: Option<String>,
            $($parameter_name: Option<$parameter_type>,)*
            $($field_name: Option<$field_type>,)*
            $($callback_name: Option<$callback_type>),*
        }

        impl $struct_name {
            #[doc = concat!("Creates a new [`", stringify!($struct_name), "`] builder, authorized with the given [`Credentials`].")]
            pub fn new( credentials: &$crate::Credentials, $($url_fragment_name: impl AsRef<str>),* ) -> $struct_name {
                $struct_name {
                    credentials: credentials.clone(),
                    method: $method,
                    url: format!( $url, $($url_fragment_name=$url_fragment_name.as_ref().to_string()),* ),
                    fields: None,
                    $($parameter_name: None,)*
                    $($field_name: None,)*
                    $($callback_name: None),*
                }
            }

            /// You can set this parameter to return the exact fields you need,
            /// and improve performance in your method call.
            ///
            /// # Note:
            ///
            /// By default, the server sends back a set of fields specific to the
            /// resource being queried. For example, the files.get method might
            /// only return the id, name, and mimeType for the files resource.
            /// The permissions.get method returns a different set of default
            /// fields for a permissions resource.
            ///
            /// See Google's
            /// [documentation](https://developers.google.com/drive/api/guides/fields-parameter)
            /// for more info.
            pub fn fields<T: AsRef<str>> ( mut self, fields: T ) -> $struct_name {
                self.fields = Some( fields.as_ref().to_string() );

                self
            }

            $(
                $(#[$parameter_attr_name $($parameter_attr_values)*])*
                pub fn $parameter_name<T> ( mut self, value: T ) -> $struct_name
                    where
                        T: Into<$parameter_type>
                {
                    self.$parameter_name = Some( value.into() );

                    self
                }
            )*

            $(
                $(#[$field_attr_name $($field_attr_values)*])*
                pub fn $field_name<T> ( mut self, value: T ) -> $struct_name
                    where
                        T: Into<$field_type>
                {
                    self.$field_name = Some( value.into() );

                    self
                }
            )*

            $(
                $(#[$callback_attr_name $($callback_attr_values)*])*
                pub fn $callback_name( mut self, func: $callback_type ) -> $struct_name {
                    self.$callback_name = Some(func);

                    self
                }
            )*

            /// Gets the current parameters for this request.
            fn get_parameters( &self ) -> Vec<(String, String)> {
                let mut parameters = Vec::new();

                if let Some(fields) = &self.fields {
                    parameters.push(( "fields".into(), fields.to_string() ));
                }

                $(
                    if let Some(value) = &self.$parameter_name {
                        use convert_case::{Case, Casing};

                        let mut parameter_name = stringify!($parameter_name).from_case(Case::Snake).to_case(Case::Camel);

                        // Rename kind to type since type is reserved
                        if &parameter_name == "kind" {
                            parameter_name = "type".to_string();
                        }

                        parameters.push(( parameter_name, value.to_string() ));
                    }
                )*

                parameters
            }

            fn build( &self ) -> Result<::reqwest::blocking::RequestBuilder, $crate::Error> {
                let parameters = self.get_parameters();
                let request_url = ::reqwest::Url::parse_with_params(&self.url, parameters)?;

                Ok(
                    ::reqwest::blocking::Client::new()
                        .request( self.method.clone(), request_url )
                        .bearer_auth( self.credentials.get_access_token() )
                )
            }

            /// Sends the built request.
            #[allow(dead_code)] // More complex request don't use this
            fn send( &self ) -> Result<::reqwest::blocking::Response, $crate::Error> {
                let request = self.build()?;
                let response = request.send()?;

                if !response.status().is_success() {
                    return Err( response.into() );
                }

                Ok(response)
            }
        }

    };
}

#[cfg(test)]
mod tests {
    use reqwest::header;
    use std::str::FromStr;
    use reqwest::{Method, Url};
    use crate::{request_builder, ErrorKind};
    use crate::utils::test::INVALID_CREDENTIALS;

    request_builder!(
        pub TestRequest {
            /// test field
            test_field: Option<String>,

            /// test field
            kind: Option<String>,
        },

        // HTTP Method
        Method::PATCH,

        // API endpoint
        ("https://www.test-domain.com/endpoint/{test_fragment}/test", test_fragment),

        // Other fields

        /// Sets the metadata that the created file will have in Google Drive.
        non_query_test_field: Option<bool>,

        // Function callbacks
        (
            test_callback: Option<fn(usize)>
        ),
    );

    #[test]
    fn test_new() {
        let credentials = INVALID_CREDENTIALS.clone();
        let test_request = TestRequest::new(&credentials, "fragment");

        assert_eq!(test_request.credentials, credentials);
        assert_eq!(test_request.method, Method::PATCH);
        assert_eq!(&test_request.url, "https://www.test-domain.com/endpoint/fragment/test");
        assert_eq!(test_request.fields, None);
        assert_eq!(test_request.test_field, None);
        assert_eq!(test_request.kind, None);
        assert_eq!(test_request.non_query_test_field, None);
        assert_eq!(test_request.test_callback, None);
    }

    #[test]
    fn test_fields() {
        let fields = String::from("testField1, testField2");

        let credentials = INVALID_CREDENTIALS.clone();
        let test_request = TestRequest::new(&credentials, "fragment")
            .fields(&fields);

        assert_eq!( test_request.fields, Some(fields) );
    }

    #[test]
    fn test_parameters() {
        let test_field = String::from("test");
        let kind = String::from("testKind");

        let expected_parameters = vec![
            ( "testField".to_string(), test_field.clone() ),
            ( "type".to_string(), kind.clone() ),
        ];

        let credentials = INVALID_CREDENTIALS.clone();
        let test_request = TestRequest::new(&credentials, "fragment")
            .test_field(&test_field)
            .kind(&kind);

        assert_eq!( test_request.test_field, Some(test_field) );
        assert_eq!( test_request.kind, Some(kind) );

        assert_eq!( test_request.get_parameters(), expected_parameters );
    }

    #[test]
    fn test_non_query_fields() {
        let non_query_test_field = true;

        let credentials = INVALID_CREDENTIALS.clone();
        let test_request = TestRequest::new(&credentials, "fragment")
            .non_query_test_field(non_query_test_field);

        assert_eq!( test_request.non_query_test_field, Some(non_query_test_field) );
        assert_eq!( test_request.get_parameters(), Vec::new() );
    }

    #[test]
    fn test_callback() {
        fn callback( arg: usize ) {
            assert_eq!(arg, 0);
        }

        let credentials = INVALID_CREDENTIALS.clone();
        let test_request = TestRequest::new(&credentials, "fragment")
            .test_callback(callback);

        assert!( test_request.test_callback.is_some() );

        (test_request.test_callback.unwrap())(0);
    }

    #[test]
    fn test_build() {
        let expected_url = Url::from_str(
            "https://www.test-domain.com/endpoint/fragment/test?fields=testField1%2C+testField2&testField=test&type=testKind"
        ).unwrap();

        let credentials = INVALID_CREDENTIALS.clone();
        let test_request = TestRequest::new(&credentials, "fragment")
            .fields("testField1, testField2")
            .test_field("test")
            .kind("testKind")
            .non_query_test_field(false);

        let request = test_request.build().unwrap().build().unwrap();

        assert_eq!( request.method(), &Method::PATCH );
        assert_eq!( request.url(), &expected_url );
        assert!( request.headers().get(header::AUTHORIZATION).unwrap().is_sensitive() );
    }

    #[test]
    fn send_test() {
        let credentials = INVALID_CREDENTIALS.clone();
        let test_request = TestRequest::new(&credentials, "fragment");

        let response = test_request.send();

        if response.is_err() {
            assert_eq!( response.unwrap_err().kind, ErrorKind::Response );
        }
    }
}