<commit_before>//! Algorithm module for executing Algorithmia algorithms
//!
//! # Examples
//!
//! ```no_run
//! use algorithmia::Service;
//! use algorithmia::algorithm::{AlgorithmOutput, Version};
//!
//! // Initialize with an API key
//! let algo_service = Service::new("111112222233333444445555566");
//! let factor = algo_service.algorithm("kenny", "Factor", Version::Latest);
//!
//! // Run the algorithm using a type safe decoding of the output to Vec<int>
//! //   since this algorithm outputs results as a JSON array of integers
//! let input = "19635".to_string();
//! let output: AlgorithmOutput<Vec<i64>> = factor.exec(&input).unwrap();
//! println!("Completed in {} seconds with result: {:?}", output.duration, output.result);
//! ```

extern crate hyper;

use ::{Service, AlgorithmiaError, API_BASE_URL};
use hyper::Url;
use rustc_serialize::{json, Decoder, Decodable, Encodable};
use std::io::Read;
use std::fmt;

static ALGORITHM_BASE_PATH: &'static str = "api";

/// Algorithmia algorithm
pub struct Algorithm<'a> {
    pub user: &'a str,
    pub repo: &'a str,
    pub version: Version<'a>,
}

/// Version of an algorithm
pub enum Version<'a> {
    /// Latest published version
    Latest,
    /// Latest published version with the same minor version, e.g., 1.2 implies 1.2.*
    Minor(u32, u32),
    /// A specific published revision, e.g., 0.1.0
    Revision(u32, u32, u32),
    /// A specific git hash - only works for the algorithm's author
    Hash(&'a str),
}

/// Result type for generic `AlgorithmOutput` when calling `exec`
pub type AlgorithmResult<T> = Result<AlgorithmOutput<T>, AlgorithmiaError>;
/// Result type for the raw JSON returned by calling `exec_raw`
pub type AlgorithmJsonResult = Result<String, hyper::HttpError>;

/// Generic struct for decoding an algorithm response JSON
#[derive(RustcDecodable, Debug)]
pub struct AlgorithmOutput<T> {
    pub duration: f32,
    pub result: T,
}

/// Service endpoint for executing algorithms
pub struct AlgorithmService<'a> {
    pub service: Service,
    pub algorithm: Algorithm<'a>,
}

impl <'a> Version<'a> {
    /// Initialize a Version from a version string slice
    ///
    /// # Examples
    /// ```
    /// # use algorithmia::algorithm::Version;
    /// assert_eq!(Version::from_str("1.2").to_string(), Version::Minor(1,2).to_string());
    /// assert_eq!(Version::from_str("1.2.3").to_string(), Version::Revision(1,2,3).to_string());
    /// assert_eq!(Version::from_str("abc123").to_string(), Version::Hash("abc123").to_string());
    /// ```
    pub fn from_str(version: &'a str) -> Version<'a> {
        match version.split('.').map(|p| p.parse::<u32>()).collect() {
            Ok(parts) => {
                let ver_parts: Vec<u32> = parts;
                match &*ver_parts {
                    [major, minor, revision] => Version::Revision(major, minor, revision),
                    [major, minor] => Version::Minor(major, minor),
                    _ => panic!("Failed to parse version {}", version),
                }
            },
            _ => Version::Hash(version),
        }
    }

    /// Convert a Verion to string (uses its Display trait implementation)
    pub fn to_string(&self) -> String { format!("{}", self) }
}

impl<'a> Algorithm<'a> {
    /// Get the API Endpoint URL for a particular algorithm
    pub fn to_url(&self) -> Url {
        let url_string = match self.version {
            Version::Latest => format!("{}/{}/{}/{}", API_BASE_URL, ALGORITHM_BASE_PATH, self.user, self.repo),
            ref version => format!("{}/{}/{}/{}/{}", API_BASE_URL, ALGORITHM_BASE_PATH, self.user, self.repo, version),
        };
        Url::parse(&*url_string).unwrap()
    }
}

impl<'c> AlgorithmService<'c> {

    /// Instantiate `AlgorithmService` directly - alternative to `Service::algorithm`
    ///
    /// # Examples
    /// ```
    /// # use algorithmia::algorithm::{AlgorithmService, Version};
    /// let mut factor = AlgorithmService::new("111112222233333444445555566", "kenny", "Factor", Version::Latest);
    /// ```
    pub fn new(api_key: &'c str, user: &'c str, repo: &'c str, version: Version<'c>) -> AlgorithmService<'c> {
        AlgorithmService {
            service: Service::new(api_key),
            algorithm: Algorithm{ user: user, repo: repo, version: version },
        }
    }

