Macro algorithmia::algo_entrypoint [] [src]

macro_rules! algo_entrypoint {
    ($t:ty, $apply:ident, Algo::$method:ident) => { ... };
    ($t:ty, $apply:ident, $p:path) => { ... };
    (&str) => { ... };
    (&[u8]) => { ... };
    (&JsonValue) => { ... };
    (AlgoInput) => { ... };
    ($t:ty) => { ... };
    (&str => Algo::$i:ident) => { ... };
    (&[u8] => Algo::$i:ident) => { ... };
    (&JsonValue => Algo::$i:ident) => { ... };
    (AlgoInput => Algo::$i:ident) => { ... };
    ($t:ty => Algo::$i:ident) => { ... };
    (&str => $p:path) => { ... };
    (&[u8] => $p:path) => { ... };
    (&JsonValue => $p:path) => { ... };
    (AlgoInput => $p:path) => { ... };
    ($t:ty => $p:path) => { ... };
}

Macro for implementing Entrypoint or DecodedEntryPoint boilerplate in an algorithm

Algorithmia support for Rust algorithms is hard-coded to call Algo::apply(AlgoInput) but for the sake of convenience the EntryPoint trait provides a default implementation of apply that delegates to apply_str, apply_bytes, or apply_json depending on the variant of AlgoInput. The DecodedEntryPoint trait overrides the default apply to provide a method that uses a deserialized associated type when calling apply_decoded. This pattern is incredibly flexible, but adds boilerplate and still requires converting output and error types back.

The algo_entrypoint! macro hides all that boilerplate code behind a single macro invocation. algo_entrypoint(type => your_fn) wires up the boilerplate for calling your_fn(type). Additionally, you can use the macro as algo_entrypoint(type) which is equivalent to algo_entrypoint!(type => apply).

Use the following types:

  • &str if your algorithm accepts text input
  • &[u8] if your algorithm accepts binary input
  • Any deserializeable type if your algorithm accepts JSON input
  • &JsonValue is you want your algorithm to work directly with the typed JSON input
  • AlgoInput if you want to work with the full enum of possible input types

In all cases, the return value of your_fn should be Result<T, E> where:

  • T implements Into<AlgoOutput> which includes String, Vec<u8>, JsonValue, and boxed* serializeable types
  • E implements Into<Box<::std::error::Error>> which includes String and all Error types

* support for unboxed serializeable depends on specialization implemented behind the 'nightly' feature.

Examples

Text Input/Output

To set the entrypoint to a function that receives and returns text:

algo_entrypoint!(&str);

fn apply(input: &str) -> Result<String, String> {
    unimplemented!()
}

which generates:

#[derive(Default)]
pub struct Algo;

impl EntryPoint for Algo {
    fn apply_str(&self, input: &str) -> Result<AlgoOutput, Box<::std::error::Error>> {
        apply(input).map(AlgoOutput::from).map_err(|err| err.into())
    }
}

fn apply(input: &str) -> Result<String, String> {
    unimplemented!()
}

Binary Input/Output

To set the entrypoint to a function that receives and returns binary data:

algo_entrypoint!(&[u8]);

fn apply(input: &[u8]) -> Result<Vec<u8>, String> {
    unimplemented!()
}

JSON Input/Output

To set the entrypoint to a function that receives and returns JSON data, you can work directly with the JsonValue:

algo_entrypoint!(&JsonValue);

fn apply(input: &JsonValue) -> Result<JsonValue, String> {
    unimplemented!()
}

Although, often the preferred way to work with JSON is to automatically deserialize into and serialize from a custom type.

#[derive(Deserialize)]
pub struct MyInput { corpus: String, msg: String }

#[derive(Serialize)]
pub struct MyOutput { probabilities: Vec<(String, f32)> }

algo_entrypoint!(MyInput);

fn apply(input: MyInput) -> Result<Box<MyOutput>, String> {
    unimplemented!()
}

Note: Box for serializeable output is currently required for lack of specialization. The specialization implementation already exists behind the nightly feature. Alternatively, you can return AlgoOutput via AlgoOutput::from(&myOutput)

The previous example expands into:

#[derive(Deserialize)]
pub struct MyInput { corpus: String, msg: String };

#[derive(Serialize)]
pub struct MyOutput { probabilities: Vec<(String, f32)> };

#[derive(Default)]
pub struct Algo;

impl DecodedEntryPoint for Algo {
    type Input (String, String);
    fn apply_decoded(&self, input: MyInput) -> Result<AlgoOutput, Box<::std::error::Error>> {
        apply(input).map(AlgoOutput::from).map_err(|err| err.into())
    }
}

fn apply(input: MyInput) -> Result<Box<MyOutput>, String> {
    unimplemented!()
}

Customizing Default

And finally, it is possible to provide a custom Default implementation and pass that state into your method by prefixing your_fn with the Algo:: namespace.

pub struct Algo{ init_id: String }

algo_entrypoint!(&str => Algo::hello_text);

impl Algo {
    fn hello_text(&self, input: &str) -> Result<String, String> {
        unimplemented!()
    }
}

impl Default for Algo {
    fn default() -> Algo {
        Algo { init_id: "foo".into() }
    }
}