[][src]Crate conjure_codegen

Code generation for Conjure definitions.

Examples

Code generation via a build script, assuming we have a service-api.conjure.json file in the crate root:

build.rs:

use std::env;
use std::path::Path;

fn main() {
    let input = "service-api.conjure.json";
    let output = Path::new(&env::var_os("OUT_DIR").unwrap()).join("service_api");

    println!("cargo:rerun-if-changed={}", input);
    conjure_codegen::Config::new()
        .run_rustfmt(false)
        .strip_prefix("com.foobar.service".to_string())
        .generate_files(input, output)
        .unwrap();
}

src/lib.rs:

This example is not tested
mod service_api {
    include!(concat!(env!("OUT_DIR"), "/service_api/mod.rs"));
}

Types

Builtin

Builtin types map directly to existing Rust types:

ConjureRust
stringString
datetimechrono::DateTime<Utc>
integeri32
doublef64
safelongconjure_object::SafeLong
binaryserde_bytes::ByteBuf
anyserde_value::Value
booleanbool
uuiduuid::Uuid
ridconjure_object::ResourceIdentifier
bearertokenconjure_object::BearerToken
optional<T>Option<T>
list<T>Vec<T>
set<T>BTreeSet<T>
map<K, V>BTreeMap<K, V>

Many of these are exposed by the conjure-object crate, which is a required dependency of crates containing the generated code.

Objects

Conjure objects turn into Rust structs along with builders used to construct them:

let object = ManyFieldExample::builder()
    .string("foo")
    .integer(123)
    .double_value(3.14)
    .optional_item("bar".to_string())
    .items(vec!["hello".to_string(), "world".to_string()])
    .alias(StringAliasExample("foobar".to_string()))
    .build();

assert_eq!(object.string(), "foo");
assert_eq!(object.optional_item(), Some("bar"));

Objects with 3 or fewer fields also have an explicit constructor:

let object = BooleanExample::new(true);

assert_eq!(object.coin(), true);

The generated structs implement Debug, Clone, PartialEq, PartialOrd, Serialize, and Deserialize. They also implement Eq, Ord, and Hash if they do not contain a double value, and Copy if they consist entirely of copyable primitive types.

Unions

Conjure unions turn into Rust enums. By default, unions are extensible through an additional Unknown variant. This allows unions to be forward-compatible by allowing clients to deserialize variants they don't yet know about and reserialize them properly:

match union_value {
    UnionTypeExample::StringExample(string) => {
        // ...
    }
    UnionTypeExample::Set(set) => {
        // ...
    }
    // ...
    UnionTypeExample::Unknown(unknown) => {
        println!("got unknown variant: {}", unknown.type_());
    }
}

The generated enums implement Debug, Clone, PartialEq, PartialOrd, Serialize, and Deserialize. They also implement Eq, Ord, and Hash if they do not contain a double value. Union variants which are themselves unions are boxed in the generated enum to avoid self-referential type definitions.

Enums

Conjure enums turn into Rust enums. By default, enums are extensible. This allows enums to be forward-compatible by allowing clients to deserialize variants they don't yet know about and reserialize them properly:

match enum_value {
    EnumExample::One => println!("found one"),
    EnumExample::Two => println!("found two"),
    EnumExample::Unknown(unknown) => println!("got unknown variant: {}", unknown),
}

The generated enums implement Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Display, Serialize, and Deserialize.

Aliases

Conjure aliases turn into Rust newtype structs that act like their inner value:

let alias_value = StringAliasExample("hello world".to_string());
assert!(alias_value.starts_with("hello"));

The generated structs implement Deref, DerefMut, Debug, Clone, PartialEq, PartialOrd, Serialize, and Deserialize. They also implement Eq, Ord, and Hash if they do not contain a double value, Copy if they wrap a copyable primitive type, Default if they wrap a type implementing Default, and Display if they wrap a type implementing Display.

Errors

Conjure errors turn into Rust structs storing the error's parameters as if it were a Conjure object. The struct additionally implements the conjure_error::ErrorType trait which encodes the extra error metadata:

use conjure_error::{ErrorType, ErrorCode};

let error = InvalidServiceDefinition::new(name, definition);

assert_eq!(error.code(), ErrorCode::InvalidArgument);
assert_eq!(error.name(), "Conjure:InvalidServiceDefinition");

Services

Conjure services turn into client- and server-side interfaces:

Clients

The client object wraps a raw HTTP client and provides methods to interact with the service's endpoints. Both synchronous and asynchronous clients are provided:

Synchronous:

let client = TestServiceClient::new(http_client);
let file_systems = client.get_file_systems(&auth_token)?;

Asynchronous:

let client = TestServiceAsyncClient::new(http_client);
let file_systems = client.get_file_systems(&auth_token).await?;

Servers

Conjure generates a trait and accompanying wrapper resource which are used to implement the service's endpoints. Both synchronous and asynchronous servers are supported:

Synchronous:

This example is not tested
struct TestServiceHandler;

impl<T> TestService<T> for TestServiceHandler
where
    T: Read
{
    fn get_file_systems(
        &self,
        auth: AuthToken,
    ) -> Result<BTreeMap<String, BackingFileSystem>, Error> {
        // ...
    }

    // ...
}

let resource = TestServiceResource::new(TestServiceHandler);
http_server.register(resource);

Asynchronous:

This example is not tested
struct TestServiceHandler;

#[async_trait]
impl<T> AsyncTestService<T> for TestServiceHandler
where
    T: AsyncRead + 'static + Send
{
    async fn get_file_systems(
        &self,
        auth: AuthToken,
    ) -> Result<BTreeMap<String, BackingFileSystem>, Error> {
        // ...
    }

    // ...
}

let resource = TestServiceResource::new(TestServiceHandler);
http_server.register(resource);

Modules

example_types

Examples of generated Conjure code.

Structs

Config

Codegen configuration.