    /// Execute an algorithm with typed JSON response decoding
    ///
    /// input_data must be JSON-encodable
    ///     use `#[derive(RustcEncodable)]` for complex input
    ///
    /// You must explicitly specify the output type `T`
    ///     `exec` will attempt to decode the response into AlgorithmOutput<T>
    ///
    /// If decoding fails, it will attempt to decode into `ApiError`
    ///     and if that fails, it will error with `DecoderErrorWithContext`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use algorithmia::{Service, AlgorithmiaError};
    /// # use algorithmia::algorithm::{AlgorithmOutput, Version};
    /// let algo_service = Service::new("111112222233333444445555566");
    /// let mut factor = algo_service.algorithm("kenny", "Factor", Version::Latest);
    /// let input = "19635".to_string();
    /// match factor.exec(&input) {
    ///     Ok(out) => {
    ///         let myVal: AlgorithmOutput<Vec<i64>> = out;
    ///         println!("{:?}", myVal.result);
    ///     },
    ///     Err(AlgorithmiaError::ApiError(error)) => {
    ///         println!("API Error: {:?}", error)
    ///     },
    ///     Err(e) => println!("ERROR: {:?}", e),
    /// };
    /// ```
    pub fn exec<'a, D, E>(&'c self, input_data: &E) -> AlgorithmResult<D>
            where D: Decodable,
                  E: Encodable {
        let raw_input = try!(json::encode(input_data));
        let res_json = try!(self.exec_raw(&*raw_input));
<commit_msg>Helpers for instantiating algos<commit_after>//! Algorithm module for executing Algorithmia algorithms
//!
//! # Examples
//!
//! ```no_run
//! use algorithmia::Service;
//! use algorithmia::algorithm::{AlgorithmOutput, Version};
//!
//! // Initialize with an API key
//! let algo_service = Service::new("111112222233333444445555566");
//! let factor = algo_service.algorithm("kenny", "Factor", Version::Latest);
//!
//! // Run the algorithm using a type safe decoding of the output to Vec<int>
//! //   since this algorithm outputs results as a JSON array of integers
//! let input = "19635".to_string();
//! let output: AlgorithmOutput<Vec<i64>> = factor.exec(&input).unwrap();
//! println!("Completed in {} seconds with result: {:?}", output.duration, output.result);
//! ```

extern crate hyper;

use ::{Service, AlgorithmiaError, API_BASE_URL};
use hyper::Url;
use rustc_serialize::{json, Decoder, Decodable, Encodable};
use std::io::Read;
use std::fmt;

static ALGORITHM_BASE_PATH: &'static str = "api";

/// Algorithmia algorithm
pub struct Algorithm<'a> {
    pub user: &'a str,
    pub repo: &'a str,
    pub version: Version<'a>,
}

/// Version of an algorithm
pub enum Version<'a> {
    /// Latest published version
    Latest,
    /// Latest published version with the same minor version, e.g., 1.2 implies 1.2.*
    Minor(u32, u32),
    /// A specific published revision, e.g., 0.1.0
    Revision(u32, u32, u32),
    /// A specific git hash - only works for the algorithm's author
    Hash(&'a str),
}

/// Result type for generic `AlgorithmOutput` when calling `exec`
pub type AlgorithmResult<T> = Result<AlgorithmOutput<T>, AlgorithmiaError>;
/// Result type for the raw JSON returned by calling `exec_raw`
pub type AlgorithmJsonResult = Result<String, hyper::HttpError>;

/// Generic struct for decoding an algorithm response JSON
#[derive(RustcDecodable, Debug)]
pub struct AlgorithmOutput<T> {
    pub duration: f32,
    pub result: T,
}

/// Service endpoint for executing algorithms
pub struct AlgorithmService<'a> {
    pub service: Service,
    pub algorithm: Algorithm<'a>,
}

