<commit_before>extern crate rusoto_mock;

use crate::generated::*;

use std::collections::HashMap;

use self::rusoto_mock::*;
use rusoto_core::param::Params;
use rusoto_core::signature::SignedRequest;
use rusoto_core::signature::SignedRequestPayload;
use rusoto_core::Region;

#[test]
fn should_serialize_map_parameters_in_create_platform_application_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("PlatformCredential".to_owned())),
                    params.get("Attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("YOUR_PLATFORM_CREDENTIAL".to_owned())),
                    params.get("Attributes.entry.1.value")
                );

                assert_eq!(
                    Some(&Some("PlatformPrincipal".to_owned())),
                    params.get("Attributes.entry.2.key")
                );
                assert_eq!(
                    Some(&Some("YOUR_PLATFORM_PRINCIPAL".to_owned())),
                    params.get("Attributes.entry.2.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "PlatformCredential".to_string(),
        "YOUR_PLATFORM_CREDENTIAL".to_string()
    );
    sns_input_attrib.insert(
        "PlatformPrincipal".to_string(),
        "YOUR_PLATFORM_PRINCIPAL".to_string(),
    );

    let sns_input = CreatePlatformApplicationInput {
        name: "example_platform_name".to_string(),
        platform: "GCM".to_string(),
        attributes: sns_input_attrib,
    };
    client.create_platform_application(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_create_platform_endpoint_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("Enabled".to_owned())),
                    params.get("Attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("false".to_owned())),
                    params.get("Attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "Enabled".to_string(),
        "false".to_string()
    );

    let sns_input = CreatePlatformEndpointInput {
        platform_application_arn: "ARN/GCM/example_platform_name".to_string(),
        token: "aaa".to_string(),
        custom_user_data: None,
        attributes: Some(sns_input_attrib),
    };
    client.create_platform_endpoint(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_set_platform_application_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("PlatformCredential".to_owned())),
                    params.get("Attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("YOUR_PLATFORM_CREDENTIAL".to_owned())),
                    params.get("Attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "PlatformCredential".to_string(),
        "YOUR_PLATFORM_CREDENTIAL".to_string()
    );

    let sns_input = SetPlatformApplicationAttributesInput {
        platform_application_arn: "ARN/GCM/example_platform_name".to_string(),
        attributes: sns_input_attrib,
    };
    client.set_platform_application_attributes(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_set_endpoint_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("Enabled".to_owned())),
                    params.get("Attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("false".to_owned())),
                    params.get("Attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert("Enabled".to_string(), "false".to_string());

    let sns_input = SetEndpointAttributesInput {
        endpoint_arn: "ARN/GCM/example_platform_name/ID".to_string(),
        attributes: sns_input_attrib,
    };
    client.set_endpoint_attributes(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_get_sms_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_body(
            r#"<GetSMSAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
              <GetSMSAttributesResult>
                <attributes>
                  <entry>
                    <key>DefaultSMSType</key>
                    <value>Promotional</value>
                  </entry>
                </attributes>
              </GetSMSAttributesResult>
              <ResponseMetadata>
                <RequestId>7e20a670-be23-5b05-9364-f02cc763f409</RequestId>
              </ResponseMetadata>
            </GetSMSAttributesResponse>"#,
        );

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let sns_input = GetSMSAttributesInput {
        attributes: Some(vec!["DefaultSMSType".to_string()]),
    };
    let _ = match client.get_sms_attributes(sns_input).sync() {
        Ok(output) => assert_eq!(
            output.attributes.unwrap().get("DefaultSMSType"),
            Some(&("Promotional".to_string()))
        ),
        Err(e) => panic!("invalid response data: {:?}", e),
    };
}

#[test]
fn should_serialize_map_parameters_in_set_sms_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("DefaultSMSType".to_owned())),
                    params.get("attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("Promotional".to_owned())),
                    params.get("attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "DefaultSMSType".to_string(),
        "Promotional".to_string()
    );
    let sns_input = SetSMSAttributesInput {
        attributes: sns_input_attrib,
    };
    client.set_sms_attributes(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_get_subscription_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_body(
            r#"<GetSubscriptionAttributesResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/">
                <GetSubscriptionAttributesResult>
                    <Attributes>
                        <entry>
                            <key>Owner</key>
                            <value>123456789012</value>
                        </entry>
                        <entry>
                            <key>DeliveryPolicy</key>
                            <value>{"healthyRetryPolicy":{"numRetries":10}}</value>
                        </entry>
                        <entry>
                            <key>SubscriptionArn</key>
                            <value>arn:aws:sns:us-east-2:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</value>
                        </entry>
                    </Attributes>
                </GetSubscriptionAttributesResult>
                <ResponseMetadata>
                    <RequestId>057f074c-33a7-11df-9540-99d0768312d3</RequestId>
                </ResponseMetadata>
            </GetSubscriptionAttributesResponse>"#,
        );

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let sns_input = GetSubscriptionAttributesInput {
        subscription_arn: "ARN:SUBSCRIPTION_ID".to_string(),
    };
    let _ = match client.get_subscription_attributes(sns_input).sync() {
        Ok(output) => {
            assert_eq!(
                output.clone().attributes.unwrap().get("Owner").unwrap(),
                &("123456789012".to_string()));
            assert_eq!(
                output.clone().attributes.unwrap().get("DeliveryPolicy").unwrap(),
                &(r#"{"healthyRetryPolicy":{"numRetries":10}}"#.to_string())
            );
        },
        Err(e) => panic!("invalid response data: {:?}", e),
    };
}
<commit_msg>fix ignore key-value order<commit_after>extern crate rusoto_mock;

use crate::generated::*;

use std::collections::HashMap;

use self::rusoto_mock::*;
use rusoto_core::param::Params;
use rusoto_core::signature::SignedRequest;
use rusoto_core::signature::SignedRequestPayload;
use rusoto_core::Region;

#[test]
fn should_serialize_map_parameters_in_create_platform_application_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                for idx in 1..2 {
                    let key = params.get(format!("Attributes.entry.{}.key", idx).as_str());
                    match key.unwrap().as_ref().unwrap().as_str() {
                        "PlatformCredential" => {
                            assert_eq!(
                                Some(&Some("YOUR_PLATFORM_CREDENTIAL".to_owned())),
                                params.get(format!("Attributes.entry.{}.value", idx).as_str())
                            );
                        },
                        "PlatformPrincipal" => {
                            assert_eq!(
                                Some(&Some("YOUR_PLATFORM_PRINCIPAL".to_owned())),
                                params.get(format!("Attributes.entry.{}.value", idx).as_str())
                            );
                        },
                        _ => panic!("invalid params. {:?}", params),
                    }
                }
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "PlatformCredential".to_string(),
        "YOUR_PLATFORM_CREDENTIAL".to_string()
    );
    sns_input_attrib.insert(
        "PlatformPrincipal".to_string(),
        "YOUR_PLATFORM_PRINCIPAL".to_string(),
    );

    let sns_input = CreatePlatformApplicationInput {
        name: "example_platform_name".to_string(),
        platform: "GCM".to_string(),
        attributes: sns_input_attrib,
    };
    client.create_platform_application(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_create_platform_endpoint_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("Enabled".to_owned())),
                    params.get("Attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("false".to_owned())),
                    params.get("Attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "Enabled".to_string(),
        "false".to_string()
    );

    let sns_input = CreatePlatformEndpointInput {
        platform_application_arn: "ARN/GCM/example_platform_name".to_string(),
        token: "aaa".to_string(),
        custom_user_data: None,
        attributes: Some(sns_input_attrib),
    };
    client.create_platform_endpoint(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_set_platform_application_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("PlatformCredential".to_owned())),
                    params.get("Attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("YOUR_PLATFORM_CREDENTIAL".to_owned())),
                    params.get("Attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "PlatformCredential".to_string(),
        "YOUR_PLATFORM_CREDENTIAL".to_string()
    );

    let sns_input = SetPlatformApplicationAttributesInput {
        platform_application_arn: "ARN/GCM/example_platform_name".to_string(),
        attributes: sns_input_attrib,
    };
    client.set_platform_application_attributes(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_set_endpoint_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("Enabled".to_owned())),
                    params.get("Attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("false".to_owned())),
                    params.get("Attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert("Enabled".to_string(), "false".to_string());

    let sns_input = SetEndpointAttributesInput {
        endpoint_arn: "ARN/GCM/example_platform_name/ID".to_string(),
        attributes: sns_input_attrib,
    };
    client.set_endpoint_attributes(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_get_sms_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_body(
            r#"<GetSMSAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
              <GetSMSAttributesResult>
                <attributes>
                  <entry>
                    <key>DefaultSMSType</key>
                    <value>Promotional</value>
                  </entry>
                </attributes>
              </GetSMSAttributesResult>
              <ResponseMetadata>
                <RequestId>7e20a670-be23-5b05-9364-f02cc763f409</RequestId>
              </ResponseMetadata>
            </GetSMSAttributesResponse>"#,
        );

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let sns_input = GetSMSAttributesInput {
        attributes: Some(vec!["DefaultSMSType".to_string()]),
    };
    let _ = match client.get_sms_attributes(sns_input).sync() {
        Ok(output) => assert_eq!(
            output.attributes.unwrap().get("DefaultSMSType"),
            Some(&("Promotional".to_string()))
        ),
        Err(e) => panic!("invalid response data: {:?}", e),
    };
}

#[test]
fn should_serialize_map_parameters_in_set_sms_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_request_checker(|request: &SignedRequest| {
            assert_eq!("POST", request.method);

            if let Some(SignedRequestPayload::Buffer(ref buffer)) = request.payload {
                let params: Params = serde_urlencoded::from_bytes(buffer).unwrap();
                assert_eq!(
                    Some(&Some("DefaultSMSType".to_owned())),
                    params.get("attributes.entry.1.key")
                );
                assert_eq!(
                    Some(&Some("Promotional".to_owned())),
                    params.get("attributes.entry.1.value")
                );
            } else {
                panic!("Unexpected request.payload: {:?}", request.payload);
            }
        });

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let mut sns_input_attrib = HashMap::new();
    sns_input_attrib.insert(
        "DefaultSMSType".to_string(),
        "Promotional".to_string()
    );
    let sns_input = SetSMSAttributesInput {
        attributes: sns_input_attrib,
    };
    client.set_sms_attributes(sns_input).sync().unwrap();
}

#[test]
fn should_serialize_map_parameters_in_get_subscription_attributes_request_body() {
    let mock = MockRequestDispatcher::with_status(200)
        .with_body(
            r#"<GetSubscriptionAttributesResponse xmlns="https://sns.amazonaws.com/doc/2010-03-31/">
                <GetSubscriptionAttributesResult>
                    <Attributes>
                        <entry>
                            <key>Owner</key>
                            <value>123456789012</value>
                        </entry>
                        <entry>
                            <key>DeliveryPolicy</key>
                            <value>{"healthyRetryPolicy":{"numRetries":10}}</value>
                        </entry>
                        <entry>
                            <key>SubscriptionArn</key>
                            <value>arn:aws:sns:us-east-2:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</value>
                        </entry>
                    </Attributes>
                </GetSubscriptionAttributesResult>
                <ResponseMetadata>
                    <RequestId>057f074c-33a7-11df-9540-99d0768312d3</RequestId>
                </ResponseMetadata>
            </GetSubscriptionAttributesResponse>"#,
        );

    let client = SnsClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let sns_input = GetSubscriptionAttributesInput {
        subscription_arn: "ARN:SUBSCRIPTION_ID".to_string(),
    };
    let _ = match client.get_subscription_attributes(sns_input).sync() {
        Ok(output) => {
            assert_eq!(
                output.clone().attributes.unwrap().get("Owner").unwrap(),
                &("123456789012".to_string()));
            assert_eq!(
                output.clone().attributes.unwrap().get("DeliveryPolicy").unwrap(),
                &(r#"{"healthyRetryPolicy":{"numRetries":10}}"#.to_string())
            );
        },
        Err(e) => panic!("invalid response data: {:?}", e),
    };
}
<|endoftext|>


<commit_before>extern crate libc;

use libc::{c_void};
use std::ffi::CStr;
use std::ffi::CString;
use std::{fmt, process, ptr};
use std::collections::HashMap;
use std::mem;
use std::mem::{forget, transmute};
use std::sync::mpsc;
use std::sync::mpsc::{Sender, Receiver};

use std::sync::{Arc, Mutex};

use super::super::couchbase::*;

use super::super::couchbase::types::response::format_error;

// Gets
pub type OperationResultGet = Result<response::Get, (Option<response::Get>, &'static str)>;
pub type OperationResultGetCallback = Box<Box<Fn(&response::Get)>>;
pub type OperationResultGetInternal<'a> = Result<&'a response::GetInternal, (Option<&'a response::GetInternal>, &'static str)>;
pub type OperationResultGetInternalCallback = Box<Box<Fn(&response::GetInternal)>>;

// Stores
pub type OperationResultStore = Result<response::Store, (Option<response::Store>, &'static str)>;
pub type OperationResultStoreCallback = Box<Box<Fn(&response::Store)>>;
pub type OperationResultStoreInternal<'a> = Result<&'a response::StoreInternal, (Option<&'a response::StoreInternal>, &'static str)>;
pub type OperationResultStoreInternalCallback = Box<Box<Fn(&response::StoreInternal)>>;

pub struct CouchbaseOperation<T> {
    counter: u64,
    map: HashMap<u64, T>
}

impl<T> fmt::Debug for CouchbaseOperation<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "CouchbaseOperation {{ counter: {}, length: {} }}", self.counter, self.map.len())
    }
}

impl<T> CouchbaseOperation<T> {
    pub fn new() -> CouchbaseOperation<T> {
        CouchbaseOperation {
            counter: 0,
            map: HashMap::new()
        }
    }

    pub fn increment_counter(&mut self) -> u64 {
        self.counter += 1;
        return self.counter;
    }
}

#[derive(Debug)]
pub struct CouchbaseOperations {
    get: CouchbaseOperation<cmd::Get>,
    store: CouchbaseOperation<cmd::Get>,
}

impl CouchbaseOperations {
    pub fn new() -> CouchbaseOperations {
        CouchbaseOperations {
            get: CouchbaseOperation::new(),
            store: CouchbaseOperation::new()
        }
    }
}

#[derive(Debug)]
pub struct Client {
    pub opts: Arc<Mutex<CreateSt>>,
    pub instance: Instance,
    pub uri: String,
    pub operations: CouchbaseOperations
}

impl Client {
    /// Constructor
    pub fn new(uri: &str) -> Client {
        let connstr = CString::new(uri).unwrap();

        let mut opts = CreateSt::default();
        opts.v3.connstr = connstr.as_ptr();

        let mut instance: Instance = ptr::null_mut();

        unsafe {
            let res = lcb_create(&mut instance as *mut Instance, &opts as *const CreateSt);
            if res != ErrorType::Success {
                error!("lcb_connect() failed - {:?}", res);
            }

            info!("Connecting to {}", uri);

            let res = lcb_connect(instance);
            if res != ErrorType::Success {
                error!("lcb_connect() failed - {:?}", res);
            }

            let res = lcb_wait(instance);
            if res != ErrorType::Success {
                error!("lcb_wait() failed - {:?}", res);
            }

            let res = lcb_get_bootstrap_status(instance);
            if res != ErrorType::Success {
                error!("lcb_get_bootstrap_status() failed - {:?}, \"{}\"",
                         res,
                         CStr::from_ptr(lcb_strerror(instance, res)).to_str().unwrap()
                );
                process::exit(-1);
            }

            lcb_install_callback3(instance, CallbackType::Get, op_callback);
            lcb_install_callback3(instance, CallbackType::Store, op_callback);

            Client {
                opts: Arc::new(Mutex::new(opts)),
                instance: instance,
                uri: uri.to_string(),
                operations: CouchbaseOperations::new()

            }
        }
    }

    ///  Will cause the operation to fail if the key already exists in the cluster.
    pub fn add<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Add, callback);
    }

    /// Rather than setting the contents of the entire document, take the value specified in value and _append_ it to the existing bytes in the value.
    pub fn append<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Append, callback);
    }

    /// Get document from database
    pub fn get<'a, F>(&'a mut self, key: &str, callback: F) -> &Client
        where F: Fn(OperationResultGet) + 'static
    {
        let key = key.to_owned();

        let mut gcmd = cmd::Get::default();

        gcmd.key._type = KvBufferType::Copy;
        gcmd.key.contig.bytes = key.as_bytes().as_ptr() as *const libc::c_void;
        gcmd.key.contig.nbytes = key.len() as u64;

        forget(key);

        unsafe {
            let _id = self.operations.get.increment_counter();

            let boxed: OperationResultGetInternalCallback = Box::new(Box::new(move |result: &response::GetInternal| {
                match result.rc {
                    ErrorType::Success => {
                        debug!("{:?}", result);
                        callback(Ok(response::Get::new(result)));
                    },
                    _ => {
                        callback(Err((Some(response::Get::new(result)), "error" /* result.error(self.instance) */)));
                    }
                }
            }));

            debug!("Boxed box occupies {} bytes in the stack", mem::size_of_val(&boxed));

            // let user_data = transmute::<*const _, *mut c_void>(Box::into_raw(boxed));
            let user_data = Box::into_raw(boxed) as *mut Box<Fn(&response::GetInternal)> as *mut c_void;
            debug!("Setting get callback address {:?}", user_data);

            let res = lcb_get3(self.instance, user_data, &gcmd as *const cmd::Get);
            if res != ErrorType::Success {
                // callback(Err((None, format_error(self.instance, &res))));
            } else {
                let res = lcb_wait(self.instance);
                if res != ErrorType::Success {
                    // callback(Err((None, format_error(self.instance, &res))))
                }
            }
        }

        return self;
    }

    pub fn get_sync(&mut self, key: &str) -> OperationResultGet
    {
        let (tx, rx): (Sender<OperationResultGet>, Receiver<OperationResultGet>) = mpsc::channel();
        self.get(key, move |result: OperationResultGet| {
            let _ = tx.send(result);
        });
        return rx.recv().unwrap();
    }

    /// Like append, but prepends the new value to the existing value.
    pub fn prepend<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Prepend, callback);
    }

    /// Will cause the operation to fail _unless_ the key already exists in the cluster.
    pub fn replace<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Replace, callback);
    }

    /// Unconditionally store the item in the cluster
    pub fn set<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Set, callback);
    }

    /// Store document in database
    pub fn store<'a, F>(&'a mut self, key: &str, value: &str, operation: Operation, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        let key = key.to_owned();

        let mut gcmd = cmd::Store::default();
        gcmd.key._type = KvBufferType::Copy;
        gcmd.key.contig.bytes = key.as_bytes().as_ptr() as *const libc::c_void;
        gcmd.key.contig.nbytes = key.len() as u64;
        gcmd.value._type = KvBufferType::Copy;
        gcmd.value.contig.bytes = value.as_bytes().as_ptr() as *const libc::c_void;
        gcmd.value.contig.nbytes = value.len() as u64;
        gcmd.operation = operation;

        forget(key);

        unsafe {
            let _id = self.operations.store.increment_counter();

            let boxed: OperationResultStoreInternalCallback = Box::new(Box::new(move |result: &response::StoreInternal| {
                match result.rc {
                    ErrorType::Success => {
                        debug!("{:?}", result);
                        callback(Ok(response::Store::new(result)));
                    },
                    _ => {
                        callback(Err((Some(response::Store::new(result)), "error" /* result.error(self.instance) */)));
                    }
                }
            }));

            debug!("Boxed box occupies {} bytes in the stack", mem::size_of_val(&boxed));

            // let user_data = transmute::<*const  _, *mut c_void>(Box::into_raw(boxed));
            let user_data = Box::into_raw(boxed) as *mut Box<Fn(&response::StoreInternal)> as *mut c_void;
            debug!("Setting store callback address {:?}", user_data);

            let res = lcb_store3(self.instance, user_data, &gcmd as *const cmd::Store);
            if res != ErrorType::Success {
                // callback(Err((None, format_error(self.instance, &res))))
            } else {
                let res = lcb_wait(self.instance);
                if res != ErrorType::Success {
                    // callback(Err((None, format_error(self.instance, &res))))
                }
            }
        }

        return self;
    }

    /// Behaviorally it is identical to set in that it will make the server unconditionally store the item, whether it exists or not.
    pub fn upsert<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Upsert, callback);
    }
}

