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 216
//! # FDK: Fn Function Development Kit //! //! This crate implements an experimental Function Development Kit for the //! [Fn Project](http://www.fnproject.io) serverless platform. //! //! The API provided hides the implementation details of the Fn platform //! contract and allows a user to focus on the code and easily implement //! function-as-a-service programs. //! //! # Usage //! //! The Fn platform offers a //! [command line tool](https://github.com/fnproject/fn/blob/master/README.md#quickstart) //! to initialize, build and deploy function projects. Follow the `fn` tool //! quickstart to learn the basics of the Fn platform. Then start a Rust //! function project with: //! //! ```text //! fn init --runtime=rust <other options to fn command> //! ``` //! //! The initializer will actually use cargo and generate a cargo binary project //! for the function. It is then possible to specify a dependency as usual. //! //! ```toml //! [dependencies] //! fdk = "0.1" //! ``` //! //! # Examples //! //! ## Stateless function with input //! //! This is a simple function which greets the name provided as input. //! //! ```no_run //! extern crate fdk; //! use std::process; //! //! fn main() { //! let exit_code = fdk::Function::new(fdk::STATELESS) //! .run(|_, i: String| { //! Ok(format!("Hello, {}!\n", //! if i.is_empty() { "world".to_string() } else { i })) //! }); //! process::exit(exit_code); //! } //! ``` //! //! ## Function with a full testable implementation //! //! This function takes advantage of features of the FDK such as configuration //! and state management, error handling, and the testbench which provides a //! wrapper to test the function code as if it was running on the Fn platform. //! //! ```no_run //! extern crate fdk; //! use std::process; //! //! struct MyState { //! greeting: String //! } //! impl MyState { //! pub fn greeting(&self) -> &str { &self.greeting } //! } //! //! fn init(context: &fdk::RuntimeContext) -> Result<MyState, fdk::FunctionError> { //! match context.config().get("GREETING") { //! Some(s) => Ok(MyState { //! greeting: s.clone() //! }), //! None => Err(fdk::FunctionError::initialization( //! "Missing greeting in configuration\n", //! )), //! } //! } //! //! fn code(state: &mut MyState, i: String) -> Result<String, fdk::FunctionError> { //! if !i.is_empty() { //! Ok(format!("{}, {}!\n", state.greeting(), i).into()) //! } else { //! Err(fdk::FunctionError::invalid_input( //! "Requires a non-empty UTF-8 string\n", //! )) //! } //! } //! //! fn main() { //! let exit_code = fdk::Function::new(init).run(code); //! process::exit(exit_code); //! } //! //! #[cfg(test)] //! mod tests { //! use fdk; //! //! use init; //! use code; //! //! #[test] //! fn test_normal_functionality() { //! let mut testbench = //! fdk::FunctionTestbench::new(init).with_config("GREETING", "Salutations"); //! let exit_code = testbench.enqueue_simple("Blah").run(code); //! assert_eq!(exit_code, 0); //! let mut responses = testbench.drain_responses(); //! assert_eq!(responses.len(), 1); //! let rb = fdk::body_as_bytes(responses.pop().unwrap().body()).unwrap(); //! assert_eq!(String::from_utf8_lossy(&rb), "Salutations, Blah!\n"); //! } //! } //! ``` //! //! ## Function handling http requests and responses directly //! //! While input and output coercions can be performed so that your function can //! just handle your own types, it is sometimes useful to manipulate requests //! and responses directly. //! //! The FDK uses the `hyper::Request` and `hyper::Response` types from the //! well-known `hyper` crate to this effect, and your function can therefore //! receive a `hyper::Request` as input and produce a `hyper::Response` as //! output. //! //! Note that this allows you to set custom headers and status code on the //! response directly rather than relying on the helper implementations in the //! FDK which associate errors with certain http statuses. //! //! ```no_run //! extern crate hyper; //! extern crate fdk; //! use std::process; //! //! struct MyState { //! greeting: String //! } //! impl MyState { //! pub fn greeting(&self) -> &str { &self.greeting } //! } //! //! fn init(context: &fdk::RuntimeContext) -> Result<MyState, fdk::FunctionError> { //! match context.config().get("GREETING") { //! Some(s) => Ok(MyState { //! greeting: s.clone() //! }), //! None => Err(fdk::FunctionError::initialization( //! "Missing greeting in configuration\n", //! )), //! } //! } //! //! fn main() { //! let exit_code = fdk::Function::new(init) //! .run(|state, req: hyper::Request| { //! // Since we have raw access to the request we can inspect the //! // headers and extract some data. //! let host = match req.headers().get::<hyper::header::Host>() { //! Some(h) => h.hostname().to_string(), //! None => "NO HOST".to_string(), //! }; //! let i = match fdk::body_as_bytes(req.body()) { //! Ok(b) => { //! match String::from_utf8(b) { //! Ok(s) => s, //! Err(_) => String::new() //! } //! } //! Err(e) => { //! return Err(e); //! } //! }; //! if i.is_empty() { //! // We can produce an "error response" of our own instead of an //! // Err value which would be converted to a 400 response with no //! // headers. //! let message = "Requires a non-empty UTF-8 string!"; //! let message_length = message.as_bytes().len() as u64; //! Ok( //! hyper::Response::new() //! .with_status(hyper::StatusCode::BadRequest) //! .with_header(hyper::header::Host::new(host.clone(), None)) //! .with_header(hyper::header::ContentLength(message_length)) //! .with_body(message) //! ) //! } else { //! let message = format!("{}, {}!\n", state.greeting(), i); //! let message_length = message.as_bytes().len() as u64; //! Ok( //! hyper::Response::new() //! .with_status(hyper::StatusCode::Ok) //! .with_header(hyper::header::Host::new(host.clone(), None)) //! .with_header(hyper::header::ContentLength(message_length)) //! .with_body(message) //! ) //! } //! }); //! process::exit(exit_code); //! } //! ``` extern crate futures; extern crate hyper; extern crate serde_json; extern crate uuid; mod codecs; mod coercions; mod context; mod errors; mod function; mod hyper_utils; pub use coercions::{InputCoercible, OutputCoercible}; pub use context::RuntimeContext; pub use errors::FunctionError; pub use function::{Function, FunctionTestbench, STATELESS}; pub use hyper_utils::body_as_bytes;