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 |
conjure_object::Any |
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.
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:
# use ;
let object = builder
.string
.integer
.double_value
.optional_item
.items
.alias
.build;
assert_eq!;
assert_eq!;
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:
# use BooleanExample;
let object = new;
assert_eq!;
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:
# use UnionTypeExample;
# let union_value = If;
match union_value
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:
# use EnumExample;
# let enum_value = One;
match enum_value
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 StringAliasExample;
let alias_value = StringAliasExample;
assert!;
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 InvalidServiceDefinition;
# let = ;
use ;
let error = new;
assert_eq!;
assert_eq!;
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 Service;
# use TestServiceClient;
#
Asynchronous:
use AsyncService;
# use TestServiceAsyncClient;
# async
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);