impl Drop for Client {
    fn drop(&mut self) {
        unsafe {
            info!("Disconnecting from {}", self.uri);
            lcb_destroy(self.instance);
        }
    }
}

/// libcouchbse callback
unsafe extern "C" fn op_callback(_instance: Instance, cbtype: CallbackType, resp: *const response::Base) {
    match cbtype {
        CallbackType::Get => {
            let gresp = resp as *const response::GetInternal;
            debug!("{:?}", *gresp);

            let cookie = (*gresp).cookie;
            // let callback = transmute::<*mut c_void, *const Box<Fn(&response::GetInternal)>>(cookie);

            let callback = cookie as *const Box<Fn(&response::GetInternal)>;
            // let callback: Box<Box<Fn(&response::GetInternal)>> = Box::from_raw(cookie as *mut Box<Fn(&response::GetInternal)>);
            debug!("Retreived boxed box occupies {} bytes in the stack", mem::size_of_val(&callback));

            debug!("Got get callback address {:?}", callback);

            (*callback)(&(*gresp));
        },
        CallbackType::Store => {
            let gresp = resp as *const response::StoreInternal;
            debug!("{:?}", *gresp);

            let cookie = (*gresp).cookie;
            // let callback = transmute::<*mut c_void, *const Box<Fn(&response::StoreInternal)>>(cookie);

            let callback = cookie as *const Box<Fn(&response::StoreInternal)>;
            // let callback: Box<Box<Fn(&response::StoreInternal)>> = Box::from_raw(cookie as *mut Box<Fn(&response::StoreInternal)>);
            debug!("Retreived boxed box occupies {} bytes in the stack", mem::size_of_val(&callback));

            debug!("Got store callback address {:?}", callback);

            (*callback)(&(*gresp));
        },
        _ => error!("! Unknown Callback...")
    };
}
<commit_msg>forget at end of get/store<commit_after>extern crate libc;

