1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use algo::{AlgoInput, AlgoOutput, JsonValue};
use std::error::Error as StdError;
use error::{Error, ErrorKind, ResultExt};
use json;

#[cfg(feature="with-serde")]
use serde::Deserialize;
#[cfg(feature="with-rustc-serialize")]
use rustc_serialize::Decodable;

/// Alternate implementation for `EntryPoint`
///   that automatically decodes JSON input to the associate type
///
/// # Examples
/// ```no_run
/// # use algorithmia::prelude::*;
/// # use std::error::Error;
/// # #[derive(Default)]
/// # struct Algo;
/// impl DecodedEntryPoint for Algo {
///     // Expect input to be an array of 2 strings
///     type Input = (String, String);
///     fn apply_decoded(&self, input: Self::Input) -> Result<AlgoOutput, Box<Error>> {
///         let msg = format!("{} - {}", input.0, input.1);
///         Ok(msg.into())
///     }
/// }
/// ```
pub trait DecodedEntryPoint: Default {
    /// Specifies the type that the input will be automatically deserialized into
    #[cfg(feature="with-serde")]
    type Input: Deserialize;
    /// Specifies the type that the input will be automatically decoded into
    #[cfg(feature="with-rustc-serialize")]
    #[deprecated(since="2.1.0", note="rustc-serialize has been deprecated")]
    type Input: Decodable;

    /// This method is an apply variant that will receive the decoded form of JSON input.
    ///   If decoding failed, a `DecoderError` will be returned before this method is invoked.
    #[allow(unused_variables)]
    fn apply_decoded(&self, input: Self::Input) -> Result<AlgoOutput, Box<StdError>>;
}

impl<T> EntryPoint for T
    where T: DecodedEntryPoint
{
    fn apply(&self, input: AlgoInput) -> Result<AlgoOutput, Box<StdError>> {
        match input.as_json() {
            Some(obj) => {
                let decoded = json::decode_value(obj.into_owned())
                    .chain_err(|| "failed to parse input as JSON into the expected type")?;
                self.apply_decoded(decoded)
            }
            None => Err(Error::from(ErrorKind::UnsupportedInput).into()),
        }
    }
}

/// Implementing an algorithm involves overriding at least one of these methods
pub trait EntryPoint: Default {
    #[allow(unused_variables)]
    /// Override to handle string input
    fn apply_str(&self, text: &str) -> Result<AlgoOutput, Box<StdError>> {
        Err(Error::from(ErrorKind::UnsupportedInput).into())
    }

    #[allow(unused_variables)]
    /// Override to handle JSON input (see also [`DecodedEntryPoint`](trait.DecodedEntryPoint.html))
    fn apply_json(&self, json: &JsonValue) -> Result<AlgoOutput, Box<StdError>> {
        Err(Error::from(ErrorKind::UnsupportedInput).into())
    }

    #[allow(unused_variables)]
    /// Override to handle binary input
    fn apply_bytes(&self, bytes: &[u8]) -> Result<AlgoOutput, Box<StdError>> {
        Err(Error::from(ErrorKind::UnsupportedInput).into())
    }

    /// The default implementation of this method calls
    /// `apply_str`, `apply_json`, or `apply_bytes` based on the input type.
    ///
    /// - `AlgoInput::Text` results in call to  `apply_str`
    /// - `AlgoInput::Json` results in call to  `apply_json`
    /// - `AlgoInput::Binary` results in call to  `apply_bytes`
    ///
    /// If that call returns anKind `UnsupportedInput` error, then this method
    ///   method will may attempt to coerce the input into another type
    ///   and attempt one more call:
    ///
    /// - `AlgoInput::Text` input will be JSON-encoded to call `apply_json`
    /// - `AlgoInput::Json` input will be parse to see it can call `apply_str`
    fn apply(&self, input: AlgoInput) -> Result<AlgoOutput, Box<StdError>> {
        match input {
            AlgoInput::Text(ref text) => {
                match self.apply_str(text) {
                    Err(err) => {
                        match err.downcast::<Error>().map(|err| *err) {
                            Ok(Error(ErrorKind::UnsupportedInput, _)) => {
                                match input.as_json() {
                                    Some(json) => self.apply_json(&json),
                                    None => Err(Error::from(ErrorKind::UnsupportedInput).into()),
                                }
                            }
                            Ok(err) => Err(err.into()),
                            Err(err) => Err(err.into()),
                        }
                    }
                    ret => ret,
                }
            }
            AlgoInput::Json(ref json) => {
                match self.apply_json(json) {
                    Err(err) => {
                        match err.downcast::<Error>().map(|err| *err) {
                            Ok(Error(ErrorKind::UnsupportedInput, _)) => {
                                match input.as_string() {
                                    Some(text) => self.apply_str(text),
                                    None => {
                                        Err(Error::from(ErrorKind::UnsupportedInput).into()).into()
                                    }
                                }
                            }
                            Ok(err) => Err(err.into()),
                            Err(err) => Err(err.into()),
                        }
                    }
                    ret => ret,
                }
            }
            AlgoInput::Binary(ref bytes) => self.apply_bytes(bytes),
        }
    }
}