Crate fdk [] [src]

FDK: Fn Function Development Kit

This crate implements an experimental Function Development Kit for the Fn Project 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 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:

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.

[dependencies]
fdk = "0.1"

Examples

Stateless function with input

This is a simple function which greets the name provided as input.

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.

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.

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);
}

Structs

Function

A function runtime which wraps a simpler function written by the user and deals with all the complexity of the Fn platform contract while providing an easy to use API.

FunctionError

An Error which can occur during the execution of a function. Depending on which kind of error it is, it could signify that the function runtime is compromised or it could just represent an issue with the way the caller provided input data.

FunctionTestbench

A testing wrapper that behaves like a Function but additionally provides methods to create testing conditions (including setting configuration and enqueuing requests) and to read results back in order for tests to check the behaviour of a function.

RuntimeContext

A RuntimeContext contains configuration that is set up once per function runtime and persists across invocations (in the case of a hot function). This configuration can therefore be used to initialize the state of the user's function.

Constants

STATELESS

This constant can be used as the initializer of a Function to indicate that the function does not need or handle state.

Traits

InputCoercible

An InputCoercible type can be generated from a hyper::Request.

OutputCoercible

An OutputCoercible type can be converted to a hyper::Response.

Functions

body_as_bytes

A utility function that consumes the body of a request or response and returns it as a vector of bytes. Note: this consumes and buffers the stream.