use libc::{c_void};
use std::ffi::CStr;
use std::ffi::CString;
use std::{fmt, process, ptr};
use std::collections::HashMap;
use std::mem;
use std::mem::{forget, transmute};
use std::sync::mpsc;
use std::sync::mpsc::{Sender, Receiver};

use std::sync::{Arc, Mutex};

use super::super::couchbase::*;

use super::super::couchbase::types::response::format_error;

// Gets
pub type OperationResultGet = Result<response::Get, (Option<response::Get>, &'static str)>;
pub type OperationResultGetCallback = Box<Box<Fn(&response::Get)>>;
pub type OperationResultGetInternal<'a> = Result<&'a response::GetInternal, (Option<&'a response::GetInternal>, &'static str)>;
pub type OperationResultGetInternalCallback = Box<Box<Fn(&response::GetInternal)>>;

// Stores
pub type OperationResultStore = Result<response::Store, (Option<response::Store>, &'static str)>;
pub type OperationResultStoreCallback = Box<Box<Fn(&response::Store)>>;
pub type OperationResultStoreInternal<'a> = Result<&'a response::StoreInternal, (Option<&'a response::StoreInternal>, &'static str)>;
pub type OperationResultStoreInternalCallback = Box<Box<Fn(&response::StoreInternal)>>;

pub struct CouchbaseOperation<T> {
    counter: u64,
    map: HashMap<u64, T>
}

impl<T> fmt::Debug for CouchbaseOperation<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "CouchbaseOperation {{ counter: {}, length: {} }}", self.counter, self.map.len())
    }
}

impl<T> CouchbaseOperation<T> {
    pub fn new() -> CouchbaseOperation<T> {
        CouchbaseOperation {
            counter: 0,
            map: HashMap::new()
        }
    }

    pub fn increment_counter(&mut self) -> u64 {
        self.counter += 1;
        return self.counter;
    }
}

#[derive(Debug)]
pub struct CouchbaseOperations {
    get: CouchbaseOperation<cmd::Get>,
    store: CouchbaseOperation<cmd::Get>,
}

impl CouchbaseOperations {
    pub fn new() -> CouchbaseOperations {
        CouchbaseOperations {
            get: CouchbaseOperation::new(),
            store: CouchbaseOperation::new()
        }
    }
}

#[derive(Debug)]
pub struct Client {
    pub opts: Arc<Mutex<CreateSt>>,
    pub instance: Instance,
    pub uri: String,
    pub operations: CouchbaseOperations
}

