Crate conjure_codegen

source ·
Expand description

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:

ConjureRust
stringString
datetimechrono::DateTime<Utc>
integeri32
doublef64
safelongconjure_object::SafeLong
binaryserde_bytes::ByteBuf
anyconjure_object::Any
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.

§double

Rust’s f64 type does not implement Ord, Eq, or Hash, which requires some special casing. Sets and maps keyed directly by double are represented as BTreeSet<DoubleKey> and BTreeMap<DoubleKey, T>, where the DoubleKey type wraps f64 and implements those traits by ordering NaN greater than all other values and equal to itself.

Conjure aliases, objects, and unions wrapping double types have trait implementations which use the same logic.

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

By default, the builder types generated for objects are by-ref and fallible - the build method will panic if any required fields are not set, and will clone fields into the built object. If the staged builders feature is enabled, the builder types are instead by-value and infallible - the compiler will prevent code from compiling if all required fields are not set. The API requires that all required fields be set first strictly in declaration order, after which optional fields can be set in any order.

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, Eq, PartialOrd, Ord, Hash, Serialize, and Deserialize. They 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, Eq, PartialOrd, Ord, Hash, Serialize, and Deserialize. 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, Eq, PartialOrd, Ord, Hash, Serialize, and Deserialize. They also implement 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:

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

Asynchronous:

use conjure_http::client::AsyncService;
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:

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:

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

§Endpoint Tags

  • server-request-context - The generated server trait method will have an additional RequestContext argument providing lower level access to request and response information.

Modules§

Structs§