impl <'a> Version<'a> {
    /// Initialize a Version from a version string slice
    ///
    /// # Examples
    /// ```
    /// # use algorithmia::algorithm::Version;
    /// assert_eq!(Version::from_str("1.2").to_string(), Version::Minor(1,2).to_string());
    /// assert_eq!(Version::from_str("1.2.3").to_string(), Version::Revision(1,2,3).to_string());
    /// assert_eq!(Version::from_str("abc123").to_string(), Version::Hash("abc123").to_string());
    /// ```
    pub fn from_str(version: &'a str) -> Version<'a> {
        match version.split('.').map(|p| p.parse::<u32>()).collect() {
            Ok(parts) => {
                let ver_parts: Vec<u32> = parts;
                match &*ver_parts {
                    [major, minor, revision] => Version::Revision(major, minor, revision),
                    [major, minor] => Version::Minor(major, minor),
                    _ => panic!("Failed to parse version {}", version),
                }
            },
            _ => Version::Hash(version),
        }
    }

    /// Convert a Verion to string (uses its Display trait implementation)
    pub fn to_string(&self) -> String { format!("{}", self) }
}

impl<'a> Algorithm<'a> {
    /// Instantiate an algorithm from it's parts
    ///
    /// # Examples
    /// ```
    /// # use algorithmia::algorithm::{Algorithm, Version};
    /// let factor = Algorithm::new("kenny", "Factor", Version::Minor(0,1));
    /// ```
    pub fn new(user: &'a str, repo: &'a str, version: Version<'a>) -> Algorithm<'a> {
        Algorithm {
            user: user,
            repo: repo,
            version: version
        }
    }

    /// Instantiate an algorithm from the algorithm's URI
    ///
    /// # Examples
    /// ```
    /// # use algorithmia::algorithm::{Algorithm, Version};
    /// let factor = Algorithm::from_str("kenny/Factor/0.1");
    /// ```
    pub fn from_str(algo_uri: &'a str) -> Result<Algorithm<'a>, &'a str> {
        // TODO: strip optional 'algo://' prefix
        match &*algo_uri.split("/").collect::<Vec<_>>() {
            [user, algo, version] => Ok(Algorithm::new(user, algo, Version::from_str(version))),
            [user, algo] => Ok(Algorithm::new(user, algo, Version::Latest)),
            _ => Err("Invalid algorithm URI")
        }
    }

    /// Get the API Endpoint URL for a particular algorithm
    pub fn to_url(&self) -> Url {
        let url_string = match self.version {
            Version::Latest => format!("{}/{}/{}/{}", API_BASE_URL, ALGORITHM_BASE_PATH, self.user, self.repo),
            ref version => format!("{}/{}/{}/{}/{}", API_BASE_URL, ALGORITHM_BASE_PATH, self.user, self.repo, version),
        };
        Url::parse(&*url_string).unwrap()
    }
}

impl<'c> AlgorithmService<'c> {

    /// Instantiate `AlgorithmService` directly - alternative to `Service::algorithm`
    ///
    /// # Examples
    /// ```
    /// # use algorithmia::algorithm::{AlgorithmService, Version};
    /// let factor = AlgorithmService::new("111112222233333444445555566", "kenny", "Factor", Version::Latest);
    /// ```
    pub fn new(api_key: &'c str, user: &'c str, repo: &'c str, version: Version<'c>) -> AlgorithmService<'c> {
        AlgorithmService {
            service: Service::new(api_key),
            algorithm: Algorithm{ user: user, repo: repo, version: version },
        }
    }

    /// Execute an algorithm with typed JSON response decoding
    ///
    /// input_data must be JSON-encodable
    ///     use `#[derive(RustcEncodable)]` for complex input
    ///
    /// You must explicitly specify the output type `T`
    ///     `exec` will attempt to decode the response into AlgorithmOutput<T>
    ///
    /// If decoding fails, it will attempt to decode into `ApiError`
    ///     and if that fails, it will error with `DecoderErrorWithContext`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use algorithmia::{Service, AlgorithmiaError};
    /// # use algorithmia::algorithm::{AlgorithmOutput, Version};
    /// let algo_service = Service::new("111112222233333444445555566");
    /// let factor = algo_service.algorithm("kenny", "Factor", Version::Latest);
    /// let input = "19635".to_string();
    /// match factor.exec(&input) {
    ///     Ok(out) => {
    ///         let myVal: AlgorithmOutput<Vec<i64>> = out;
    ///         println!("{:?}", myVal.result);
    ///     },
    ///     Err(AlgorithmiaError::ApiError(error)) => {
    ///         println!("API Error: {:?}", error)
    ///     },
    ///     Err(e) => println!("ERROR: {:?}", e),
    /// };
    /// ```
    pub fn exec<'a, D, E>(&'c self, input_data: &E) -> AlgorithmResult<D>
            where D: Decodable,
                  E: Encodable {
        let raw_input = try!(json::encode(input_data));
        let res_json = try!(self.exec_raw(&*raw_input));

{'fraction_non_alphanumeric': 0.17572136174227534, 'fraction_numerical': 0.021385329035301517, 'mean_word_length': 2.6625395911315866, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 102, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4286275', 'n_tokens_mistral': 4048, 'n_tokens_neox': 3854, 'n_words': 1009}