impl Client {
    /// Constructor
    pub fn new(uri: &str) -> Client {
        let connstr = CString::new(uri).unwrap();

        let mut opts = CreateSt::default();
        opts.v3.connstr = connstr.as_ptr();

        let mut instance: Instance = ptr::null_mut();

        unsafe {
            let res = lcb_create(&mut instance as *mut Instance, &opts as *const CreateSt);
            if res != ErrorType::Success {
                error!("lcb_connect() failed - {:?}", res);
            }

            info!("Connecting to {}", uri);

            let res = lcb_connect(instance);
            if res != ErrorType::Success {
                error!("lcb_connect() failed - {:?}", res);
            }

            let res = lcb_wait(instance);
            if res != ErrorType::Success {
                error!("lcb_wait() failed - {:?}", res);
            }

            let res = lcb_get_bootstrap_status(instance);
            if res != ErrorType::Success {
                error!("lcb_get_bootstrap_status() failed - {:?}, \"{}\"",
                         res,
                         CStr::from_ptr(lcb_strerror(instance, res)).to_str().unwrap()
                );
                process::exit(-1);
            }

            lcb_install_callback3(instance, CallbackType::Get, op_callback);
            lcb_install_callback3(instance, CallbackType::Store, op_callback);

            Client {
                opts: Arc::new(Mutex::new(opts)),
                instance: instance,
                uri: uri.to_string(),
                operations: CouchbaseOperations::new()

            }
        }
    }

    ///  Will cause the operation to fail if the key already exists in the cluster.
    pub fn add<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Add, callback);
    }

    /// Rather than setting the contents of the entire document, take the value specified in value and _append_ it to the existing bytes in the value.
    pub fn append<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Append, callback);
    }

    /// Get document from database
    pub fn get<'a, F>(&'a mut self, key: &str, callback: F) -> &Client
        where F: Fn(OperationResultGet) + 'static
    {
        let key = key.to_owned();

        let mut gcmd = cmd::Get::default();

        gcmd.key._type = KvBufferType::Copy;
        gcmd.key.contig.bytes = key.as_bytes().as_ptr() as *const libc::c_void;
        gcmd.key.contig.nbytes = key.len() as u64;

        unsafe {
            let _id = self.operations.get.increment_counter();

            let boxed: OperationResultGetInternalCallback = Box::new(Box::new(move |result: &response::GetInternal| {
                match result.rc {
                    ErrorType::Success => {
                        debug!("{:?}", result);
                        callback(Ok(response::Get::new(result)));
                    },
                    _ => {
                        callback(Err((Some(response::Get::new(result)), "error" /* result.error(self.instance) */)));
                    }
                }
            }));

            debug!("Boxed box occupies {} bytes in the stack", mem::size_of_val(&boxed));

            // let user_data = transmute::<*const _, *mut c_void>(Box::into_raw(boxed));
            let user_data = Box::into_raw(boxed) as *mut Box<Fn(&response::GetInternal)> as *mut c_void;
            debug!("Setting get callback address {:?}", user_data);

            let res = lcb_get3(self.instance, user_data, &gcmd as *const cmd::Get);
            if res != ErrorType::Success {
                // callback(Err((None, format_error(self.instance, &res))));
            } else {
                let res = lcb_wait(self.instance);
                if res != ErrorType::Success {
                    // callback(Err((None, format_error(self.instance, &res))))
                }
            }
        }

        forget(key);

        return self;
    }

    pub fn get_sync(&mut self, key: &str) -> OperationResultGet
    {
        let (tx, rx): (Sender<OperationResultGet>, Receiver<OperationResultGet>) = mpsc::channel();
        self.get(key, move |result: OperationResultGet| {
            let _ = tx.send(result);
        });
        return rx.recv().unwrap();
    }

    /// Like append, but prepends the new value to the existing value.
    pub fn prepend<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Prepend, callback);
    }

    /// Will cause the operation to fail _unless_ the key already exists in the cluster.
    pub fn replace<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Replace, callback);
    }

    /// Unconditionally store the item in the cluster
    pub fn set<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Set, callback);
    }

    /// Store document in database
    pub fn store<'a, F>(&'a mut self, key: &str, value: &str, operation: Operation, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        let key = key.to_owned();

        let mut gcmd = cmd::Store::default();
        gcmd.key._type = KvBufferType::Copy;
        gcmd.key.contig.bytes = key.as_bytes().as_ptr() as *const libc::c_void;
        gcmd.key.contig.nbytes = key.len() as u64;
        gcmd.value._type = KvBufferType::Copy;
        gcmd.value.contig.bytes = value.as_bytes().as_ptr() as *const libc::c_void;
        gcmd.value.contig.nbytes = value.len() as u64;
        gcmd.operation = operation;

        unsafe {
            let _id = self.operations.store.increment_counter();

            let boxed: OperationResultStoreInternalCallback = Box::new(Box::new(move |result: &response::StoreInternal| {
                match result.rc {
                    ErrorType::Success => {
                        debug!("{:?}", result);
                        callback(Ok(response::Store::new(result)));
                    },
                    _ => {
                        callback(Err((Some(response::Store::new(result)), "error" /* result.error(self.instance) */)));
                    }
                }
            }));

            debug!("Boxed box occupies {} bytes in the stack", mem::size_of_val(&boxed));

            // let user_data = transmute::<*const  _, *mut c_void>(Box::into_raw(boxed));
            let user_data = Box::into_raw(boxed) as *mut Box<Fn(&response::StoreInternal)> as *mut c_void;
            debug!("Setting store callback address {:?}", user_data);

            let res = lcb_store3(self.instance, user_data, &gcmd as *const cmd::Store);
            if res != ErrorType::Success {
                // callback(Err((None, format_error(self.instance, &res))))
            } else {
                let res = lcb_wait(self.instance);
                if res != ErrorType::Success {
                    // callback(Err((None, format_error(self.instance, &res))))
                }
            }
        }

        forget(key);

        return self;
    }

    /// Behaviorally it is identical to set in that it will make the server unconditionally store the item, whether it exists or not.
    pub fn upsert<'a, F>(&'a mut self, key: &str, value: &str, callback: F) -> &Client
        where F: Fn(OperationResultStore) + 'static
    {
        return self.store(key, value, Operation::Upsert, callback);
    }
}

impl Drop for Client {
    fn drop(&mut self) {
        unsafe {
            info!("Disconnecting from {}", self.uri);
            lcb_destroy(self.instance);
        }
    }
}

/// libcouchbse callback
unsafe extern "C" fn op_callback(_instance: Instance, cbtype: CallbackType, resp: *const response::Base) {
    match cbtype {
        CallbackType::Get => {
            let gresp = resp as *const response::GetInternal;
            debug!("{:?}", *gresp);

            let cookie = (*gresp).cookie;
            // let callback = transmute::<*mut c_void, *const Box<Fn(&response::GetInternal)>>(cookie);

            let callback = cookie as *const Box<Fn(&response::GetInternal)>;
            // let callback: Box<Box<Fn(&response::GetInternal)>> = Box::from_raw(cookie as *mut Box<Fn(&response::GetInternal)>);
            debug!("Retreived boxed box occupies {} bytes in the stack", mem::size_of_val(&callback));

            debug!("Got get callback address {:?}", callback);

            (*callback)(&(*gresp));
        },
        CallbackType::Store => {
            let gresp = resp as *const response::StoreInternal;
            debug!("{:?}", *gresp);

            let cookie = (*gresp).cookie;
            // let callback = transmute::<*mut c_void, *const Box<Fn(&response::StoreInternal)>>(cookie);

            let callback = cookie as *const Box<Fn(&response::StoreInternal)>;
            // let callback: Box<Box<Fn(&response::StoreInternal)>> = Box::from_raw(cookie as *mut Box<Fn(&response::StoreInternal)>);
            debug!("Retreived boxed box occupies {} bytes in the stack", mem::size_of_val(&callback));

            debug!("Got store callback address {:?}", callback);

            (*callback)(&(*gresp));
        },
        _ => error!("! Unknown Callback...")
    };
}
<|endoftext|>


<commit_before>use analysis::namespaces;
use case::CaseExt;
use codegen::general::{self, version_condition, version_condition_string};
use config::gobjects::GObject;
use env::Env;
use file_saver;
use library::*;
use std::collections::HashSet;
use std::io::prelude::*;
use std::io::Result;
use std::path::Path;
use traits::*;
use version::Version;

pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec<String>) {
    let configs: Vec<&GObject> = env.config.objects.values()
        .filter(|c| {
            c.status.need_generate() &&
                c.type_id.map_or(false, |tid| tid.ns_id == namespaces::MAIN)
        })
        .collect();
    let mut has_get_quark = false;
    let mut has_any = false;
    for config in &configs {
        if let Type::Enumeration(ref enum_) = *env.library.type_(config.type_id.unwrap()) {
            has_any = true;
            if get_error_quark_name(enum_).is_some(){
                has_get_quark = true;
                break;
            }
        }
    }

    let path = root_path.join("enums.rs");
    file_saver::save_to_file(path, env.config.make_backup, |w| {
        try!(general::start_comments(w, &env.config));
        try!(writeln!(w, ""));
        try!(writeln!(w, "use ffi;"));
        if env.namespaces.glib_ns_id == namespaces::MAIN {
            if has_get_quark {
                try!(writeln!(w, "use ffi as glib_ffi;"));
                try!(writeln!(w, "use error::ErrorDomain;"));
            }
            try!(writeln!(w, "use translate::*;"));
        } else {
            if has_get_quark {
                try!(writeln!(w, "use glib_ffi;"));
                try!(writeln!(w, "use glib::error::ErrorDomain;"));
            }
            try!(writeln!(w, "use glib::translate::*;"));
        }
        try!(writeln!(w, ""));

        if has_any {
            mod_rs.push("\nmod enums;".into());
        }
        for config in &configs {
            if let Type::Enumeration(ref enum_) = *env.library.type_(config.type_id.unwrap()) {
                if let Some (cfg) = version_condition_string(env, enum_.version, false, 0) {
                    mod_rs.push(cfg);
                }
                mod_rs.push(format!("pub use self::enums::{};", enum_.name));
                try!(generate_enum(env, w, enum_, config));
            }
        }

        Ok(())
    });
}

