conjure-codegen 0.4.1

Rust code generation for Conjure definitions
Documentation

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:

mod service_api {
include!(concat!(env!("OUT_DIR"), "/service_api/mod.rs"));
}

Types

Builtin

Builtin types map directly to existing Rust types:

Conjure Rust
string String
datetime chrono::DateTime<Utc>
integer i32
double f64
safelong conjure_object::SafeLong
binary serde_bytes::ByteBuf
any serde_value::Value
boolean bool
uuid uuid::Uuid
rid conjure_object::ResourceIdentifier
bearertoken conjure_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:

# use conjure_codegen::example_types::product::{ManyFieldExample, StringAliasExample};
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:

# use conjure_codegen::example_types::product::BooleanExample;
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:

# use conjure_codegen::example_types::product::UnionTypeExample;
# let union_value = UnionTypeExample::If(0);
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:

# use conjure_codegen::example_types::product::EnumExample;
# let enum_value = EnumExample::One;
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:

# use conjure_codegen::example_types::product::StringAliasExample;
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_codegen::example_types::product::InvalidServiceDefinition;
# let (name, definition) = ("", "");
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:

# use conjure_codegen::example_types::another::TestServiceClient;
# fn foo<T: conjure_http::client::Client>(http_client: T) -> Result<(), conjure_error::Error> {
# let auth_token = "foobar".parse().unwrap();
let client = TestServiceClient::new(http_client);
let file_systems = client.get_file_systems(&auth_token)?;
# Ok(())
# }

Servers

Conjure generates a trait and accompanying wrapper resource which are used to implement the service's endpoints:

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