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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
//! Algorithmia entrypoint for authoring Rust-based algorithms //! //! This module contains the traits that are used for defining the entrypoint //! of a rust-based algorithm on the Algorithmia platform. //! //! Rust algorithms are loaded by instantiating an `Algo` type via `Algo::default()` //! and each API request invokes the `apply` method on that instance. //! While it is possible to manually define the `Algo` type and implement //! either `EntryPoint` or `DecodedEntryPoint`, the `#[entrypoint]` //! attribute will generate these implementations for you based on the signature //! of the function (or impl) that it is attached to. //! //! ## `#[entrypoint]` Usage //! //! Just annotate a function with `#[entrypoint]` //! //! ```no_compile //! #[entrypoint] //! fn apply(name: String) -> Result<String, String> { //! Ok(format!("Hello, {}.", name)) //! } //! ``` //! //! This will generate the algorithm entrypoint as long the function //! signature specifices supported input and output types: //! //! **Supported input types**: //! - String types (e.g. `&str` and `String`) //! - Byte types (e.g. `&[u8]` and `Vec<u8>`) //! - Json `&Value` type //! - `AlgoIo` enum type (for matching on text, json, and binary input) //! - Any type that implements `serde::Deserialize` (e.g. `#[derive(Deserialize)]`) //! //! **Supported output (`Ok` variant of return value)**: //! - `String`, `Vec<u8>`, `Value`, `AlgoIo` //! - Any type that implements `serde::Serialize` (e.g. `#[derive(Serialize)]`) //! //! **Supported error types (`Err` variant of return value)**: //! - Any type with a conversion to `Box<std::error::Error>`. This includes `String` and basically any type that implements the `Error` trait. //! //! //! ## Automatic JSON serialization/deserialization //! //! Since `#[entrypoint]` supports `Deserialize` input and `Serialize` output, this example will //! accept a JSON array and return a JSON number: //! //! ```no_compile //! #[entrypoint] //! fn start(names: Vec<String>) -> Result<usize, String> { //! Ok(name.len()) //! } //! ``` //! //! To use your own custom types as input and output, simply implement `Deserialize` and //! `Serialize` respectively. //! //! ```no_compile //! #[derive(Deserialize) //! struct Input { titles: Vec<String> } //! //! #[derive(Serialize) //! struct Output { count: u32 } //! //! #[entrypoint] //! fn start(input: Input) -> Result<Output, String> { //! Ok(Output{ count: input.titles.len() }) //! } //! ``` //! //! ## Preloading (advanced usage) //! //! If your algorithm has a preload step that doesn't vary with user input (e.g. loading a model), //! you can create a type that implements `Default`, and use a method on that type as your //! entrypoint (the `#[entrypoint]` annotation goes on the impl of your type. Multiple API calls in //! succession from a single user will only instantiate the type once, but call `apply` multiple //! times: //! //! ```no_compile //! #[derive(Deserialize) //! struct Input { titles: Vec<String>, max: u32 } //! //! #[derive(Serialize) //! struct Output { titles: Vec<String> } //! //! struct App { model: Vec<u8> } //! //! #[entrypoint] //! impl App { //! fn apply(&mut self, input: Input) -> Result<Output, String> { //! unimplemented!(); //! } //! } //! //! impl Default for App { //! fn default() -> Self { //! App { model: load_model() } //! } //! } //! ``` use crate::algo::AlgoIo; use crate::error::{ApiError, ResultExt, INPUT_ERROR, UNSUPPORTED_ERROR}; use serde_json; use std::error::Error as StdError; use serde::de::DeserializeOwned; use serde_json::Value; #[cfg(feature = "entrypoint")] #[doc(hidden)] pub use algorithmia_entrypoint::entrypoint; /// 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(&mut self, input: Self::Input) -> Result<AlgoIo, 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 type Input: DeserializeOwned; /// 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(&mut self, input: Self::Input) -> Result<AlgoIo, Box<StdError>>; } impl<T> EntryPoint for T where T: DecodedEntryPoint, { fn apply(&mut self, input: AlgoIo) -> Result<AlgoIo, Box<StdError>> { match input.as_json() { Some(obj) => { let decoded = serde_json::from_value(obj.into_owned()) .chain_err(|| "failed to parse input as JSON into the expected type")?; self.apply_decoded(decoded) } None => Err(ApiError::new(INPUT_ERROR, "Failed to parse input as JSON").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(&mut self, text: &str) -> Result<AlgoIo, Box<StdError>> { Err(ApiError::new(UNSUPPORTED_ERROR, "String input is not supported").into()) } #[allow(unused_variables)] /// Override to handle JSON input (see also [`DecodedEntryPoint`](trait.DecodedEntryPoint.html)) fn apply_json(&mut self, json: &Value) -> Result<AlgoIo, Box<StdError>> { Err(ApiError::new(UNSUPPORTED_ERROR, "JSON input is not supported").into()) } #[allow(unused_variables)] /// Override to handle binary input fn apply_bytes(&mut self, bytes: &[u8]) -> Result<AlgoIo, Box<StdError>> { Err(ApiError::new(UNSUPPORTED_ERROR, "Binary input is not supported").into()) } /// The default implementation of this method calls /// `apply_str`, `apply_json`, or `apply_bytes` based on the input type. /// /// - `AlgoIo::Text` results in call to `apply_str` /// - `AlgoIo::Json` results in call to `apply_json` /// - `AlgoIo::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: /// /// - `AlgoIo::Text` input will be JSON-encoded to call `apply_json` /// - `AlgoIo::Json` input will be parse to see it can call `apply_str` fn apply(&mut self, input: AlgoIo) -> Result<AlgoIo, Box<StdError>> { match input { AlgoIo::Text(ref text) => match self.apply_str(text) { Err(err) => match &err.downcast_ref::<ApiError>().map(|err| &*err.error_type) { Some(UNSUPPORTED_ERROR) => match input.as_json() { Some(json) => self.apply_json(&json), None => Err(err.into()), }, _ => Err(err.into()), }, ret => ret, }, AlgoIo::Json(ref json) => match self.apply_json(json) { Err(err) => match &err.downcast_ref::<ApiError>().map(|err| &*err.error_type) { Some(UNSUPPORTED_ERROR) => match input.as_string() { Some(text) => self.apply_str(text), None => Err(err.into()), }, _ => Err(err.into()), }, ret => ret, }, AlgoIo::Binary(ref bytes) => self.apply_bytes(bytes), } } }