fn generate_enum(env: &Env, w: &mut Write, enum_: &Enumeration, config: &GObject) -> Result<()> {
    struct Member {
        name: String,
        c_name: String,
        version: Option<Version>,
    }

    let mut members: Vec<Member> = Vec::new();
    let mut vals: HashSet<String> = HashSet::new();

    for member in &enum_.members {
        let member_config = config.members.matched(&member.name);
        let is_alias = member_config.iter().any(|m| m.alias);
        if is_alias || vals.contains(&member.value) {
            continue
        }
        vals.insert(member.value.clone());
        let version = member_config.iter().filter_map(|m| m.version).next();
        members.push(Member {
            name: member.name.to_camel(),
            c_name: member.c_identifier.clone(),
            version: version,
        });
    }

    try!(version_condition(w, env, enum_.version, false, 0));
    try!(writeln!(w, "#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]"));
    try!(writeln!(w, "pub enum {} {{", enum_.name));
    for member in &members {
        try!(writeln!(w, "\t{},", member.name));
    }
    try!(writeln!(w, "{}", "    #[doc(hidden)]
    __Nonexhaustive(()),
}
"));

    try!(version_condition(w, env, enum_.version, false, 0));
    try!(writeln!(w, "#[doc(hidden)]
impl ToGlib for {name} {{
    type GlibType = ffi::{ffi_name};

    fn to_glib(&self) -> ffi::{ffi_name} {{
        match *self {{", name = enum_.name, ffi_name = enum_.c_type));
    for member in &members {
        try!(version_condition(w, env, member.version, false, 3));
        try!(writeln!(w, "\t\t\t{}::{} => ffi::{},", enum_.name, member.name, member.c_name));
    }
    try!(writeln!(w, "\t\t\t{}::__Nonexhaustive(_) => panic!(),", enum_.name));
    try!(writeln!(w, "{}",
"        }
    }
}
"));

    let assert = if env.config.generate_safety_asserts {
        "skip_assert_initialized!();\n\t\t"
    } else {
        ""
    };

    try!(version_condition(w, env, enum_.version, false, 0));
    try!(writeln!(w, "#[doc(hidden)]
impl FromGlib<ffi::{ffi_name}> for {name} {{
    fn from_glib(value: ffi::{ffi_name}) -> Self {{
        {assert}match value {{", name = enum_.name, ffi_name = enum_.c_type, assert = assert));
    for member in &members {
        try!(version_condition(w, env, member.version, false, 3));
        try!(writeln!(w, "\t\t\tffi::{} => {}::{},", member.c_name, enum_.name, member.name));
    }
    if members.len() == 1 {
        try!(writeln!(w, "\t\t\t_ => {}::__Nonexhaustive(()),", enum_.name));
    }
    try!(writeln!(w, "{}",
"        }
    }
}
"));
    if let Some(ref get_quark) = get_error_quark_name(enum_) {
        let get_quark = get_quark.replace("-", "_");
        let has_failed_member = members.iter().any(|m| m.name == "Failed");

        try!(version_condition(w, env, enum_.version, false, 0));
        try!(writeln!(w, "impl ErrorDomain for {name} {{
    fn domain() -> glib_ffi::GQuark {{
        {assert}unsafe {{ ffi::{get_quark}() }}
    }}

    fn code(self) -> i32 {{
        self.to_glib() as i32
    }}

    fn from(code: i32) -> Option<Self> {{
        {assert}match code {{", name = enum_.name, get_quark = get_quark, assert = assert));

        for member in &members {
            try!(version_condition(w, env, member.version, false, 3));
            try!(writeln!(w, "\t\t\tx if x == ffi::{} as i32 => Some({}::{}),", member.c_name,
                enum_.name, member.name));
        }
        if has_failed_member {
            try!(writeln!(w, "\t\t\t_ => Some({}::Failed),", enum_.name));
        } else {
            try!(writeln!(w, "\t\t\t_ => Some({}::__Nonexhaustive(())),", enum_.name));
        }

        try!(writeln!(w, "{}",
"        }
    }
}
"));
    }

    Ok(())
}

fn get_error_quark_name(enum_: &Enumeration) -> Option<String> {
    enum_.functions.iter()
        .find(|f| f.name == "quark")
        .and_then(|f| f.c_identifier.clone())
        .or_else(|| enum_.error_domain.clone())
}
<commit_msg>Fix generation of versioned enum member<commit_after>use analysis::namespaces;
use case::CaseExt;
use codegen::general::{self, version_condition, version_condition_string};
use config::gobjects::GObject;
use env::Env;
use file_saver;
use library::*;
use std::collections::HashSet;
use std::io::prelude::*;
use std::io::Result;
use std::path::Path;
use traits::*;
use version::Version;

pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec<String>) {
    let configs: Vec<&GObject> = env.config.objects.values()
        .filter(|c| {
            c.status.need_generate() &&
                c.type_id.map_or(false, |tid| tid.ns_id == namespaces::MAIN)
        })
        .collect();
    let mut has_get_quark = false;
    let mut has_any = false;
    for config in &configs {
        if let Type::Enumeration(ref enum_) = *env.library.type_(config.type_id.unwrap()) {
            has_any = true;
            if get_error_quark_name(enum_).is_some(){
                has_get_quark = true;
                break;
            }
        }
    }

    let path = root_path.join("enums.rs");
    file_saver::save_to_file(path, env.config.make_backup, |w| {
        try!(general::start_comments(w, &env.config));
        try!(writeln!(w, ""));
        try!(writeln!(w, "use ffi;"));
        if env.namespaces.glib_ns_id == namespaces::MAIN {
            if has_get_quark {
                try!(writeln!(w, "use ffi as glib_ffi;"));
                try!(writeln!(w, "use error::ErrorDomain;"));
            }
            try!(writeln!(w, "use translate::*;"));
        } else {
            if has_get_quark {
                try!(writeln!(w, "use glib_ffi;"));
                try!(writeln!(w, "use glib::error::ErrorDomain;"));
            }
            try!(writeln!(w, "use glib::translate::*;"));
        }
        try!(writeln!(w, ""));

        if has_any {
            mod_rs.push("\nmod enums;".into());
        }
        for config in &configs {
            if let Type::Enumeration(ref enum_) = *env.library.type_(config.type_id.unwrap()) {
                if let Some (cfg) = version_condition_string(env, enum_.version, false, 0) {
                    mod_rs.push(cfg);
                }
                mod_rs.push(format!("pub use self::enums::{};", enum_.name));
                try!(generate_enum(env, w, enum_, config));
            }
        }

        Ok(())
    });
}

fn generate_enum(env: &Env, w: &mut Write, enum_: &Enumeration, config: &GObject) -> Result<()> {
    struct Member {
        name: String,
        c_name: String,
        version: Option<Version>,
    }

    let mut members: Vec<Member> = Vec::new();
    let mut vals: HashSet<String> = HashSet::new();

    for member in &enum_.members {
        let member_config = config.members.matched(&member.name);
        let is_alias = member_config.iter().any(|m| m.alias);
        if is_alias || vals.contains(&member.value) {
            continue
        }
        vals.insert(member.value.clone());
        let version = member_config.iter().filter_map(|m| m.version).next();
        members.push(Member {
            name: member.name.to_camel(),
            c_name: member.c_identifier.clone(),
            version: version,
        });
    }

    try!(version_condition(w, env, enum_.version, false, 0));
    try!(writeln!(w, "#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]"));
    try!(writeln!(w, "pub enum {} {{", enum_.name));
    for member in &members {
        try!(version_condition(w, env, member.version, false, 1));
        try!(writeln!(w, "\t{},", member.name));
    }
    try!(writeln!(w, "{}", "    #[doc(hidden)]
    __Nonexhaustive(()),
}
"));

    try!(version_condition(w, env, enum_.version, false, 0));
    try!(writeln!(w, "#[doc(hidden)]
impl ToGlib for {name} {{
    type GlibType = ffi::{ffi_name};

    fn to_glib(&self) -> ffi::{ffi_name} {{
        match *self {{", name = enum_.name, ffi_name = enum_.c_type));
    for member in &members {
        try!(version_condition(w, env, member.version, false, 3));
        try!(writeln!(w, "\t\t\t{}::{} => ffi::{},", enum_.name, member.name, member.c_name));
    }
    try!(writeln!(w, "\t\t\t{}::__Nonexhaustive(_) => panic!(),", enum_.name));
    try!(writeln!(w, "{}",
"        }
    }
}
"));

    let assert = if env.config.generate_safety_asserts {
        "skip_assert_initialized!();\n\t\t"
    } else {
        ""
    };

    try!(version_condition(w, env, enum_.version, false, 0));
    try!(writeln!(w, "#[doc(hidden)]
impl FromGlib<ffi::{ffi_name}> for {name} {{
    fn from_glib(value: ffi::{ffi_name}) -> Self {{
        {assert}match value {{", name = enum_.name, ffi_name = enum_.c_type, assert = assert));
    for member in &members {
        try!(version_condition(w, env, member.version, false, 3));
        try!(writeln!(w, "\t\t\tffi::{} => {}::{},", member.c_name, enum_.name, member.name));
    }
    if members.len() == 1 {
        try!(writeln!(w, "\t\t\t_ => {}::__Nonexhaustive(()),", enum_.name));
    }
    try!(writeln!(w, "{}",
"        }
    }
}
"));
    if let Some(ref get_quark) = get_error_quark_name(enum_) {
        let get_quark = get_quark.replace("-", "_");
        let has_failed_member = members.iter().any(|m| m.name == "Failed");

        try!(version_condition(w, env, enum_.version, false, 0));
        try!(writeln!(w, "impl ErrorDomain for {name} {{
    fn domain() -> glib_ffi::GQuark {{
        {assert}unsafe {{ ffi::{get_quark}() }}
    }}

    fn code(self) -> i32 {{
        self.to_glib() as i32
    }}

    fn from(code: i32) -> Option<Self> {{
        {assert}match code {{", name = enum_.name, get_quark = get_quark, assert = assert));

        for member in &members {
            try!(version_condition(w, env, member.version, false, 3));
            try!(writeln!(w, "\t\t\tx if x == ffi::{} as i32 => Some({}::{}),", member.c_name,
                enum_.name, member.name));
        }
        if has_failed_member {
            try!(writeln!(w, "\t\t\t_ => Some({}::Failed),", enum_.name));
        } else {
            try!(writeln!(w, "\t\t\t_ => Some({}::__Nonexhaustive(())),", enum_.name));
        }

        try!(writeln!(w, "{}",
"        }
    }
}
"));
    }

    Ok(())
}

fn get_error_quark_name(enum_: &Enumeration) -> Option<String> {
    enum_.functions.iter()
        .find(|f| f.name == "quark")
        .and_then(|f| f.c_identifier.clone())
        .or_else(|| enum_.error_domain.clone())
}
<|endoftext|>


<commit_before>//! HTML encoding for node types in the "data" category
//!
//! ## Validators
//!
//! Currently validators are used for the `validator` property of two node
//! types: `DatatableColumn` and `Parameter`. This modules provides to functions
//! for each of these use cases.
//!
//! For use in a `DatatableColumn`, the usual `ToHtml` trait is implemented which
//! generates a custom element e.g. `<stencila-number-validator>` to indicate the
//! type of the column and it's restrictions.
//!
//! For use in a `Parameter`, the `ToAttrs` trait is implemented so that the validator
//! can be represented as standard HTML form input attributes e.g. `type="number" minimum="0"`

use super::{
    attr, attr_bool, attr_id, attr_itemprop, attr_itemtype, attr_itemtype_str, attr_prop,
    attr_slot, concat, concat_html, elem, elem_empty, elem_meta, elem_placeholder, nothing,
    EncodeContext, ToHtml,
};
use codec_txt::ToTxt;
use node_dispatch::dispatch_validator;
use std::string::ToString;
use stencila_schema::*;

/// Encode a `Datatable`
impl ToHtml for Datatable {
    fn to_html(&self, context: &EncodeContext) -> String {
        let columns = elem(
            "tr",
            &[attr_prop("columns")],
            &concat_html(&self.columns, context),
        );
        let rows = elem_meta("rows", "");
        let values = elem_meta("values", "");

        let head = elem("thead", &[], &[columns, rows, values].concat());

        let rows = self.columns.iter().fold(0, |mut rows, column| {
            let len = column.values.len();
            if len > rows {
                rows = len
            }
            rows
        });
        let rows = (0..rows)
            .into_iter()
            .map(|row| {
                let data = concat(&self.columns, |column| {
                    let data = if let Some(data) = column.values.get(row) {
                        data.to_html(context)
                    } else {
                        nothing()
                    };
                    elem("td", &[], &data)
                });
                elem("tr", &[], &data)
            })
            .collect::<Vec<String>>()
            .concat();
        let body = elem("tbody", &[], &rows);

        elem(
            "stencila-datatable",
            &[attr_itemtype::<Self>()],
            &elem("table", &[], &[head, body].concat()),
        )
    }
}

/// Encode a `DatatableColumn`
impl ToHtml for DatatableColumn {
    fn to_html(&self, context: &EncodeContext) -> String {
        let name = elem("span", &[attr_prop("name")], &self.name.to_html(context));
        elem("th", &[attr_itemtype::<Self>()], &[name].concat())
    }
}

/// Encode a `Parameter`
impl ToHtml for Parameter {
    fn to_html(&self, context: &EncodeContext) -> String {
        // Generate a unique id for the <input> to be able to associate the
        // <label> with it. We avoid using `self.name` or `self.name` which could
        // get updated via patches (and thus would need changing in two places).
        // But for determinism in tests, create a static id.
        let input_id = match cfg!(test) {
            true => "input-id".to_string(),
            false => uuids::generate("in").to_string(),
        };

        let name = elem(
            "label",
            &[attr_prop("name"), attr_slot("name"), attr("for", &input_id)],
            &self.name,
        );

        // Meta elements for `validator`, `default`, and `value` that add HTML Microdata and
        // are used as "proxies" to the attributes added to the <input> element when patching the DOM

        let validator = elem_empty(
            "meta",
            &[
                attr_itemprop("validator"),
                self.validator
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
            ],
        );

        let default = elem_empty(
            "meta",
            &[
                attr_itemprop("default"),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let value = elem_empty(
            "meta",
            &[
                attr_itemprop("value"),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let input = if let Some(ValidatorTypes::EnumValidator(validator)) =
            self.validator.as_deref()
        {
            // Select the `value`, or secondarily, the `default` <option>
            let value = self
                .value
                .as_deref()
                .or_else(|| self.default.as_deref())
                .map(|node| node.to_txt())
                .unwrap_or_default();

            let options = match validator.values.as_deref() {
                Some(values) => concat(values, |node| {
                    let txt = node.to_txt();
                    let selected = if txt == value { "selected" } else { "" };
                    elem("option", &[attr("value", &txt), selected.to_string()], &txt)
                }),
                None => nothing(),
            };

            elem(
                "select",
                &[attr("id", &input_id), attr_slot("value")],
                &[options].concat(),
            )
        } else {
            // Get the attrs corresponding to the validator so that we
            // can add them to the <input> element
            let validator_attrs = match &self.validator {
                Some(validator) => validator.to_attrs(context),
                None => vec![attr("type", "text")],
            };

            // If the parameter's `default` property is set then set a `placeholder` attribute
            let placeholder_attr = match &self.default {
                Some(node) => attr("placeholder", &node.to_txt()),
                None => "".to_string(),
            };

            let value_attr = match &self.value {
                Some(node) => attr("value", &node.to_txt()),
                None => "".to_string(),
            };

            // If a `BooleanValidator` then need to set the `checked` attribute if true
            let checked_attr =
                if let (Some(ValidatorTypes::BooleanValidator(..)), Some(Node::Boolean(true))) =
                    (self.validator.as_deref(), self.value.as_deref())
                {
                    attr("checked", "")
                } else {
                    nothing()
                };

            elem_empty(
                "input",
                &[
                    attr("id", &input_id),
                    attr_slot("value"),
                    validator_attrs.join(" "),
                    placeholder_attr,
                    value_attr,
                    checked_attr,
                ],
            )
        };

        elem(
            "stencila-parameter",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[name, validator, default, value, input].concat(),
        )
    }
}

/// Encode a `ValidatorTypes` variant
impl ToHtml for ValidatorTypes {
    fn to_html(&self, context: &EncodeContext) -> String {
        dispatch_validator!(self, to_html, context)
    }

    fn to_attrs(&self, context: &EncodeContext) -> Vec<String> {
        dispatch_validator!(self, to_attrs, context)
    }
}

/// Encode a `Validator`
///
/// Note that this is just an empty base for all other validators and should not
/// really be part of the `ValidatorTypes` enum and never be instantiated.
/// So this just logs a warning returns an empty string.
impl ToHtml for Validator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        tracing::warn!("Unexpected instantiation of `Validator` type");
        String::new()
    }
}

/// Encode a `ArrayValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for ArrayValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        todo!()
    }
}

/// Encode a `BooleanValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for BooleanValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        elem_empty(
            "stencila-boolean-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        vec![attr("type", "checkbox")]
    }
}

/// Encode a `ConstantValidator`
///
/// Encodes the constant `value`.
impl ToHtml for ConstantValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let value = elem(
            "span",
            &[attr_prop("value"), attr_slot("value")],
            &self.value.to_html(context),
        );
        elem(
            "stencila-constant-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &value,
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // The `type=text` could be changed to depend on the
        // `Node` type of the `value`.
        vec![attr("type", "text"), attr_bool("readonly")]
    }
}

/// Encode a `EnumValidator`
///
/// Encodes the possible `values`. Each of these will be an element
/// indicating the type of the value.
impl ToHtml for EnumValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let values = elem(
            "div",
            &[attr_prop("values"), attr_slot("values")],
            &self.values.to_html(context),
        );
        elem(
            "stencila-enum-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &values,
        )
    }
}

/// Encode a `IntegerValidator`
///
/// No properties, so just an empty element used to indicate the type.
/// It is probable that in the future, this will have properties such as
/// `minimum` and `maximum` like `NumberValidator`.
impl ToHtml for IntegerValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        elem_empty(
            "stencila-integer-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        vec![attr("type", "number")]
    }
}

/// Encode a `NumberValidator`
///
/// Encodes all properties
impl ToHtml for NumberValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        // We use `.map(|value| value.to_string()` for properties so they get
        // rendered as text, not wrapped as a `<span itemtype="https://schema.stenci.la/Number"...`

        let minimum = elem_placeholder(
            "span",
            &[attr_prop("minimum"), attr_slot("minimum")],
            &self.minimum.map(|value| value.to_string()),
            context,
        );

        let exclusive_minimum = elem_placeholder(
            "span",
            &[
                attr_prop("exclusive_minimum"),
                attr_slot("exclusive-minimum"),
            ],
            &self.exclusive_minimum.map(|value| value.to_string()),
            context,
        );

        let maximum = elem_placeholder(
            "span",
            &[attr_prop("maximum"), attr_slot("maximum")],
            &self.maximum.map(|value| value.to_string()),
            context,
        );

        let exclusive_maximum = elem_placeholder(
            "span",
            &[
                attr_prop("exclusive_maximum"),
                attr_slot("exclusive-maximum"),
            ],
            &self.exclusive_maximum.map(|value| value.to_string()),
            context,
        );

        let multiple_of = elem_placeholder(
            "span",
            &[attr_prop("multiple_of"), attr_slot("multiple-of")],
            &self.multiple_of.map(|value| value.to_string()),
            context,
        );

        elem(
            "stencila-number-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[
                minimum,
                exclusive_minimum,
                maximum,
                exclusive_maximum,
                multiple_of,
            ]
            .concat(),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number for
        // attributes supported here.
        let mut attrs = Vec::with_capacity(4);
        attrs.push(attr("type", "number"));
        if let Some(min) = self.minimum {
            attrs.push(attr("min", &min.to_string()))
        }
        if let Some(max) = self.maximum {
            attrs.push(attr("max", &max.to_string()))
        }
        if let Some(step) = self.multiple_of {
            attrs.push(attr("step", &step.to_string()))
        }
        attrs
    }
}

/// Encode a `StringValidator`
///
/// Encodes all properties
impl ToHtml for StringValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let min_length = elem_placeholder(
            "span",
            &[attr_prop("min_length"), attr_slot("min-length")],
            &self.min_length.map(|value| value.to_string()),
            context,
        );

        let max_length = elem_placeholder(
            "span",
            &[attr_prop("max_length"), attr_slot("max-length")],
            &self.max_length.map(|value| value.to_string()),
            context,
        );

        let pattern = elem_placeholder(
            "span",
            &[attr_prop("pattern"), attr_slot("pattern")],
            &self.pattern,
            context,
        );

        elem(
            "stencila-string-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[min_length, max_length, pattern].concat(),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text for
        // attributes supported here.
        let mut attrs = Vec::with_capacity(4);
        attrs.push(attr("type", "text"));
        if let Some(min_length) = self.min_length {
            attrs.push(attr("minlength", &min_length.to_string()))
        }
        if let Some(max_length) = self.max_length {
            attrs.push(attr("maxlength", &max_length.to_string()))
        }
        if let Some(pattern) = &self.pattern {
            attrs.push(attr("pattern", pattern))
        }
        attrs
    }
}

/// Encode a `TupleValidator`
///
/// Encodes each of the validators in `items`
impl ToHtml for TupleValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        elem(
            "stencila-tuple-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &self.items.to_html(context),
        )
    }
}
<commit_msg>chore(HTML codec): Update URL in comment<commit_after>//! HTML encoding for node types in the "data" category
//!
//! ## Validators
//!
//! Currently validators are used for the `validator` property of two node
//! types: `DatatableColumn` and `Parameter`. This modules provides to functions
//! for each of these use cases.
//!
//! For use in a `DatatableColumn`, the usual `ToHtml` trait is implemented which
//! generates a custom element e.g. `<stencila-number-validator>` to indicate the
//! type of the column and it's restrictions.
//!
//! For use in a `Parameter`, the `ToAttrs` trait is implemented so that the validator
//! can be represented as standard HTML form input attributes e.g. `type="number" minimum="0"`

use super::{
    attr, attr_bool, attr_id, attr_itemprop, attr_itemtype, attr_itemtype_str, attr_prop,
    attr_slot, concat, concat_html, elem, elem_empty, elem_meta, elem_placeholder, nothing,
    EncodeContext, ToHtml,
};
use codec_txt::ToTxt;
use node_dispatch::dispatch_validator;
use std::string::ToString;
use stencila_schema::*;

/// Encode a `Datatable`
impl ToHtml for Datatable {
    fn to_html(&self, context: &EncodeContext) -> String {
        let columns = elem(
            "tr",
            &[attr_prop("columns")],
            &concat_html(&self.columns, context),
        );
        let rows = elem_meta("rows", "");
        let values = elem_meta("values", "");

        let head = elem("thead", &[], &[columns, rows, values].concat());

        let rows = self.columns.iter().fold(0, |mut rows, column| {
            let len = column.values.len();
            if len > rows {
                rows = len
            }
            rows
        });
        let rows = (0..rows)
            .into_iter()
            .map(|row| {
                let data = concat(&self.columns, |column| {
                    let data = if let Some(data) = column.values.get(row) {
                        data.to_html(context)
                    } else {
                        nothing()
                    };
                    elem("td", &[], &data)
                });
                elem("tr", &[], &data)
            })
            .collect::<Vec<String>>()
            .concat();
        let body = elem("tbody", &[], &rows);

        elem(
            "stencila-datatable",
            &[attr_itemtype::<Self>()],
            &elem("table", &[], &[head, body].concat()),
        )
    }
}

/// Encode a `DatatableColumn`
impl ToHtml for DatatableColumn {
    fn to_html(&self, context: &EncodeContext) -> String {
        let name = elem("span", &[attr_prop("name")], &self.name.to_html(context));
        elem("th", &[attr_itemtype::<Self>()], &[name].concat())
    }
}

/// Encode a `Parameter`
impl ToHtml for Parameter {
    fn to_html(&self, context: &EncodeContext) -> String {
        // Generate a unique id for the <input> to be able to associate the
        // <label> with it. We avoid using `self.name` or `self.name` which could
        // get updated via patches (and thus would need changing in two places).
        // But for determinism in tests, create a static id.
        let input_id = match cfg!(test) {
            true => "input-id".to_string(),
            false => uuids::generate("in").to_string(),
        };

        let name = elem(
            "label",
            &[attr_prop("name"), attr_slot("name"), attr("for", &input_id)],
            &self.name,
        );

        // Meta elements for `validator`, `default`, and `value` that add HTML Microdata and
        // are used as "proxies" to the attributes added to the <input> element when patching the DOM

        let validator = elem_empty(
            "meta",
            &[
                attr_itemprop("validator"),
                self.validator
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
            ],
        );

        let default = elem_empty(
            "meta",
            &[
                attr_itemprop("default"),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let value = elem_empty(
            "meta",
            &[
                attr_itemprop("value"),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let input = if let Some(ValidatorTypes::EnumValidator(validator)) =
            self.validator.as_deref()
        {
            // Select the `value`, or secondarily, the `default` <option>
            let value = self
                .value
                .as_deref()
                .or_else(|| self.default.as_deref())
                .map(|node| node.to_txt())
                .unwrap_or_default();

            let options = match validator.values.as_deref() {
                Some(values) => concat(values, |node| {
                    let txt = node.to_txt();
                    let selected = if txt == value { "selected" } else { "" };
                    elem("option", &[attr("value", &txt), selected.to_string()], &txt)
                }),
                None => nothing(),
            };

            elem(
                "select",
                &[attr("id", &input_id), attr_slot("value")],
                &[options].concat(),
            )
        } else {
            // Get the attrs corresponding to the validator so that we
            // can add them to the <input> element
            let validator_attrs = match &self.validator {
                Some(validator) => validator.to_attrs(context),
                None => vec![attr("type", "text")],
            };

            // If the parameter's `default` property is set then set a `placeholder` attribute
            let placeholder_attr = match &self.default {
                Some(node) => attr("placeholder", &node.to_txt()),
                None => "".to_string(),
            };

            let value_attr = match &self.value {
                Some(node) => attr("value", &node.to_txt()),
                None => "".to_string(),
            };

            // If a `BooleanValidator` then need to set the `checked` attribute if true
            let checked_attr =
                if let (Some(ValidatorTypes::BooleanValidator(..)), Some(Node::Boolean(true))) =
                    (self.validator.as_deref(), self.value.as_deref())
                {
                    attr("checked", "")
                } else {
                    nothing()
                };

            elem_empty(
                "input",
                &[
                    attr("id", &input_id),
                    attr_slot("value"),
                    validator_attrs.join(" "),
                    placeholder_attr,
                    value_attr,
                    checked_attr,
                ],
            )
        };

        elem(
            "stencila-parameter",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[name, validator, default, value, input].concat(),
        )
    }
}

/// Encode a `ValidatorTypes` variant
impl ToHtml for ValidatorTypes {
    fn to_html(&self, context: &EncodeContext) -> String {
        dispatch_validator!(self, to_html, context)
    }

    fn to_attrs(&self, context: &EncodeContext) -> Vec<String> {
        dispatch_validator!(self, to_attrs, context)
    }
}

/// Encode a `Validator`
///
/// Note that this is just an empty base for all other validators and should not
/// really be part of the `ValidatorTypes` enum and never be instantiated.
/// So this just logs a warning returns an empty string.
impl ToHtml for Validator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        tracing::warn!("Unexpected instantiation of `Validator` type");
        String::new()
    }
}

/// Encode a `ArrayValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for ArrayValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        todo!()
    }
}

/// Encode a `BooleanValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for BooleanValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        elem_empty(
            "stencila-boolean-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        vec![attr("type", "checkbox")]
    }
}

/// Encode a `ConstantValidator`
///
/// Encodes the constant `value`.
impl ToHtml for ConstantValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let value = elem(
            "span",
            &[attr_prop("value"), attr_slot("value")],
            &self.value.to_html(context),
        );
        elem(
            "stencila-constant-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &value,
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // The `type=text` could be changed to depend on the
        // `Node` type of the `value`.
        vec![attr("type", "text"), attr_bool("readonly")]
    }
}

/// Encode a `EnumValidator`
///
/// Encodes the possible `values`. Each of these will be an element
/// indicating the type of the value.
impl ToHtml for EnumValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let values = elem(
            "div",
            &[attr_prop("values"), attr_slot("values")],
            &self.values.to_html(context),
        );
        elem(
            "stencila-enum-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &values,
        )
    }
}

/// Encode a `IntegerValidator`
///
/// No properties, so just an empty element used to indicate the type.
/// It is probable that in the future, this will have properties such as
/// `minimum` and `maximum` like `NumberValidator`.
impl ToHtml for IntegerValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        elem_empty(
            "stencila-integer-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        vec![attr("type", "number")]
    }
}

/// Encode a `NumberValidator`
///
/// Encodes all properties
impl ToHtml for NumberValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        // We use `.map(|value| value.to_string())` for properties so they get
        // rendered as text, not wrapped as a `<span itemtype="https://schema.org/Number"...`

        let minimum = elem_placeholder(
            "span",
            &[attr_prop("minimum"), attr_slot("minimum")],
            &self.minimum.map(|value| value.to_string()),
            context,
        );

        let exclusive_minimum = elem_placeholder(
            "span",
            &[
                attr_prop("exclusive_minimum"),
                attr_slot("exclusive-minimum"),
            ],
            &self.exclusive_minimum.map(|value| value.to_string()),
            context,
        );

        let maximum = elem_placeholder(
            "span",
            &[attr_prop("maximum"), attr_slot("maximum")],
            &self.maximum.map(|value| value.to_string()),
            context,
        );

        let exclusive_maximum = elem_placeholder(
            "span",
            &[
                attr_prop("exclusive_maximum"),
                attr_slot("exclusive-maximum"),
            ],
            &self.exclusive_maximum.map(|value| value.to_string()),
            context,
        );

        let multiple_of = elem_placeholder(
            "span",
            &[attr_prop("multiple_of"), attr_slot("multiple-of")],
            &self.multiple_of.map(|value| value.to_string()),
            context,
        );

        elem(
            "stencila-number-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[
                minimum,
                exclusive_minimum,
                maximum,
                exclusive_maximum,
                multiple_of,
            ]
            .concat(),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number for
        // attributes supported here.
        let mut attrs = Vec::with_capacity(4);
        attrs.push(attr("type", "number"));
        if let Some(min) = self.minimum {
            attrs.push(attr("min", &min.to_string()))
        }
        if let Some(max) = self.maximum {
            attrs.push(attr("max", &max.to_string()))
        }
        if let Some(step) = self.multiple_of {
            attrs.push(attr("step", &step.to_string()))
        }
        attrs
    }
}

/// Encode a `StringValidator`
///
/// Encodes all properties
impl ToHtml for StringValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let min_length = elem_placeholder(
            "span",
            &[attr_prop("min_length"), attr_slot("min-length")],
            &self.min_length.map(|value| value.to_string()),
            context,
        );

        let max_length = elem_placeholder(
            "span",
            &[attr_prop("max_length"), attr_slot("max-length")],
            &self.max_length.map(|value| value.to_string()),
            context,
        );

        let pattern = elem_placeholder(
            "span",
            &[attr_prop("pattern"), attr_slot("pattern")],
            &self.pattern,
            context,
        );

        elem(
            "stencila-string-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[min_length, max_length, pattern].concat(),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text for
        // attributes supported here.
        let mut attrs = Vec::with_capacity(4);
        attrs.push(attr("type", "text"));
        if let Some(min_length) = self.min_length {
            attrs.push(attr("minlength", &min_length.to_string()))
        }
        if let Some(max_length) = self.max_length {
            attrs.push(attr("maxlength", &max_length.to_string()))
        }
        if let Some(pattern) = &self.pattern {
            attrs.push(attr("pattern", pattern))
        }
        attrs
    }
}

/// Encode a `TupleValidator`
///
/// Encodes each of the validators in `items`
impl ToHtml for TupleValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        elem(
            "stencila-tuple-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &self.items.to_html(context),
        )
    }
}
<|endoftext|>