Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions codegen/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use comline_codegen::{GenRequest, GeneratedFile, Mode, PackageMeta};
/// `comline-runtime` — the crate the generated RPC code links against. Pinned
/// by git rev (no crates.io yet); see design/runtime-repo-structure.md.
const RUNTIME_GIT: &str = "https://github.com/ComlineProject/runtime";
const RUNTIME_REV: &str = "435b7e833f970fadf4edbedcade20037143713d5";
const RUNTIME_REV: &str = "0501812f57817d7b11165bc6a24f54afa45acb16";

pub fn generate_rust(req: &GenRequest) -> Result<Vec<GeneratedFile>> {
match req.mode {
Expand Down Expand Up @@ -240,6 +240,9 @@ struct FnInfo {
/// explicit `-> ()` (`KindValue::Unit`) is *not* this — it's a normal
/// request/response with an empty ack (§4.4).
one_way: bool,
/// `@timeout_ms = N` on the function — the client method calls
/// `Client::call_with_timeout` with `Duration::from_millis(N)`.
timeout_ms: Option<u64>,
}

fn protocol(proto: &str, functions: &[FrozenUnit], errors: &HashMap<u16, String>) -> String {
Expand All @@ -248,11 +251,14 @@ fn protocol(proto: &str, functions: &[FrozenUnit], errors: &HashMap<u16, String>
.filter_map(|f| match f {
FrozenUnit::Function {
name,
parameters,
arguments,
_return,
throws,
..
} => Some(fn_info(proto, name, arguments, _return, throws, errors)),
} => Some(fn_info(
proto, name, parameters, arguments, _return, throws, errors,
)),
_ => None,
})
.collect();
Expand Down Expand Up @@ -455,9 +461,13 @@ fn protocol(proto: &str, functions: &[FrozenUnit], errors: &HashMap<u16, String>
" pub fn {}(&mut self{args}) -> Result<{}, CallError<{}>> {{",
f.name, f.ret, f.err_ty
));
l.push(format!(
" let (reply, fmt) = self.0.call({i}u16, {param_expr})?;"
));
let call = match f.timeout_ms {
Some(ms) => format!(
"self.0.call_with_timeout({i}u16, {param_expr}, core::time::Duration::from_millis({ms}))"
),
None => format!("self.0.call({i}u16, {param_expr})"),
};
l.push(format!(" let (reply, fmt) = {call}?;"));
l.push(" match reply {".into());
l.push(
" Envelope::Ok(payload) => fmt.decode(payload).map_err(CallError::Runtime),"
Expand Down Expand Up @@ -490,12 +500,14 @@ fn protocol(proto: &str, functions: &[FrozenUnit], errors: &HashMap<u16, String>
fn fn_info(
proto: &str,
name: &str,
parameters: &[FrozenUnit],
arguments: &[FrozenArgument],
ret: &Option<KindValue>,
throws: &[u16],
errors: &HashMap<u16, String>,
) -> FnInfo {
let pascal_fn = pascal(name);
let timeout_ms = annotation(parameters, "timeout_ms").and_then(|v| v.parse::<u64>().ok());
let args: Vec<Arg> = arguments
.iter()
.map(|a| {
Expand Down Expand Up @@ -541,9 +553,19 @@ fn fn_info(
err_ty: format!("{proto}{pascal_fn}Error"),
throws,
one_way,
timeout_ms,
}
}

/// The value of a scalar `@key = value` annotation (frozen as
/// `FrozenUnit::Property`), by key.
fn annotation<'a>(parameters: &'a [FrozenUnit], key: &str) -> Option<&'a str> {
parameters.iter().find_map(|p| match p {
FrozenUnit::Property { name, expression } if name == key => expression.as_deref(),
_ => None,
})
}

/// `(signature type, params-struct field type)` for a function argument.
/// A `str` / `string` arg is passed by reference and decoded borrowed from
/// the receive buffer; everything else is owned (borrowed arrays / nested
Expand Down
13 changes: 13 additions & 0 deletions codegen/tests/compiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ fn chat_schema() -> Vec<FrozenUnit> {
),
function("wipe", vec![], Some(KindValue::Unit), vec![]),
function("poke", vec![], None, vec![]),
// @timeout_ms = 3000 → the client emits `call_with_timeout`
FrozenUnit::Function {
docstring: String::new(),
parameters: vec![FrozenUnit::Property {
name: "timeout_ms".into(),
expression: Some("3000".into()),
}],
name: "await_ack".into(),
arguments: vec![arg("token", KindValue::Namespaced("string".into(), None))],
_return: Some(KindValue::Unit),
throws: vec![],
span: (0, 0),
},
],
span: (0, 0),
},
Expand Down
42 changes: 42 additions & 0 deletions codegen/tests/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,48 @@ fn protocol_errors_map_to_ordinals_and_a_union() {
assert!(src.contains("self.0.notify(2u16, &ChatPokeParams { note })"));
}

#[test]
fn timeout_ms_annotation_emits_call_with_timeout() {
let proto = FrozenUnit::Protocol {
docstring: "Api".to_string(),
parameters: vec![],
name: "Api".to_string(),
functions: vec![
// @timeout_ms = 2500
FrozenUnit::Function {
docstring: String::new(),
name: "slow".to_string(),
parameters: vec![FrozenUnit::Property {
name: "timeout_ms".to_string(),
expression: Some("2500".to_string()),
}],
arguments: vec![],
_return: Some(KindValue::Primitive(Primitive::U32(None))),
throws: vec![],
span: (0, 0),
},
// no annotation
FrozenUnit::Function {
docstring: String::new(),
name: "fast".to_string(),
parameters: vec![],
arguments: vec![],
_return: Some(KindValue::Primitive(Primitive::U32(None))),
throws: vec![],
span: (0, 0),
},
],
span: (0, 0),
};
let schemas = vec![("api".to_string(), vec![proto])];
let src = generate_rust(&code_req(&schemas)).unwrap().remove(0).contents;

assert!(src.contains(
"self.0.call_with_timeout(0u16, &(), core::time::Duration::from_millis(2500))?;"
));
assert!(src.contains("self.0.call(1u16, &())?;"));
}

#[test]
fn an_all_one_way_protocol_leaves_the_dispatch_out_param_unbound() {
let proto = FrozenUnit::Protocol {
Expand Down
Loading