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:
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:
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 additionalRequestContext
argument providing lower level access to request and response information.
Modules§
- Examples of generated Conjure code.
Structs§
- Codegen configuration.