test_r::enable!();
#[cfg(test)]
#[cfg(feature = "export_golem_agentic")]
#[test_r::sequential]
#[allow(
clippy::disallowed_names,
dead_code,
private_interfaces,
unused_imports
)]
mod tests {
use golem_rust::agentic::{
CanonicalInputModel, ExtendedOptionShape, ExtendedToolType, ToolBuildCtx, ToolBuildError,
ToolErrorSchema, get_tool_invoker_by_name,
};
use golem_rust::{
FromSchema, FromWire, IntoSchema, IntoWire, Quantity, QuantityUnit, WireSchema,
tool_definition, tool_implementation,
};
use golem_rust_macro::ToolError;
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
use std::process::Command;
use test_r::test;
fn encoded_input(
tool: &ExtendedToolType,
command_path: &[&str],
values: Vec<golem_rust::SchemaValue>,
) -> golem_rust::golem_agentic::exports::golem::tool::guest::TypedSchemaValue {
let path = command_path
.iter()
.map(|segment| segment.to_string())
.collect::<Vec<_>>();
let command_index = tool
.command_index_by_path(&path)
.expect("command path resolves");
let model = CanonicalInputModel::from_fields(tool.canonical_input_fields(command_index))
.expect("canonical input model builds");
let params = model
.fields
.iter()
.zip(values)
.map(|(field, value)| (field.name.as_str(), value))
.collect();
let input = golem_rust::agentic::build_canonical_input(&model, params)
.expect("canonical input builds");
golem_rust::encode_typed_schema_value(&input).expect("typed schema value encodes")
}
fn anonymous_principal() -> golem_rust::agentic::Principal {
golem_rust::agentic::Principal::Anonymous
}
#[test]
fn ordinary_tool_error_wire_preflight_preserves_resources() {
use golem_rust::schema::wit::{GuestSecretHandle, direct, wire};
#[derive(ToolError)]
enum Failure {
#[tool_error(kind = "runtime-error", exit_code = 1)]
Resources(Vec<GuestSecretHandle>),
}
let secret = GuestSecretHandle::new(unsafe { wire::Secret::from_handle(71) });
let failure = Failure::Resources(vec![secret.clone(), secret.clone()]);
assert!(matches!(
direct::encode(&failure),
Err(direct::WireError::AliasedResource("secret"))
));
assert!(secret.is_present());
assert_eq!(secret.take().unwrap().take_handle(), 71);
assert!(matches!(
direct::encode(&failure),
Err(direct::WireError::ConsumedResource("secret"))
));
}
#[test]
fn ordinary_tool_error_wire_matches_schema() {
use golem_rust::schema::wit::{decode_graph, decode_value, direct};
use std::io::Result;
enum Local {
Some,
None,
}
use Local::{None, Some};
let _: Result<()> = std::result::Result::Ok(());
let _ = (Some, None);
#[derive(Debug, PartialEq, ToolError)]
enum EmptyFailure {
#[tool_error(kind = "usage-error", exit_code = 2)]
BadInput,
#[tool_error(kind = "runtime-error", exit_code = 1)]
Tuple(),
#[tool_error(kind = "runtime-error", exit_code = 1)]
Named {},
}
#[derive(Debug, PartialEq, ToolError)]
enum Failure {
#[tool_error(kind = "usage-error", exit_code = 2)]
BadInput(String),
#[tool_error(kind = "runtime-error", exit_code = 1)]
Named { detail: Option<Vec<u32>> },
#[tool_error(kind = "runtime-error", exit_code = 1)]
Empty,
}
fn check<T: IntoSchema + FromWire + IntoWire + WireSchema + PartialEq + std::fmt::Debug>(
values: Vec<T>,
) {
let graph = decode_graph(&direct::schema::<T>()).unwrap();
assert_eq!(
graph,
golem_rust::schema::conversion::try_into_schema_graph::<T>().unwrap()
);
for value in values {
let encoded = direct::encode(&value).unwrap();
assert_eq!(
decode_value(direct::encode(&value).unwrap()).unwrap(),
value.to_value()
);
assert_eq!(direct::decode::<T>(encoded).unwrap(), value);
}
}
check(vec![
EmptyFailure::BadInput,
EmptyFailure::Tuple(),
EmptyFailure::Named {},
]);
check(vec![
Failure::BadInput("bad".into()),
Failure::Named {
detail: std::option::Option::Some(vec![1, 2]),
},
Failure::Empty,
]);
assert!(direct::decode::<EmptyFailure>(direct::encode(&0u32).unwrap()).is_err());
}
#[test]
async fn direct_error_metadata_and_decoding_do_not_call_model_codecs() {
use golem_rust::agentic::DirectToolError;
use golem_rust::schema::wit::{direct, wire};
#[derive(Debug, PartialEq, FromWire, IntoWire, WireSchema)]
struct Payload {
code: u16,
notes: Vec<Option<Result<String, bool>>>,
}
impl IntoSchema for Payload {
fn type_id() -> golem_rust::schema::TypeId {
panic!("direct metadata must not request a model type id")
}
fn register_in(_: &mut golem_rust::schema::SchemaBuilder) -> golem_rust::SchemaType {
panic!("direct metadata must not construct a model")
}
fn to_value(&self) -> golem_rust::SchemaValue {
panic!("direct encoding must not construct a model")
}
}
impl FromSchema for Payload {
fn from_value(
_: &golem_rust::SchemaValue,
) -> Result<Self, golem_rust::schema::FromSchemaError> {
panic!("direct decoding must not construct a model")
}
}
#[derive(Debug, PartialEq, ToolError)]
enum Failure {
#[tool_error(kind = "usage-error", exit_code = 17)]
Rejected(Payload),
#[tool_error(kind = "runtime-error", exit_code = 29)]
Delayed { details: Payload },
#[tool_error(kind = "runtime-error", exit_code = 31)]
Empty,
}
let mut builder = direct::WireSchemaBuilder::default();
let cases = Failure::wire_error_cases(&mut builder);
assert_eq!(
cases
.iter()
.map(|case| case.name.as_str())
.collect::<Vec<_>>(),
["rejected", "delayed", "empty"]
);
assert_eq!(cases[0].exit_code, 17);
assert!(matches!(
cases[0].kind,
golem_rust::schema::tool::wit::wire::ErrorKind::UsageError
));
assert_eq!(cases[0].doc.summary, "Retry with a different input.");
assert!(cases[2].payload.is_none());
let graph = builder.finish(cases[0].payload.unwrap());
assert_eq!(graph.defs.len(), 1);
for case in &cases[..2] {
assert!(matches!(
graph.type_nodes[case.payload.unwrap() as usize].body,
wire::SchemaTypeBody::RefType(0)
));
}
let payload = Payload {
code: 513,
notes: vec![Some(Err(true)), None, Some(Ok("retry".into()))],
};
let failure = Failure::Delayed { details: payload };
let ordinary = direct::encode(&failure).unwrap();
assert_eq!(direct::decode::<Failure>(ordinary).unwrap(), failure);
assert_eq!(direct::schema::<Failure>().defs.len(), 2);
let (name, encoded) = failure.direct_error_payload().await.unwrap();
assert_eq!(name, "delayed");
assert_eq!(encoded.graph.defs.len(), 1);
assert_eq!(
Failure::from_direct_error_payload(&name, encoded.value).unwrap(),
Some(failure)
);
assert_eq!(
Failure::from_direct_error_payload("empty", direct::encode(&()).unwrap()).unwrap(),
Some(Failure::Empty)
);
assert!(
Failure::from_direct_error_payload("empty", direct::encode(&false).unwrap()).is_err()
);
assert!(
Failure::from_direct_error_payload("rejected", direct::encode(&()).unwrap()).is_err()
);
assert_eq!(
Failure::from_direct_error_payload("unknown", direct::encode(&()).unwrap()).unwrap(),
None
);
}
#[test]
fn direct_input_consumes_named_record_and_unbound_globals_without_model_traits() {
use golem_rust::agentic::DirectToolInput;
use golem_rust::schema::wit::{direct, wire};
#[derive(IntoWire, WireSchema)]
struct Input {
target: String,
inherited: Vec<Option<String>>,
}
let make_input = || wire::TypedSchemaValue {
graph: direct::schema::<Input>(),
value: direct::encode(&Input {
target: "leaf-value".to_string(),
inherited: vec![None, Some("ancestor-value".to_string())],
})
.unwrap(),
};
let mut input = DirectToolInput::new(make_input()).unwrap();
assert_eq!(input.take::<String>("target").unwrap(), "leaf-value");
input.finish().unwrap();
let mut invalid = make_input();
let definition = invalid.graph.defs[0].body as usize;
let wire::SchemaTypeBody::RecordType(fields) =
&mut invalid.graph.type_nodes[definition].body
else {
panic!("expected named record");
};
fields[1].metadata.aliases.push("target".to_string());
assert!(
DirectToolInput::new(invalid)
.err()
.unwrap()
.contains("ambiguous")
);
let mut invalid = make_input();
let inherited = invalid
.value
.value_nodes
.iter_mut()
.find(|node| matches!(node, wire::SchemaValueNode::ListValue(_)))
.unwrap();
*inherited = wire::SchemaValueNode::ListValue(vec![999]);
let mut input = DirectToolInput::new(invalid).unwrap();
assert_eq!(input.take::<String>("target").unwrap(), "leaf-value");
assert!(input.finish().unwrap_err().contains("out of bounds"));
}
#[test]
fn direct_input_structurally_adapts_option_carriers_through_references() {
use golem_rust::agentic::DirectToolInput;
use golem_rust::schema::wit::{direct, wire};
#[derive(IntoWire, WireSchema)]
struct OptionalInput {
value: Option<String>,
}
#[derive(IntoWire, WireSchema)]
struct BareInput {
value: String,
}
#[derive(Debug, PartialEq)]
struct CustomMaybe(Option<String>);
impl FromWire for CustomMaybe {
fn read_wire(
reader: &mut direct::WireReader,
index: wire::ValueNodeIndex,
) -> Result<Self, direct::WireError> {
Option::<String>::read_wire(reader, index).map(Self)
}
}
impl WireSchema for CustomMaybe {
fn append_schema(builder: &mut direct::WireSchemaBuilder) -> wire::TypeNodeIndex {
let (definition, fresh) =
builder.reserve("test.CustomMaybe".to_string(), Some("custom-maybe".into()));
if fresh {
let inner = Option::<String>::append_schema(builder);
builder.commit(definition, inner);
}
builder.reference(definition)
}
}
fn reference_field(graph: &mut wire::SchemaGraph) {
let record = graph.defs[0].body as usize;
let wire::SchemaTypeBody::RecordType(fields) = &graph.type_nodes[record].body else {
panic!("expected named record")
};
let body = fields[0].body;
let definition = graph.defs.len() as wire::DefIndex;
graph.defs.push(wire::SchemaTypeDef {
id: "test.FieldCarrier".to_string(),
name: Some("field-carrier".to_string()),
body,
});
let reference = graph.type_nodes.len() as wire::TypeNodeIndex;
graph.type_nodes.push(wire::SchemaTypeNode {
body: wire::SchemaTypeBody::RefType(definition),
metadata: direct::empty_metadata(),
});
let wire::SchemaTypeBody::RecordType(fields) = &mut graph.type_nodes[record].body
else {
unreachable!()
};
fields[0].body = reference;
}
let optional = |value| {
let mut graph = direct::schema::<OptionalInput>();
reference_field(&mut graph);
wire::TypedSchemaValue {
graph,
value: direct::encode(&OptionalInput { value }).unwrap(),
}
};
let mut input = DirectToolInput::new(optional(Some("present".to_string()))).unwrap();
assert_eq!(
input.take_any_adapted::<String>(&["value"]).unwrap(),
"present"
);
input.finish().unwrap();
let error = DirectToolInput::new(optional(None))
.unwrap()
.take_any_adapted::<String>(&["value"])
.unwrap_err();
assert!(error.contains("absent"));
let mut graph = direct::schema::<BareInput>();
reference_field(&mut graph);
let mut input = DirectToolInput::new(wire::TypedSchemaValue {
graph,
value: direct::encode(&BareInput {
value: "wrapped".to_string(),
})
.unwrap(),
})
.unwrap();
assert_eq!(
input.take_any_adapted::<CustomMaybe>(&["value"]).unwrap(),
CustomMaybe(Some("wrapped".to_string()))
);
input.finish().unwrap();
let mut cyclic = direct::schema::<BareInput>();
let record = cyclic.defs[0].body as usize;
let definition = cyclic.defs.len() as wire::DefIndex;
let reference = cyclic.type_nodes.len() as wire::TypeNodeIndex;
cyclic.defs.push(wire::SchemaTypeDef {
id: "test.Cycle".to_string(),
name: None,
body: reference,
});
cyclic.type_nodes.push(wire::SchemaTypeNode {
body: wire::SchemaTypeBody::RefType(definition),
metadata: direct::empty_metadata(),
});
let wire::SchemaTypeBody::RecordType(fields) = &mut cyclic.type_nodes[record].body else {
unreachable!()
};
fields[0].body = reference;
let error = DirectToolInput::new(wire::TypedSchemaValue {
graph: cyclic,
value: direct::encode(&BareInput {
value: "cycle".to_string(),
})
.unwrap(),
})
.err()
.unwrap();
assert!(error.contains("reference cycle"));
}
#[tool_definition]
trait RefinedTextDirectRoundTrip {
#[arg(value = "positional", regex = "[a-z]+")]
#[arg(items = "option", regex = "[a-z]+")]
#[arg(maybe = "option", regex = "[a-z]+")]
fn echo(&self, value: String, items: Vec<String>, maybe: Option<String>) -> String;
}
struct RefinedTextDirectRoundTripImpl;
#[tool_implementation]
impl RefinedTextDirectRoundTrip for RefinedTextDirectRoundTripImpl {
fn echo(&self, value: String, items: Vec<String>, maybe: Option<String>) -> String {
format!("{value}:{}:{}", items.join("/"), maybe.unwrap_or_default())
}
}
#[test]
async fn guest_invoke_decodes_refined_text_in_scalar_list_and_option() {
use golem_rust::schema::{SchemaValue, TextValuePayload};
let text = |value: &str| {
SchemaValue::Text(TextValuePayload {
text: value.to_string(),
language: None,
})
};
let tool =
<RefinedTextDirectRoundTripImpl as RefinedTextDirectRoundTrip>::__tool_descriptor();
let input = encoded_input(
&tool,
&["echo"],
vec![
text("scalar"),
SchemaValue::List {
elements: vec![text("first"), text("second")],
},
SchemaValue::Option {
inner: Some(Box::new(text("optional"))),
},
],
);
let result = RefinedTextDirectRoundTripImpl::__tool_invoke(
vec!["echo".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.unwrap()
.result
.unwrap();
assert_eq!(
golem_rust::schema::wit::direct::decode::<String>(result.value).unwrap(),
"scalar:first/second:optional",
);
}
type RequiredMaybeString = Option<String>;
#[tool_definition]
trait AliasedOptionDirectRoundTrip {
#[arg(value = "option", required = true)]
fn echo(&self, value: RequiredMaybeString) -> String;
}
struct AliasedOptionDirectRoundTripImpl;
#[tool_implementation]
impl AliasedOptionDirectRoundTrip for AliasedOptionDirectRoundTripImpl {
fn echo(&self, value: RequiredMaybeString) -> String {
value.unwrap_or_default()
}
}
#[test]
async fn guest_invoke_preserves_option_carrier_hidden_by_type_alias() {
let tool =
<AliasedOptionDirectRoundTripImpl as AliasedOptionDirectRoundTrip>::__tool_descriptor();
let input = encoded_input(
&tool,
&["echo"],
vec![golem_rust::SchemaValue::Option {
inner: Some(Box::new(golem_rust::SchemaValue::String(
"aliased".to_string(),
))),
}],
);
let result = AliasedOptionDirectRoundTripImpl::__tool_invoke(
vec!["echo".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.unwrap()
.result
.unwrap();
assert_eq!(
golem_rust::schema::wit::direct::decode::<String>(result.value).unwrap(),
"aliased",
);
}
#[tool_definition]
trait OptionalStreamRoundTrip {
async fn optional_streams(
&self,
stdin: Option<golem_rust::agentic::InputStream>,
stdout: Option<golem_rust::agentic::OutputStream>,
) -> u32;
}
struct OptionalStreamRoundTripImpl;
#[tool_implementation]
impl OptionalStreamRoundTrip for OptionalStreamRoundTripImpl {
async fn optional_streams(
&self,
stdin: Option<golem_rust::agentic::InputStream>,
stdout: Option<golem_rust::agentic::OutputStream>,
) -> u32 {
u32::from(stdin.is_some()) | (u32::from(stdout.is_some()) << 1)
}
}
#[test]
async fn optional_streams_are_described_and_invoked_as_optional() {
let tool = <OptionalStreamRoundTripImpl as OptionalStreamRoundTrip>::__tool_descriptor();
let command_index = tool
.command_index_by_path(&["optional-streams".to_string()])
.expect("optional-streams command exists");
let body = tool.commands[command_index]
.body
.as_ref()
.expect("optional-streams has a body");
assert_eq!(
body.stdin.as_ref().map(|stream| stream.required),
Some(false)
);
assert_eq!(
body.stdout.as_ref().map(|stream| stream.required),
Some(false)
);
let input = encoded_input(&tool, &["optional-streams"], Vec::new());
let invoker = get_tool_invoker_by_name("optional-stream-round-trip")
.expect("optional stream tool implementation registers an invoker");
let result = invoker(
vec!["optional-streams".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("optional streams may both be absent");
let result = golem_rust::decode_typed_schema_value(
&result.result.expect("stream presence mask is returned"),
)
.expect("stream presence mask decodes");
assert_eq!(u32::from_value(result.value()).unwrap(), 0);
}
#[tool_definition]
trait DualOutputRoundTrip {
#[arg(diagnostics, channel = "stderr")]
async fn outputs(
&self,
stdout: Option<golem_rust::agentic::OutputStream>,
diagnostics: Option<golem_rust::agentic::OutputStream>,
) -> u32;
}
struct DualOutputRoundTripImpl;
#[tool_implementation]
impl DualOutputRoundTrip for DualOutputRoundTripImpl {
async fn outputs(
&self,
stdout: Option<golem_rust::agentic::OutputStream>,
diagnostics: Option<golem_rust::agentic::OutputStream>,
) -> u32 {
(u32::from(stdout.is_some()) << 1) | (u32::from(diagnostics.is_some()) << 2)
}
}
#[test]
async fn output_channel_selector_projects_and_dispatches_independently() {
let tool = <DualOutputRoundTripImpl as DualOutputRoundTrip>::__tool_descriptor();
let command_index = tool
.command_index_by_path(&["outputs".to_string()])
.expect("outputs command exists");
let body = tool.commands[command_index]
.body
.as_ref()
.expect("outputs has a body");
assert_eq!(
body.stdout.as_ref().map(|stream| stream.required),
Some(false)
);
assert_eq!(
body.stderr.as_ref().map(|stream| stream.required),
Some(false)
);
let input = encoded_input(&tool, &["outputs"], Vec::new());
let invoker = get_tool_invoker_by_name("dual-output-round-trip")
.expect("dual output implementation registers an invoker");
let result = invoker(
vec!["outputs".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("optional outputs may both be absent");
let result = golem_rust::decode_typed_schema_value(
&result.result.expect("output presence mask is returned"),
)
.expect("output presence mask decodes");
assert_eq!(u32::from_value(result.value()).unwrap(), 0);
}
#[tool_definition]
trait PrincipalAutoInjectedRoundTrip {
fn whoami(&self, principal: golem_rust::tool::Principal, name: String) -> String;
}
#[tool_definition]
trait GeneratedNameCollisionTool {
fn echo(
&self,
underlying: String,
__param_values: String,
__value: String,
__command_path: String,
__descriptor: String,
__command_index: String,
__model: String,
__input: String,
__result: String,
) -> String;
}
#[tool_definition]
trait GeneratedRawNameCollisionTool {
fn echo(
&self,
r#underlying: String,
r#__param_values: String,
r#__value: String,
r#__command_path: String,
r#__descriptor: String,
r#__command_index: String,
r#__model: String,
r#__input: String,
r#__result: String,
) -> String;
}
#[test]
fn tool_descriptor_accepts_auto_injected_principal_parameters() {
let tool = __golem_tool_descriptor_for_PrincipalAutoInjectedRoundTrip(
&mut ToolBuildCtx::new(),
)
.expect("Principal is supplied by the guest invocation principal, not by the input record");
let command_index = tool
.command_index_by_path(&["whoami".to_string()])
.expect("whoami command exists");
let fields = tool.canonical_input_fields(command_index);
assert_eq!(
fields
.iter()
.map(|field| field.name.as_str())
.collect::<Vec<_>>(),
vec!["name"],
"auto-injected Principal parameters must not appear in the tool input schema",
);
}
#[test]
fn imported_user_principal_parameter_is_schema_input() {
mod user_principal_schema {
use golem_rust::{FromSchema, FromWire, IntoSchema, IntoWire, WireSchema};
#[derive(IntoSchema, FromSchema, WireSchema, IntoWire, FromWire)]
pub struct Principal {
pub id: String,
}
}
use user_principal_schema::Principal;
#[tool_definition]
trait LocalImportedPrincipalTool {
fn whoami(&self, principal: Principal, name: String) -> String;
}
let tool = __golem_tool_descriptor_for_LocalImportedPrincipalTool(&mut ToolBuildCtx::new())
.expect("an imported user Principal type is a normal schema input");
let command_index = tool
.command_index_by_path(&["whoami".to_string()])
.expect("whoami command exists");
let fields = tool.canonical_input_fields(command_index);
assert_eq!(
fields
.iter()
.map(|field| field.name.as_str())
.collect::<Vec<_>>(),
vec!["principal", "name"],
"bare/imported Principal is not enough to mark a tool parameter as auto-injected",
);
}
#[derive(ToolError)]
enum GrepError {
#[tool_error(kind = "usage-error", exit_code = 2)]
BadPattern(String),
#[tool_error(kind = "runtime-error", exit_code = 1)]
Io(String),
}
#[tool_definition(version = "1.2.3")]
trait Grep {
#[arg(case_sensitive = "global", short = 'i', kind = "flag")]
#[arg(pattern = "positional", regex = r"^.+$")]
#[arg(files = "tail", accepts_stdio = true)]
fn grep(
&self,
case_sensitive: bool,
pattern: String,
files: Vec<String>,
) -> Result<Vec<String>, GrepError>;
}
struct GrepImpl;
#[tool_implementation]
impl Grep for GrepImpl {
fn grep(
&self,
_case_sensitive: bool,
_pattern: String,
_files: Vec<String>,
) -> Result<Vec<String>, GrepError> {
Ok(vec![])
}
}
struct GrepMiddlewareShape;
impl GrepMiddleware for GrepMiddlewareShape {
async fn grep(
&self,
underlying: &GrepUnderlying,
case_sensitive: bool,
pattern: String,
files: Vec<String>,
) -> Result<Vec<String>, golem_rust::tool::ToolInvokeError<GrepError>> {
underlying.grep(case_sensitive, pattern, files).await
}
fn __golem_tool_middleware_annotation() {}
}
struct GrepAdapterShape;
impl GrepMiddleware<RemoteUnderlying> for GrepAdapterShape {
async fn grep(
&self,
_underlying: &RemoteUnderlying,
_case_sensitive: bool,
_pattern: String,
_files: Vec<String>,
) -> Result<Vec<String>, golem_rust::tool::ToolInvokeError<GrepError>> {
unimplemented!()
}
fn __golem_tool_middleware_annotation() {}
}
struct PrincipalMiddlewareShape;
impl PrincipalAutoInjectedRoundTripMiddleware for PrincipalMiddlewareShape {
async fn whoami(
&self,
underlying: &PrincipalAutoInjectedRoundTripUnderlying,
_principal: golem_rust::tool::Principal,
name: String,
) -> Result<String, golem_rust::tool::ToolInvokeError<std::convert::Infallible>> {
underlying.whoami(name).await
}
fn __golem_tool_middleware_annotation() {}
}
struct GeneratedNameCollisionMiddlewareShape;
impl GeneratedNameCollisionToolMiddleware for GeneratedNameCollisionMiddlewareShape {
async fn echo(
&self,
proxied: &GeneratedNameCollisionToolUnderlying,
underlying: String,
__param_values: String,
__value: String,
__command_path: String,
__descriptor: String,
__command_index: String,
__model: String,
__input: String,
__result: String,
) -> Result<String, golem_rust::tool::ToolInvokeError<std::convert::Infallible>> {
proxied
.echo(
underlying,
__param_values,
__value,
__command_path,
__descriptor,
__command_index,
__model,
__input,
__result,
)
.await
}
fn __golem_tool_middleware_annotation() {}
}
struct GeneratedRawNameCollisionMiddlewareShape;
impl GeneratedRawNameCollisionToolMiddleware for GeneratedRawNameCollisionMiddlewareShape {
async fn echo(
&self,
proxied: &GeneratedRawNameCollisionToolUnderlying,
r#underlying: String,
r#__param_values: String,
r#__value: String,
r#__command_path: String,
r#__descriptor: String,
r#__command_index: String,
r#__model: String,
r#__input: String,
r#__result: String,
) -> Result<String, golem_rust::tool::ToolInvokeError<std::convert::Infallible>> {
proxied
.echo(
r#underlying,
r#__param_values,
r#__value,
r#__command_path,
r#__descriptor,
r#__command_index,
r#__model,
r#__input,
r#__result,
)
.await
}
fn __golem_tool_middleware_annotation() {}
}
fn descriptor<T: Grep>() -> ExtendedToolType {
<T as Grep>::__tool_descriptor()
}
#[test]
fn grep_descriptor_builds() {
let tool = descriptor::<GrepImpl>();
assert_eq!(tool.version, "1.2.3");
assert_eq!(tool.tool_name(), "grep");
let root = &tool.commands[0];
assert_eq!(root.name, "grep");
let body = root.body.as_ref().expect("root has a body");
assert_eq!(root.globals.flags.len(), 1);
assert_eq!(root.globals.flags[0].long, "case-sensitive");
assert_eq!(root.globals.flags[0].short, Some('i'));
assert_eq!(body.positionals.fixed.len(), 1);
assert_eq!(body.positionals.fixed[0].name, "pattern");
let tail = body.positionals.tail.as_ref().expect("tail positional");
assert_eq!(tail.name, "files");
assert!(tail.accepts_stdio);
assert_eq!(body.errors.len(), 2);
assert_eq!(body.errors[0].name, "bad-pattern");
assert_eq!(body.errors[0].exit_code, 2);
tool.try_to_tool().expect("grep tool is valid");
}
#[test]
fn grep_error_cases() {
let cases = GrepError::error_cases().expect("error cases build");
assert_eq!(cases.len(), 2);
assert_eq!(cases[0].name, "bad-pattern");
assert_eq!(cases[1].name, "io");
}
#[test]
fn grep_canonical_input_record_order_matches_packed_values() {
let tool = descriptor::<GrepImpl>();
let command_index = tool
.command_index_by_path(&[])
.expect("implicit grep body is addressed by the empty path");
let fields = tool.canonical_input_fields(command_index);
assert_eq!(
fields
.iter()
.map(|field| field.name.as_str())
.collect::<Vec<_>>(),
vec!["case-sensitive", "pattern", "files"],
"canonical order is inherited/global flags before fixed positionals and tail values",
);
let input = encoded_input(
&tool,
&[],
vec![
golem_rust::SchemaValue::Bool(true),
golem_rust::SchemaValue::String("needle".to_string()),
golem_rust::SchemaValue::List {
elements: vec![
golem_rust::SchemaValue::String("src/lib.rs".to_string()),
golem_rust::SchemaValue::String("tests/tool.rs".to_string()),
],
},
],
);
let decoded = golem_rust::decode_typed_schema_value(&input).expect("input decodes");
let golem_rust::SchemaValue::Record { fields } = decoded.value() else {
panic!("typed tool input must be encoded as a positional record");
};
assert_eq!(fields[0], golem_rust::SchemaValue::Bool(true));
assert_eq!(
fields[1],
golem_rust::SchemaValue::String("needle".to_string())
);
assert_eq!(
fields[2],
golem_rust::SchemaValue::List {
elements: vec![
golem_rust::SchemaValue::String("src/lib.rs".to_string()),
golem_rust::SchemaValue::String("tests/tool.rs".to_string()),
],
}
);
}
#[tool_definition]
trait Remote {
fn add(&self, verbose: bool, name: String, url: String) -> Result<(), RemoteError>;
#[command(name = "rm")]
fn remove(&self, name: String) -> Result<(), RemoteError>;
}
#[derive(ToolError)]
enum RemoteError {
#[tool_error(kind = "runtime-error", exit_code = 1)]
Failed(String),
}
struct RemoteImpl;
#[tool_implementation]
impl Remote for RemoteImpl {
fn add(&self, _verbose: bool, _name: String, _url: String) -> Result<(), RemoteError> {
Ok(())
}
fn remove(&self, _name: String) -> Result<(), RemoteError> {
Ok(())
}
}
#[tool_definition]
trait Git {
#[arg(config = "option", repeatable = "repeated")]
#[command(aliases = ["ci"])]
fn commit(
&self,
message: String,
config: BTreeMap<String, String>,
) -> Result<(), CommitError>;
#[command(subtree = Remote)]
fn remote(&self, verbose: bool) -> RemoteSubtree;
}
struct RemoteSubtree;
#[derive(ToolError)]
enum CommitError {
#[tool_error(kind = "runtime-error", exit_code = 1)]
Failed(String),
}
struct GitImpl;
#[tool_implementation]
impl Git for GitImpl {
fn commit(
&self,
_message: String,
_config: BTreeMap<String, String>,
) -> Result<(), CommitError> {
Ok(())
}
fn remote(&self, _verbose: bool) -> RemoteSubtree {
RemoteSubtree
}
}
#[test]
fn git_subtree_grafts_remote() {
let tool = __golem_tool_descriptor_for_Git(&mut ToolBuildCtx::new())
.expect("git descriptor builds");
assert_eq!(tool.tool_name(), "git");
let root = &tool.commands[0];
assert_eq!(root.subcommands.len(), 2);
let commit = root
.subcommands
.iter()
.map(|&i| &tool.commands[i as usize])
.find(|c| c.name == "commit")
.expect("commit command");
assert_eq!(commit.aliases, vec!["ci".to_string()]);
let commit_body = commit.body.as_ref().expect("commit body");
let config_opt = commit_body
.options
.iter()
.find(|o| o.long == "config")
.expect("config option");
assert!(matches!(
config_opt.shape,
ExtendedOptionShape::RepeatableMap(_)
));
let remote = root
.subcommands
.iter()
.map(|&i| &tool.commands[i as usize])
.find(|c| c.name == "remote")
.expect("remote command");
assert!(remote.body.is_none());
assert!(
remote.globals.flags.iter().any(|f| f.long == "verbose"),
"remote dispatcher carries the inherited `verbose` global flag"
);
let sub_names: Vec<&str> = remote
.subcommands
.iter()
.map(|&i| tool.commands[i as usize].name.as_str())
.collect();
assert!(sub_names.contains(&"add"));
assert!(sub_names.contains(&"rm"));
let add = remote
.subcommands
.iter()
.map(|&i| &tool.commands[i as usize])
.find(|c| c.name == "add")
.expect("add command");
let add_body = add.body.as_ref().expect("add body");
assert!(
!add_body.flags.iter().any(|f| f.long == "verbose"),
"inherited `verbose` global must be suppressed from the `add` body"
);
tool.try_to_tool().expect("git tool is valid");
}
#[test]
fn git_remote_add_canonical_input_record_starts_with_inherited_global() {
let tool = __golem_tool_descriptor_for_Git(&mut ToolBuildCtx::new())
.expect("git descriptor builds");
let command_index = tool
.command_index_by_path(&["remote".to_string(), "add".to_string()])
.expect("remote add command exists");
let fields = tool.canonical_input_fields(command_index);
assert_eq!(
fields
.iter()
.map(|field| field.name.as_str())
.collect::<Vec<_>>(),
vec!["verbose", "name", "url"],
"grafted subtree leaf inputs must be encoded as inherited globals followed by child body fields",
);
let input = encoded_input(
&tool,
&["remote", "add"],
vec![
golem_rust::SchemaValue::Bool(true),
golem_rust::SchemaValue::String("origin".to_string()),
golem_rust::SchemaValue::String("https://example.invalid/repo.git".to_string()),
],
);
let decoded = golem_rust::decode_typed_schema_value(&input).expect("input decodes");
let golem_rust::SchemaValue::Record { fields } = decoded.value() else {
panic!("typed tool input must be encoded as a positional record");
};
assert_eq!(fields[0], golem_rust::SchemaValue::Bool(true));
assert_eq!(
fields[1],
golem_rust::SchemaValue::String("origin".to_string())
);
assert_eq!(
fields[2],
golem_rust::SchemaValue::String("https://example.invalid/repo.git".to_string())
);
}
#[tool_definition]
trait Inner {
fn leaf(&self, verbose: bool, name: String) -> Result<(), RemoteError>;
}
struct InnerSubtree;
struct MidSubtree;
#[tool_definition]
trait Mid {
#[command(subtree = Inner)]
fn inner(&self, verbose: bool) -> InnerSubtree;
}
#[tool_definition]
trait Outer {
#[command(subtree = Mid)]
fn mid(&self, verbose: bool) -> MidSubtree;
}
struct GitMiddlewareShape;
impl GitMiddleware for GitMiddlewareShape {
async fn commit(
&self,
underlying: &GitUnderlying,
message: String,
config: BTreeMap<String, String>,
) -> Result<(), golem_rust::tool::ToolInvokeError<CommitError>> {
underlying.commit(message, config).await
}
async fn remote__add(
&self,
underlying: &GitUnderlying,
verbose: bool,
name: String,
url: String,
) -> Result<(), golem_rust::tool::ToolInvokeError<RemoteError>> {
underlying.remote__add(verbose, name, url).await
}
async fn remote__remove(
&self,
underlying: &GitUnderlying,
verbose: bool,
name: String,
) -> Result<(), golem_rust::tool::ToolInvokeError<RemoteError>> {
underlying.remote__remove(verbose, name).await
}
fn __golem_tool_middleware_annotation() {}
}
struct OuterMiddlewareShape;
impl OuterMiddleware for OuterMiddlewareShape {
async fn mid__inner__leaf(
&self,
underlying: &OuterUnderlying,
verbose: bool,
name: String,
) -> Result<(), golem_rust::tool::ToolInvokeError<RemoteError>> {
underlying.mid__inner__leaf(verbose, name).await
}
fn __golem_tool_middleware_annotation() {}
}
#[tool_definition]
trait MiddlewareStreamSurface {
fn copy(
&self,
input: golem_rust::agentic::InputStream,
output: golem_rust::agentic::OutputStream,
) -> Result<String, RemoteError>;
}
struct MiddlewareStreamShape;
impl MiddlewareStreamSurfaceMiddleware for MiddlewareStreamShape {
async fn copy(
&self,
underlying: &MiddlewareStreamSurfaceUnderlying,
input: golem_rust::tool::InputStream,
_output: golem_rust::tool::OutputStream,
) -> Result<String, golem_rust::tool::ToolInvokeError<RemoteError>> {
let invocation = underlying.start_copy(input).await?;
invocation.get().await
}
fn __golem_tool_middleware_annotation() {}
}
#[test]
fn generated_middleware_surfaces_expose_native_descriptors() {
let underlying =
<GrepUnderlying as golem_rust::tool::ToolUnderlying>::__golem_tool_descriptor();
let presented =
<GrepMiddlewareShape as GrepMiddleware>::__golem_presented_tool_descriptor();
assert_eq!(underlying, presented);
assert_eq!(underlying.name(), Some("grep"));
assert_eq!(underlying.version, "1.2.3");
}
#[test]
fn bug_finder_monomorphic_middleware_authoring_macro_compiles() {
let output = cargo_check_tool_crate(
"monomorphic-middleware-authoring-compiles",
r#"
use golem_rust::{tool_definition, tool_middleware};
#[tool_definition]
trait Echo {
fn echo(&self, value: String) -> String;
}
struct Policy;
impl Policy {
fn new() -> Self {
Self
}
}
#[tool_middleware(name = "policy", constructor = Policy::new)]
impl EchoMiddleware for Policy {
async fn echo(
&self,
underlying: &EchoUnderlying,
value: String,
) -> Result<String, golem_rust::tool::ToolInvokeError<std::convert::Infallible>> {
underlying.echo(value).await
}
}
"#,
);
assert!(
output.status.success(),
"a valid monomorphic middleware must compile:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn bug_finder_universal_middleware_authoring_macro_compiles() {
let output = cargo_check_tool_crate(
"universal-middleware-authoring-compiles",
r#"
use golem_rust::{universal_tool_middleware, TypedSchemaValue};
use golem_rust::tool::{
InputStream, InvocationResult, OutputStream, Principal, RawCustomToolError, Tool,
ToolInvokeError, UnderlyingTool,
};
#[universal_tool_middleware(name = "audit")]
async fn audit(
_tool_name: String,
_tool_metadata: Tool,
command_path: Vec<String>,
input: TypedSchemaValue,
stdin: Option<InputStream>,
stdout: Option<OutputStream>,
stderr: Option<OutputStream>,
_principal: Principal,
underlying: UnderlyingTool,
) -> Result<InvocationResult, ToolInvokeError<RawCustomToolError>> {
underlying
.invoke_forwarding_outputs(command_path, input, stdin, stdout, stderr)
.await
}
"#,
);
assert!(
output.status.success(),
"a valid universal middleware must compile:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn multilevel_subtree_suppresses_inherited_globals_at_depth() {
let tool = __golem_tool_descriptor_for_Outer(&mut ToolBuildCtx::new())
.expect("outer descriptor builds");
assert_eq!(tool.tool_name(), "outer");
let find = |name: &str| {
tool.commands
.iter()
.find(|c| c.name == name)
.unwrap_or_else(|| panic!("command `{name}` present"))
};
let mid = find("mid");
assert!(
mid.globals.flags.iter().any(|f| f.long == "verbose"),
"the `mid` dispatcher carries the `verbose` global"
);
let inner = find("inner");
assert!(
!inner.globals.flags.iter().any(|f| f.long == "verbose"),
"nested dispatcher must not redeclare the inherited `verbose` global"
);
let leaf = find("leaf");
let leaf_body = leaf.body.as_ref().expect("leaf body");
assert!(
!leaf_body.flags.iter().any(|f| f.long == "verbose"),
"inherited `verbose` global must be suppressed from the `leaf` body"
);
tool.try_to_tool()
.expect("multi-level subtree tool is valid");
}
#[tool_definition]
trait Cli {
#[arg(verbose = "global", kind = "flag")]
fn cli(&self, verbose: bool, target: String) -> Result<(), RemoteError>;
#[command(subtree = Mid)]
fn mid(&self) -> MidSubtree;
}
#[test]
fn subtree_inherits_global_from_parent_root_command() {
let tool = __golem_tool_descriptor_for_Cli(&mut ToolBuildCtx::new())
.expect("cli descriptor builds");
assert_eq!(tool.tool_name(), "cli");
let find = |name: &str| {
tool.commands
.iter()
.find(|c| c.name == name)
.unwrap_or_else(|| panic!("command `{name}` present"))
};
let root = find("cli");
assert!(
root.globals.flags.iter().any(|f| f.long == "verbose"),
"the `cli` root command carries the `verbose` global"
);
let inner = find("inner");
assert!(
!inner.globals.flags.iter().any(|f| f.long == "verbose"),
"nested dispatcher must not redeclare the root-inherited `verbose` global"
);
tool.try_to_tool()
.expect("subtree inheriting a root global is valid");
}
#[tool_definition]
trait TypeMismatchChild {
fn leaf(&self, verbose: String, name: String) -> Result<(), RemoteError>;
}
struct TypeMismatchChildSubtree;
#[tool_definition]
trait TypeMismatchParent {
#[command(subtree = TypeMismatchChild)]
fn type_mismatch_child(&self, verbose: bool) -> TypeMismatchChildSubtree;
}
#[test]
fn inherited_global_conflict_with_different_typed_child_parameter_is_rejected() {
let err = __golem_tool_descriptor_for_TypeMismatchParent(&mut ToolBuildCtx::new())
.expect_err("a child parameter colliding with an inherited global of a different shape must be rejected");
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "verbose" && inherited == "verbose" && command == "leaf"
),
"expected an InheritedGlobalConflict for `verbose` on `leaf`, got {err:?}",
);
}
#[tool_definition]
trait OptionalCaptureChild {
fn leaf(&self, count: u32, name: String) -> Result<(), RemoteError>;
}
struct OptionalCaptureChildSubtree;
#[tool_definition]
trait OptionalCaptureParent {
#[command(subtree = OptionalCaptureChild, name = "optional-capture-child")]
#[arg(count = "global", required = false)]
fn child(&self, count: u32) -> OptionalCaptureChildSubtree;
}
fn generated_subtree_optional_capture_typechecks() {
let client = OptionalCaptureParentClient::default().child(7);
let _: &str = &client.inherited_prefix[0].name;
}
#[test]
fn optional_parent_field_keeps_its_carrier_when_child_redeclares() {
let tool = __golem_tool_descriptor_for_OptionalCaptureParent(&mut ToolBuildCtx::new())
.expect("optional subtree descriptor builds");
let child = tool
.node_index_by_path(&["optional-capture-child".to_string()])
.expect("subtree command exists");
let field = tool
.canonical_input_fields(child)
.into_iter()
.find(|field| field.name == "count")
.expect("parent count field exists");
assert!(matches!(
field.schema.root,
golem_rust::SchemaType::Option { .. }
));
}
#[tool_definition]
trait SameTraitGlobalRoundTrip {
#[arg(verbose = "global", kind = "flag")]
fn same_trait_global_round_trip(
&self,
verbose: bool,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, verbose: bool, name: String) -> String;
fn fail(&self, reason: String) -> Result<String, RemoteError>;
}
struct SameTraitGlobalRoundTripImpl;
#[tool_implementation]
impl SameTraitGlobalRoundTrip for SameTraitGlobalRoundTripImpl {
fn same_trait_global_round_trip(
&self,
_verbose: bool,
_target: String,
) -> Result<(), RemoteError> {
Ok(())
}
fn leaf(&self, verbose: bool, name: String) -> String {
format!("{verbose}:{name}")
}
fn fail(&self, reason: String) -> Result<String, RemoteError> {
Err(RemoteError::Failed(reason))
}
}
#[test]
async fn guest_invoke_decodes_same_trait_root_globals_and_plain_return() {
let tool = <SameTraitGlobalRoundTripImpl as SameTraitGlobalRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("same-trait-global-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["leaf"],
vec![
golem_rust::SchemaValue::Bool(true),
golem_rust::SchemaValue::String("alice".to_string()),
],
);
let result = invoker(
vec!["leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("guest invocation succeeds");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "true:alice");
}
#[test]
async fn guest_invoke_encodes_custom_tool_errors() {
let tool = <SameTraitGlobalRoundTripImpl as SameTraitGlobalRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("same-trait-global-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["fail"],
vec![
golem_rust::SchemaValue::Bool(false),
golem_rust::SchemaValue::String("boom".to_string()),
],
);
let err = invoker(
vec!["fail".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect_err("tool error is surfaced as guest ToolError");
let golem_rust::golem_agentic::exports::golem::tool::guest::ToolError::CustomError(value) =
err
else {
panic!("expected custom tool error, got {err:?}");
};
let payload =
golem_rust::decode_typed_schema_value(&value.payload).expect("custom error decodes");
let decoded = RemoteError::from_error_payload_value(value.name, payload)
.expect("custom error payload decodes to the declared error enum");
match decoded {
Some(RemoteError::Failed(reason)) => assert_eq!(reason, "boom"),
None => panic!("declared custom error was not recognized"),
}
}
#[test]
async fn guest_custom_tool_error_payload_matches_declared_error_case_payload_schema() {
let tool = <SameTraitGlobalRoundTripImpl as SameTraitGlobalRoundTrip>::__tool_descriptor();
let fail_index = tool
.command_index_by_path(&["fail".to_string()])
.expect("fail command exists");
let payload_schema = tool.commands[fail_index]
.body
.as_ref()
.and_then(|body| body.errors.first())
.and_then(|error| error.payload.as_ref())
.expect("RemoteError::Failed declares a payload schema");
assert!(
matches!(payload_schema.root, golem_rust::SchemaType::String { .. }),
"the declared error-case payload schema is String, got {:#?}",
payload_schema.root
);
let invoker = get_tool_invoker_by_name("same-trait-global-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["fail"],
vec![
golem_rust::SchemaValue::Bool(false),
golem_rust::SchemaValue::String("boom".to_string()),
],
);
let err = invoker(
vec!["fail".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect_err("tool error is surfaced as guest ToolError");
let golem_rust::golem_agentic::exports::golem::tool::guest::ToolError::CustomError(value) =
err
else {
panic!("expected custom tool error, got {err:?}");
};
assert_eq!(value.name, "failed");
let value =
golem_rust::decode_typed_schema_value(&value.payload).expect("custom error decodes");
let payload = String::from_value(value.value())
.expect("custom-error payload must match the declared error-case payload type");
assert_eq!(payload, "boom");
}
#[derive(Debug, Eq, PartialEq, ToolError)]
enum AmbiguousRemoteError {
#[tool_error(kind = "usage-error", exit_code = 2)]
BadInput(String),
#[tool_error(kind = "runtime-error", exit_code = 1)]
Backend(String),
}
#[tool_definition]
trait AmbiguousErrorRoundTrip {
fn fail_backend(&self, reason: String) -> Result<String, AmbiguousRemoteError>;
}
struct AmbiguousErrorRoundTripImpl;
#[tool_implementation]
impl AmbiguousErrorRoundTrip for AmbiguousErrorRoundTripImpl {
fn fail_backend(&self, reason: String) -> Result<String, AmbiguousRemoteError> {
Err(AmbiguousRemoteError::Backend(reason))
}
}
#[test]
async fn custom_tool_error_duplicate_payload_schemas_decode_by_name() {
let tool = <AmbiguousErrorRoundTripImpl as AmbiguousErrorRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("ambiguous-error-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["fail-backend"],
vec![golem_rust::SchemaValue::String("boom".to_string())],
);
let err = invoker(
vec!["fail-backend".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect_err("tool error is surfaced as guest ToolError");
let golem_rust::golem_agentic::exports::golem::tool::guest::ToolError::CustomError(value) =
err
else {
panic!("expected custom tool error, got {err:?}");
};
let payload =
golem_rust::decode_typed_schema_value(&value.payload).expect("custom error decodes");
assert_eq!(
AmbiguousRemoteError::from_error_payload_value(value.name, payload)
.expect("custom error payload decodes"),
Some(AmbiguousRemoteError::Backend("boom".to_string())),
);
}
#[test]
async fn bug_finder_guest_custom_error_wire_value_matches_declared_case_payload() {
let tool = <SameTraitGlobalRoundTripImpl as SameTraitGlobalRoundTrip>::__tool_descriptor();
let fail_index = tool
.command_index_by_path(&["fail".to_string()])
.expect("fail command exists");
assert!(
matches!(
tool.commands[fail_index]
.body
.as_ref()
.and_then(|body| body.errors.first())
.and_then(|error| error.payload.as_ref())
.map(|payload| &payload.root),
Some(golem_rust::SchemaType::String { .. })
),
"the declared custom error case payload is String"
);
let invoker = get_tool_invoker_by_name("same-trait-global-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["fail"],
vec![
golem_rust::SchemaValue::Bool(false),
golem_rust::SchemaValue::String("declared-payload".to_string()),
],
);
let err = invoker(
vec!["fail".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect_err("tool error is surfaced as guest ToolError");
let golem_rust::golem_agentic::exports::golem::tool::guest::ToolError::CustomError(value) =
err
else {
panic!("expected custom tool error, got {err:?}");
};
assert_eq!(value.name, "failed");
let value =
golem_rust::decode_typed_schema_value(&value.payload).expect("custom error decodes");
let payload = String::from_value(value.value())
.expect("custom-error wire value must match the declared error-case payload schema");
assert_eq!(payload, "declared-payload");
}
#[derive(Debug, Eq, PartialEq, IntoSchema, FromSchema, WireSchema, IntoWire, FromWire)]
struct CustomPlainReturn {
name: String,
}
#[tool_definition]
trait CustomPlainReturnRoundTrip {
fn custom_plain(&self, name: String) -> CustomPlainReturn;
}
struct CustomPlainReturnRoundTripImpl;
#[tool_implementation]
impl CustomPlainReturnRoundTrip for CustomPlainReturnRoundTripImpl {
fn custom_plain(&self, name: String) -> CustomPlainReturn {
CustomPlainReturn { name }
}
}
#[test]
async fn guest_invoke_encodes_custom_plain_return_types() {
let tool =
<CustomPlainReturnRoundTripImpl as CustomPlainReturnRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("custom-plain-return-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["custom-plain"],
vec![golem_rust::SchemaValue::String("custom".to_string())],
);
let result = invoker(
vec!["custom-plain".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("guest invocation succeeds for a custom IntoSchema return type");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = CustomPlainReturn::from_value(result.value())
.expect("result schema matches return type");
assert_eq!(
value,
CustomPlainReturn {
name: "custom".to_string()
}
);
}
#[tool_definition]
trait DefaultBodyInvokeRoundTrip {
fn default_body_invoke_round_trip(&self, name: String) -> String {
format!("default:{name}")
}
}
struct DefaultBodyInvokeRoundTripImpl;
#[tool_implementation]
impl DefaultBodyInvokeRoundTrip for DefaultBodyInvokeRoundTripImpl {}
#[test]
async fn guest_invoke_dispatches_trait_default_method_bodies() {
let tool =
<DefaultBodyInvokeRoundTripImpl as DefaultBodyInvokeRoundTrip>::__tool_descriptor();
assert!(
tool.command_index_by_path(&[]).is_some(),
"the default method body is present in the generated descriptor"
);
let invoker = get_tool_invoker_by_name("default-body-invoke-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&[],
vec![golem_rust::SchemaValue::String("alice".to_string())],
);
let result = invoker(vec![], input, None, None, None, anonymous_principal())
.await
.expect("guest invocation dispatches the trait default method body");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "default:alice");
}
#[tool_definition]
trait StatefulInstanceRoundTrip {
fn stateful_instance_round_trip(&self, suffix: String) -> String;
}
struct StatefulInstanceRoundTripImpl {
prefix: String,
}
#[tool_implementation]
impl StatefulInstanceRoundTrip for StatefulInstanceRoundTripImpl {
fn stateful_instance_round_trip(&self, suffix: String) -> String {
format!("{}:{suffix}", self.prefix)
}
}
#[test]
async fn generated_instance_invoker_dispatches_on_borrowed_state() {
let tool =
<StatefulInstanceRoundTripImpl as StatefulInstanceRoundTrip>::__tool_descriptor();
let input = encoded_input(
&tool,
&[],
vec![golem_rust::SchemaValue::String("value".to_string())],
);
let instance = StatefulInstanceRoundTripImpl {
prefix: "state".to_string(),
};
let result = instance
.__tool_invoke_on(vec![], input, None, None, None, anonymous_principal())
.await
.expect("instance invocation succeeds");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "state:value");
}
#[test]
fn tool_output_result_shape_compiles() {
let output = cargo_check_tool_crate(
"tool-output-result-shape",
r#"
use golem_rust::agentic::{ToolInvocation, ToolInvocationOutput};
use std::convert::Infallible;
fn stdout(invocation: ToolInvocation<(), Infallible>) -> Option<ToolInvocationOutput> {
invocation.stdout
}
async fn consume(_output: ToolInvocationOutput) {}
fn consume_result_and_outputs_concurrently(invocation: ToolInvocation<(), Infallible>) {
let result = invocation.result();
let stdout = invocation.stdout.map(consume);
let stderr = invocation.stderr.map(consume);
let _ = (result, stdout, stderr);
}
"#,
);
assert!(
output.status.success(),
"tool invocation results should expose native stdout streams, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn stdout_tool_invocation_shape_compiles() {
let output = cargo_check_tool_crate(
"stdout-tool-invocation-shape",
r#"
use golem_rust::agentic::OutputStream;
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum RemoteError {
#[tool_error(kind = "runtime-error", exit_code = 1)]
Failed(String),
}
#[tool_definition]
trait StdoutTool {
fn run(&self, stdout: OutputStream) -> Result<(), RemoteError>;
}
struct StdoutToolImpl;
#[tool_implementation]
impl StdoutTool for StdoutToolImpl {
fn run(&self, mut stdout: OutputStream) -> Result<(), RemoteError> {
stdout.write(b"hello".to_vec());
Ok(())
}
}
"#,
);
assert!(
output.status.success(),
"stdout tool definitions should generate a P3 stream pair, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn stdout_tool_parameter_must_not_collide_with_generated_reader_local() {
let output = cargo_check_tool_crate(
"stdout-tool-generated-reader-name-collision",
r#"
use golem_rust::agentic::OutputStream;
use golem_rust::{tool_definition, tool_implementation};
#[tool_definition]
trait StdoutTool {
fn run(&self, __golem_stdout_reader: OutputStream);
}
struct StdoutToolImpl;
#[tool_implementation]
impl StdoutTool for StdoutToolImpl {
fn run(&self, mut __golem_stdout_reader: OutputStream) {
__golem_stdout_reader.write(b"hello".to_vec());
}
}
"#,
);
assert!(
output.status.success(),
"a legal tool parameter name must not collide with macro-generated stdout locals\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn stdout_tool_raw_parameter_must_not_collide_with_generated_reader_local() {
let output = cargo_check_tool_crate(
"stdout-tool-generated-reader-raw-name-collision",
r#"
use golem_rust::agentic::OutputStream;
use golem_rust::{tool_definition, tool_implementation};
#[tool_definition]
trait StdoutTool {
fn run(&self, r#__golem_stdout_reader: OutputStream);
}
struct StdoutToolImpl;
#[tool_implementation]
impl StdoutTool for StdoutToolImpl {
fn run(&self, mut r#__golem_stdout_reader: OutputStream) {
r#__golem_stdout_reader.write(b"hello".to_vec());
}
}
"#,
);
assert!(
output.status.success(),
"a legal raw tool parameter name must not collide with macro-generated stdout locals\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn async_tool_invocation_shape_compiles() {
let output = cargo_check_tool_crate(
"async-tool-invocation-shape",
r#"
use golem_rust::{tool_definition, tool_implementation};
#[tool_definition]
trait AsyncTool {
async fn run(&self, value: String) -> String;
}
struct AsyncToolImpl;
#[tool_implementation]
impl AsyncTool for AsyncToolImpl {
async fn run(&self, value: String) -> String {
value
}
}
"#,
);
assert!(
output.status.success(),
"async tool definitions should use the P3 guest executor, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn generated_tool_client_requires_direct_decodable_error_type() {
let output = cargo_check_tool_crate(
"tool-client-error-schema-bound",
r#"
use golem_rust::agentic::{ExtendedErrorCase, ToolBuildError, ToolErrorSchema};
use golem_rust::{tool_definition, TypedSchemaValue};
enum ManualError {
Failed,
}
impl ToolErrorSchema for ManualError {
fn error_cases() -> Result<Vec<ExtendedErrorCase>, ToolBuildError> {
Ok(Vec::new())
}
fn to_error_payload_value(&self) -> Result<(String, TypedSchemaValue), String> {
todo!()
}
fn from_error_payload_value(_name: String, _value: TypedSchemaValue) -> Result<Option<Self>, String> {
todo!()
}
}
#[tool_definition]
trait ManualErrorTool {
fn run(&self) -> Result<(), ManualError>;
}
"#,
);
assert!(
!output.status.success(),
"a tool client error type without DirectToolError must fail to compile\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("DirectToolError"),
"expected the compile error to mention the direct decodability bound, got:\n{stderr}",
);
}
#[test]
fn bug_finder_tool_error_zero_field_payloadless_variants_compile() {
let output = cargo_check_tool_crate(
"tool-error-zero-field-payloadless-variants",
r#"
use golem_rust::ToolError;
#[derive(ToolError)]
enum EmptyPayloadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
EmptyTuple(),
#[tool_error(kind = "runtime-error", exit_code = 1)]
EmptyStruct {},
}
"#,
);
assert!(
output.status.success(),
"zero-field tuple/struct ToolError variants are parsed as payload-less and should compile\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn generated_tool_client_surface_shapes_compile() {
let output = cargo_check_tool_crate(
"typed-tool-client-surface-shapes",
r#"
use std::convert::Infallible;
use std::future::Future;
use golem_rust::{tool_definition, ToolError};
#[derive(ToolError)]
enum GrepError {
#[tool_error(kind = "usage-error", exit_code = 2)]
BadPattern(String),
}
#[derive(ToolError)]
enum RemoteError {
#[tool_error(kind = "runtime-error", exit_code = 1)]
Failed(String),
}
#[tool_definition]
trait Grep {
#[arg(case_sensitive = "global", short = 'i', kind = "flag")]
#[arg(pattern = "positional")]
#[arg(files = "tail", accepts_stdio = true)]
fn grep(
&self,
case_sensitive: bool,
pattern: String,
files: Vec<String>,
) -> Result<Vec<String>, GrepError>;
}
#[tool_definition]
trait StreamTool {
fn copy(
&self,
input: golem_rust::agentic::InputStream,
output: golem_rust::agentic::OutputStream,
) -> Result<String, RemoteError>;
}
#[tool_definition]
trait PlainReturnTool {
fn version(&self) -> String;
}
#[tool_definition]
trait Remote {
fn add(&self, verbose: bool, name: String, url: String) -> Result<(), RemoteError>;
}
struct RemoteSubtree;
#[tool_definition]
trait Git {
#[command(subtree = Remote)]
fn remote(&self, verbose: bool) -> RemoteSubtree;
}
fn assert_future_output<F, T>(_future: F)
where
F: Future<Output = T>,
{
}
fn check_grep_client_shape() {
let client = GrepClient::default();
assert_future_output::<_, Result<Vec<String>, golem_rust::agentic::ToolError<GrepError>>>(
client.grep(true, "needle".to_string(), vec!["src/lib.rs".to_string()]),
);
}
fn check_plain_return_uses_infallible_tool_error() {
let client = PlainReturnToolClient::default();
assert_future_output::<_, Result<String, golem_rust::agentic::ToolError<Infallible>>>(
client.version(),
);
}
fn check_stdout_is_returned_not_passed(
client: &StreamToolClient,
input: golem_rust::agentic::InputStream,
) {
assert_future_output::<_, Result<golem_rust::agentic::ToolInvocation<String, RemoteError>, golem_rust::agentic::ToolError<RemoteError>>>(
client.copy(input),
);
}
fn check_subtree_client_shape() {
let remote = GitClient::default().remote(true);
assert_future_output::<_, Result<(), golem_rust::agentic::ToolError<RemoteError>>>(
remote.add(
"origin".to_string(),
"https://example.invalid/repo.git".to_string(),
),
);
assert_future_output::<_, Result<(), golem_rust::agentic::ToolError<RemoteError>>>(
RemoteClient::default().add(
true,
"upstream".to_string(),
"https://example.invalid/upstream.git".to_string(),
),
);
}
"#,
);
assert!(
output.status.success(),
"typed tool clients should expose awaitable methods with typed errors, stdout-in-result, stdin params, and subtree clients; cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_omits_alias_captured_global() {
let output = cargo_check_tool_crate(
"subtree-client-omits-alias-captured-global",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
fn leaf(&self, format: u32, name: String) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait Parent {
#[arg(count = "global", aliases = ["format"])]
#[command(subtree = Child)]
fn child(&self, count: u32) -> ChildSubtree;
}
fn check_grafted_client_uses_captured_alias() {
let child = ParentClient::default().child(7);
let _ = child.leaf("alice".to_string());
}
fn check_standalone_child_keeps_full_signature() {
let _ = ChildClient::default().leaf(7, "alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"grafted typed clients should omit child parameters captured by an inherited global alias, while standalone child clients keep the full signature\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_omits_non_first_alias_captured_global() {
let output = cargo_check_tool_crate(
"subtree-client-omits-non-first-alias-captured-global",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
fn leaf(&self, format: u32, name: String) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait Parent {
#[arg(count = "global", aliases = ["level", "format"])]
#[command(subtree = Child)]
fn child(&self, count: u32) -> ChildSubtree;
}
fn check_grafted_client_uses_captured_non_first_alias() {
let child = ParentClient::default().child(7);
let _ = child.leaf("alice".to_string());
}
fn check_standalone_child_keeps_full_signature() {
let _ = ChildClient::default().leaf(7, "alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"grafted typed clients should omit child parameters captured by any inherited global alias, not only the first alias, while standalone child clients keep the full signature\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_omits_child_root_alias_captured_by_primary_global() {
let output = cargo_check_tool_crate(
"subtree-client-omits-child-root-alias-captured-by-primary-global",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
#[arg(count = "global", aliases = ["format"])]
fn child(&self, count: u32, target: String) -> String;
fn leaf(&self, format: u32, name: String) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait Parent {
#[arg(count = "global")]
#[command(subtree = Child)]
fn child(&self, count: u32) -> ChildSubtree;
}
fn check_grafted_client_uses_captured_child_root_alias() {
let child = ParentClient::default().child(7);
let _ = child.leaf("alice".to_string());
}
fn check_standalone_child_keeps_full_signature() {
let _ = ChildClient::default().leaf(7, "alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"grafted typed clients should omit child leaf parameters captured via the child root global's alias when the parent supplies that canonical global\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_omits_dispatcher_param_canonicalized_by_parent_alias() {
let output = cargo_check_tool_crate(
"subtree-client-omits-dispatcher-param-canonicalized-by-parent-alias",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
fn leaf(&self, count: u32, name: String) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait ParentAlias {
#[arg(count = "global", aliases = ["format"])]
fn parent_alias(&self, count: u32, target: String) -> String;
#[command(subtree = Child)]
fn child(&self, format: u32) -> ChildSubtree;
}
fn check_grafted_client_uses_dispatcher_alias_as_inherited_count() {
let child = ParentAliasClient::default().child(7);
let _ = child.leaf("alice".to_string());
}
fn check_standalone_child_keeps_full_signature() {
let _ = ChildClient::default().leaf(7, "alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"a subtree dispatcher parameter captured via a parent root-global alias should omit child parameters matching the canonical inherited global\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_omits_parent_root_global_without_dispatcher_param() {
let output = cargo_check_tool_crate(
"subtree-client-omits-parent-root-global-without-dispatcher-param",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
fn leaf(&self, verbose: bool, name: String) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait Parent {
#[arg(verbose = "global", kind = "flag")]
fn parent(&self, verbose: bool, target: String) -> String;
#[command(subtree = Child)]
fn child(&self) -> ChildSubtree;
}
fn check_grafted_client_uses_root_inherited_global() {
let child = ParentClient::default().child(true);
let _ = child.leaf("alice".to_string());
}
fn check_standalone_child_keeps_full_signature() {
let _ = ChildClient::default().leaf(true, "alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"grafted typed clients should omit child parameters captured by globals inherited from the parent root command, even when the subtree dispatcher has no own parameters\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_keeps_leaf_param_when_child_root_alias_is_deprojected() {
let output = cargo_check_tool_crate(
"subtree-client-keeps-leaf-param-when-child-root-alias-is-deprojected",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
#[arg(format = "global", aliases = ["count"])]
fn child(&self, format: u32, target: String) -> String;
fn leaf(&self, format: u32, name: String) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait Parent {
#[arg(count = "global")]
#[command(subtree = Child)]
fn child(&self, count: u32) -> ChildSubtree;
}
fn check_deprojected_child_root_alias_does_not_omit_primary_leaf_param() {
let child = ParentClient::default().child(7);
let _ = child.leaf(9, "alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"when a child root global is de-projected through an alias to the inherited `count`, its primary `format` surface must not omit unrelated child leaf parameters\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_omits_non_prefix_captured_global() {
let output = cargo_check_tool_crate(
"subtree-client-omits-non-prefix-captured-global",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
fn leaf(&self, name: String, verbose: bool) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait Parent {
#[command(subtree = Child)]
fn child(&self, verbose: bool) -> ChildSubtree;
}
fn check_grafted_client_uses_captured_non_prefix_global() {
let child = ParentClient::default().child(true);
let _ = child.leaf("alice".to_string());
}
fn check_standalone_child_keeps_full_signature() {
let _ = ChildClient::default().leaf("alice".to_string(), true);
}
"#,
);
assert!(
output.status.success(),
"grafted typed clients should omit inherited globals regardless of where the matching child parameter appears, while standalone child clients keep the full signature\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_wide_child_surface_still_omits_captured_global() {
let mut source = String::from("use golem_rust::tool_definition;\n\n");
source.push_str("#[tool_definition]\ntrait Child {\n");
source.push_str(" fn leaf(&self,\n");
for i in 0..17 {
source.push_str(&format!(" g{i}: String,\n"));
}
source.push_str(" value: String,\n ) -> String;\n}\n\n");
source.push_str("struct ChildSubtree;\n\n");
source.push_str("#[tool_definition]\ntrait Parent {\n");
source.push_str(" #[arg(g0 = \"global\")]\n");
source.push_str(" #[command(subtree = Child)]\n");
source.push_str(" fn child(&self, g0: String) -> ChildSubtree;\n");
source.push_str("}\n\n");
source.push_str("fn check_grafted_client_uses_captured_global() {\n");
source.push_str(" let child = ParentClient::default().child(\"zero\".to_string());\n");
source.push_str(" let _ = child.leaf(\n");
for i in 1..17 {
source.push_str(&format!(" \"g{i}\".to_string(),\n"));
}
source.push_str(" \"value\".to_string(),\n );\n}\n\n");
source.push_str("fn check_standalone_child_keeps_full_signature() {\n");
source.push_str(" let _ = ChildClient::default().leaf(\n");
for i in 0..17 {
source.push_str(&format!(" \"g{i}\".to_string(),\n"));
}
source.push_str(" \"value\".to_string(),\n );\n}\n");
let output = cargo_check_tool_crate(
"subtree-client-wide-child-surface-omits-captured-global",
&source,
);
assert!(
output.status.success(),
"bounded omitted-surface generation should still compile valid grafted clients for wide child surfaces and omit the captured inherited global\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_wide_child_surface_omits_two_captured_globals() {
let mut source = String::from("use golem_rust::tool_definition;\n\n");
source.push_str("#[tool_definition]\ntrait Child {\n");
source.push_str(" fn leaf(&self,\n");
for i in 0..17 {
source.push_str(&format!(" g{i}: String,\n"));
}
source.push_str(" value: String,\n ) -> String;\n}\n\n");
source.push_str("struct ChildSubtree;\n\n");
source.push_str("#[tool_definition]\ntrait Parent {\n");
source.push_str(" #[arg(g0 = \"global\")]\n");
source.push_str(" #[arg(g1 = \"global\")]\n");
source.push_str(" #[command(subtree = Child)]\n");
source.push_str(" fn child(&self, g0: String, g1: String) -> ChildSubtree;\n");
source.push_str("}\n\n");
source.push_str("fn check_grafted_client_uses_captured_globals() {\n");
source.push_str(" let child = ParentClient::default().child(\"zero\".to_string(), \"one\".to_string());\n");
source.push_str(" let _ = child.leaf(\n");
for i in 2..17 {
source.push_str(&format!(" \"g{i}\".to_string(),\n"));
}
source.push_str(" \"value\".to_string(),\n );\n}\n\n");
source.push_str("fn check_standalone_child_keeps_full_signature() {\n");
source.push_str(" let _ = ChildClient::default().leaf(\n");
for i in 0..17 {
source.push_str(&format!(" \"g{i}\".to_string(),\n"));
}
source.push_str(" \"value\".to_string(),\n );\n}\n");
let output = cargo_check_tool_crate(
"subtree-client-wide-child-surface-omits-two-captured-globals",
&source,
);
assert!(
output.status.success(),
"bounded omitted-surface generation should still compile valid grafted clients for wide child surfaces and omit all captured inherited globals\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_omits_reversed_order_captured_globals() {
let output = cargo_check_tool_crate(
"subtree-client-omits-reversed-order-captured-globals",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Child {
fn leaf(&self, g0: String, g1: String, value: String) -> String;
}
struct ChildSubtree;
#[tool_definition]
trait Parent {
#[arg(g1 = "global")]
#[arg(g0 = "global")]
#[command(subtree = Child)]
fn child(&self, g1: String, g0: String) -> ChildSubtree;
}
fn check_grafted_client_uses_captured_globals_regardless_of_parent_order() {
let child = ParentClient::default().child("one".to_string(), "zero".to_string());
let _ = child.leaf("value".to_string());
}
fn check_standalone_child_keeps_full_signature() {
let _ = ChildClient::default().leaf(
"zero".to_string(),
"one".to_string(),
"value".to_string(),
);
}
"#,
);
assert!(
output.status.success(),
"grafted typed clients should omit all inherited globals regardless of whether the parent captures them in child parameter order; cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn client_duplicate_canonical_name_preserves_last_staged_value() {
let output = cargo_test_tool_crate_with_fake_sdk(
"client-duplicate-canonical-name-last-wins",
r#"
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
thread_local! {
static LAST_INPUT: RefCell<Option<golem_rust::TypedSchemaValue>> = const { RefCell::new(None) };
}
mod golem_rust {
pub use golem_rust_actual::*;
pub mod golem_agentic {
pub use golem_rust_actual::golem_agentic::*;
pub mod golem {
pub use golem_rust_actual::golem_agentic::golem::*;
pub mod tool {
pub use golem_rust_actual::golem_agentic::golem::tool::*;
pub mod host {
#[derive(Clone, Debug)]
pub struct ToolRpc;
impl ToolRpc {
pub fn new(_name: &str) -> Self {
Self
}
}
}
}
}
}
pub mod agentic {
pub use golem_rust_actual::agentic::*;
pub mod ambient_tool_rpc {
pub struct AmbientToolRpc;
impl AmbientToolRpc {
pub fn new(_name: &str) -> Self {
Self
}
}
}
pub async fn invoke_and_await_direct_infallible(
_rpc: &ambient_tool_rpc::AmbientToolRpc,
_command_path: &[String],
input: crate::golem_rust::schema::wit::wire::TypedSchemaValue,
_stdin: Option<golem_rust_actual::golem_agentic::golem::tool::host::ToolStdin>,
_stdout: Option<golem_rust_actual::golem_agentic::golem::tool::host::ToolOutput>,
_stderr: Option<golem_rust_actual::golem_agentic::golem::tool::host::ToolOutput>,
) -> Result<DirectInvocationResult, ToolError<std::convert::Infallible>> {
crate::LAST_INPUT.with(|slot| *slot.borrow_mut() = Some(crate::golem_rust::decode_typed_schema_value_owned(input).unwrap()));
let tree = crate::golem_rust::schema::wit::direct::encode("ok").unwrap();
Ok(DirectInvocationResult {
root: Some(tree.root),
snapshot: std::rc::Rc::new(crate::golem_rust::schema::wit::direct::WireSnapshot::new(tree.value_nodes)),
})
}
}
}
pub use golem_rust::*;
#[tool_definition]
trait MultiAliasClientInput {
#[arg(count = "global", aliases = ["format", "level"])]
fn multi_alias_client_input(&self, count: u32, target: String);
fn leaf(&self, format: u32, level: u32, name: String) -> String;
}
fn block_on_ready<F: Future>(future: F) -> F::Output {
fn no_op(_: *const ()) {}
fn clone(_: *const ()) -> RawWaker {
RawWaker::new(std::ptr::null(), &VTABLE)
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, no_op, no_op, no_op);
let waker = unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) };
let mut context = Context::from_waker(&waker);
let mut future = std::pin::pin!(future);
match Future::poll(Pin::as_mut(&mut future), &mut context) {
Poll::Ready(output) => output,
Poll::Pending => panic!("future unexpectedly pending"),
}
}
#[test]
fn duplicate_canonical_param_uses_last_staged_value() {
let value = block_on_ready(
MultiAliasClientInputClient::default().leaf(1, 2, "alice".to_string()),
)
.expect("fake RPC returns a successful String result");
assert_eq!(value, "ok");
let input = LAST_INPUT
.with(|slot| slot.borrow_mut().take())
.expect("client invoked fake RPC");
let golem_rust::SchemaValue::Record { fields } = input.value() else {
panic!("expected client input to be a record");
};
assert_eq!(
fields[0],
golem_rust::SchemaValue::Option {
inner: Some(Box::new(golem_rust::SchemaValue::U32(2))),
},
"when two client arguments map to the same canonical inherited global, the generated client must preserve the pre-optimization BTreeMap::insert overwrite semantics",
);
}
"#,
);
assert!(
output.status.success(),
"generated clients should preserve the pre-optimization last-write-wins behavior when multiple arguments map to the same canonical input name\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn client_duplicate_canonical_name_preserves_last_staged_value_after_prior_removal() {
let output = cargo_test_tool_crate_with_fake_sdk(
"client-duplicate-canonical-name-last-wins-after-prior-removal",
r#"
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
thread_local! {
static LAST_INPUT: RefCell<Option<golem_rust::TypedSchemaValue>> = const { RefCell::new(None) };
}
mod golem_rust {
pub use golem_rust_actual::*;
pub mod golem_agentic {
pub use golem_rust_actual::golem_agentic::*;
pub mod golem {
pub use golem_rust_actual::golem_agentic::golem::*;
pub mod tool {
pub use golem_rust_actual::golem_agentic::golem::tool::*;
pub mod host {
#[derive(Clone, Debug)]
pub struct ToolRpc;
impl ToolRpc {
pub fn new(_name: &str) -> Self {
Self
}
}
}
}
}
}
pub mod agentic {
pub use golem_rust_actual::agentic::*;
pub mod ambient_tool_rpc {
pub struct AmbientToolRpc;
impl AmbientToolRpc {
pub fn new(_name: &str) -> Self {
Self
}
}
}
pub async fn invoke_and_await_direct_infallible(
_rpc: &ambient_tool_rpc::AmbientToolRpc,
_command_path: &[String],
input: crate::golem_rust::schema::wit::wire::TypedSchemaValue,
_stdin: Option<golem_rust_actual::golem_agentic::golem::tool::host::ToolStdin>,
_stdout: Option<golem_rust_actual::golem_agentic::golem::tool::host::ToolOutput>,
_stderr: Option<golem_rust_actual::golem_agentic::golem::tool::host::ToolOutput>,
) -> Result<DirectInvocationResult, ToolError<std::convert::Infallible>> {
crate::LAST_INPUT.with(|slot| *slot.borrow_mut() = Some(crate::golem_rust::decode_typed_schema_value_owned(input).unwrap()));
let tree = crate::golem_rust::schema::wit::direct::encode("ok").unwrap();
Ok(DirectInvocationResult {
root: Some(tree.root),
snapshot: std::rc::Rc::new(crate::golem_rust::schema::wit::direct::WireSnapshot::new(tree.value_nodes)),
})
}
}
}
pub use golem_rust::*;
#[tool_definition]
trait SwapRemoveLastWins {
#[arg(first = "global")]
#[arg(count = "global", aliases = ["format", "level", "depth"])]
fn swap_remove_last_wins(&self, first: u32, count: u32, target: String);
fn leaf(&self, name: String, format: u32, level: u32, depth: u32) -> String;
}
fn block_on_ready<F: Future>(future: F) -> F::Output {
fn no_op(_: *const ()) {}
fn clone(_: *const ()) -> RawWaker {
RawWaker::new(std::ptr::null(), &VTABLE)
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, no_op, no_op, no_op);
let waker = unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) };
let mut context = Context::from_waker(&waker);
let mut future = std::pin::pin!(future);
match Future::poll(Pin::as_mut(&mut future), &mut context) {
Poll::Ready(output) => output,
Poll::Pending => panic!("future unexpectedly pending"),
}
}
#[test]
fn duplicate_canonical_param_uses_last_staged_value_after_prior_removal() {
let value = block_on_ready(
SwapRemoveLastWinsClient::default().leaf(
99,
"alice".to_string(),
1,
2,
3,
),
)
.expect("fake RPC returns a successful String result");
assert_eq!(value, "ok");
let input = LAST_INPUT
.with(|slot| slot.borrow_mut().take())
.expect("client invoked fake RPC");
let golem_rust::SchemaValue::Record { fields } = input.value() else {
panic!("expected client input to be a record");
};
assert_eq!(
fields[0],
golem_rust::SchemaValue::Option {
inner: Some(Box::new(golem_rust::SchemaValue::U32(99))),
},
);
assert_eq!(
fields[1],
golem_rust::SchemaValue::Option {
inner: Some(Box::new(golem_rust::SchemaValue::U32(3))),
},
"last staged duplicate canonical value must still win after packing an earlier canonical field",
);
assert_eq!(
fields[2],
golem_rust::SchemaValue::String("alice".to_string()),
);
}
"#,
);
assert!(
output.status.success(),
"generated clients should preserve last-write-wins when packing an earlier canonical field removes a staged value before a later duplicate canonical field\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_exposes_nested_subtree_methods() {
let output = cargo_check_tool_crate(
"subtree-client-exposes-nested-subtree-methods",
r#"
use golem_rust::{tool_definition, ToolError};
#[derive(ToolError)]
enum RemoteError {
#[tool_error(kind = "runtime-error", exit_code = 1)]
Failed(String),
}
#[tool_definition]
trait Inner {
fn leaf(&self, verbose: bool, name: String) -> Result<(), RemoteError>;
}
struct InnerSubtree;
struct MidSubtree;
#[tool_definition]
trait Mid {
#[command(subtree = Inner)]
fn inner(&self) -> InnerSubtree;
}
#[tool_definition]
trait Outer {
#[command(subtree = Mid)]
fn mid(&self, verbose: bool) -> MidSubtree;
}
fn check_nested_grafted_client_shape() {
let mid = OuterClient::default().mid(true);
let inner = mid.inner();
let _ = inner.leaf("alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"typed clients for valid nested subtree descriptors should expose nested subtree wrapper methods and keep inherited captures across levels\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn subtree_client_reused_nested_subtree_wrappers_do_not_duplicate_impls() {
let output = cargo_check_tool_crate(
"subtree-client-reused-nested-subtree-wrappers",
r#"
use golem_rust::{tool_definition, ToolError};
#[derive(ToolError)]
enum RemoteError {
#[tool_error(kind = "runtime-error", exit_code = 1)]
Failed(String),
}
#[tool_definition]
trait Inner {
fn leaf(&self, verbose: bool, name: String) -> Result<(), RemoteError>;
}
struct InnerSubtree;
struct MidSubtree;
#[tool_definition]
trait Mid {
#[command(subtree = Inner)]
fn inner(&self) -> InnerSubtree;
}
#[tool_definition]
trait FirstOuter {
#[command(subtree = Mid)]
fn mid(&self, verbose: bool) -> MidSubtree;
}
#[tool_definition]
trait SecondOuter {
#[command(subtree = Mid)]
fn mid(&self, verbose: bool) -> MidSubtree;
}
fn check_reused_nested_grafted_client_shape() {
let first_inner = FirstOuterClient::default().mid(true).inner();
let _ = first_inner.leaf("alice".to_string());
let second_inner = SecondOuterClient::default().mid(false).inner();
let _ = second_inner.leaf("bob".to_string());
}
"#,
);
assert!(
output.status.success(),
"reusing the same intermediate subtree under multiple parents must not emit duplicate nested wrapper impls\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_subtree_client_supports_sparse_nested_capture_set() {
let output = cargo_check_tool_crate(
"subtree-client-sparse-nested-capture-set",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Inner {
fn leaf(&self, verbose: bool, name: String, mode: u32) -> String;
}
struct InnerSubtree;
struct MidSubtree;
#[tool_definition]
trait Mid {
#[command(subtree = Inner)]
fn inner(&self, mode: u32) -> InnerSubtree;
}
#[tool_definition]
trait Outer {
#[command(subtree = Mid)]
fn mid(&self, verbose: bool) -> MidSubtree;
}
fn check_sparse_nested_capture_set() {
let mid = OuterClient::default().mid(true);
let inner = mid.inner(7);
let _ = inner.leaf("alice".to_string());
}
"#,
);
assert!(
output.status.success(),
"nested grafted typed clients should expose leaf methods when inherited globals captured at different levels form a sparse child-parameter subset; cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait ChildRoundTrip {
fn leaf(&self, verbose: bool, name: String) -> String;
}
struct ChildRoundTripSubtree;
struct ChildRoundTripImpl;
#[tool_implementation]
impl ChildRoundTrip for ChildRoundTripImpl {
fn leaf(&self, verbose: bool, name: String) -> String {
format!("child:{verbose}:{name}")
}
}
#[tool_definition]
trait ParentRoundTrip {
#[command(name = "kid", aliases = ["k"], subtree = ChildRoundTrip)]
fn child_round_trip(&self, verbose: bool) -> ChildRoundTripSubtree;
}
struct ParentRoundTripImpl;
#[tool_implementation]
impl ParentRoundTrip for ParentRoundTripImpl {
fn child_round_trip(&self, _verbose: bool) -> ChildRoundTripSubtree {
ChildRoundTripSubtree
}
}
#[test]
async fn guest_invoke_routes_grafted_subtrees_to_child_invoker() {
let tool = <ParentRoundTripImpl as ParentRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("parent-round-trip")
.expect("parent tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["kid", "leaf"],
vec![
golem_rust::SchemaValue::Bool(true),
golem_rust::SchemaValue::String("bob".to_string()),
],
);
let result = invoker(
vec!["kid".to_string(), "leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("grafted subtree invocation succeeds");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "child:true:bob");
}
#[test]
async fn guest_invoke_routes_grafted_subtrees_through_command_aliases() {
let tool = <ParentRoundTripImpl as ParentRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("parent-round-trip")
.expect("parent tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["k", "leaf"],
vec![
golem_rust::SchemaValue::Bool(true),
golem_rust::SchemaValue::String("aliased".to_string()),
],
);
let result = invoker(
vec!["k".to_string(), "leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("grafted subtree invocation succeeds through command aliases");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "child:true:aliased");
}
#[tool_definition]
trait AliasChildRoundTrip {
fn leaf(&self, format: u32, name: String) -> String;
}
struct AliasChildRoundTripSubtree;
struct AliasChildRoundTripImpl;
#[tool_implementation]
impl AliasChildRoundTrip for AliasChildRoundTripImpl {
fn leaf(&self, format: u32, name: String) -> String {
format!("child:{format}:{name}")
}
}
#[tool_definition]
trait AliasParentSubtreeRoundTrip {
#[arg(count = "global", aliases = ["format"])]
#[command(subtree = AliasChildRoundTrip)]
fn alias_child_round_trip(&self, count: u32) -> AliasChildRoundTripSubtree;
}
struct AliasParentSubtreeRoundTripImpl;
#[tool_implementation]
impl AliasParentSubtreeRoundTrip for AliasParentSubtreeRoundTripImpl {
fn alias_child_round_trip(&self, _count: u32) -> AliasChildRoundTripSubtree {
AliasChildRoundTripSubtree
}
}
#[test]
async fn guest_invoke_maps_alias_deprojected_subtree_globals_to_child_parameters() {
let tool =
<AliasParentSubtreeRoundTripImpl as AliasParentSubtreeRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("alias-parent-subtree-round-trip")
.expect("parent tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["alias-child-round-trip", "leaf"],
vec![
golem_rust::SchemaValue::U32(7),
golem_rust::SchemaValue::String("aliased-subtree".to_string()),
],
);
let result = invoker(
vec!["alias-child-round-trip".to_string(), "leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect(
"guest invocation maps an inherited subtree global alias back to the child parameter",
);
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "child:7:aliased-subtree");
}
#[tool_definition]
trait SubtreeAliasSiblingIsolation {
fn sibling(&self, count: u32, format: String) -> String;
#[arg(count = "global", aliases = ["format"])]
#[command(subtree = AliasChildRoundTrip)]
fn alias_child_round_trip(&self, count: u32) -> AliasChildRoundTripSubtree;
}
struct SubtreeAliasSiblingIsolationImpl;
#[tool_implementation]
impl SubtreeAliasSiblingIsolation for SubtreeAliasSiblingIsolationImpl {
fn sibling(&self, count: u32, format: String) -> String {
format!("{count}:{format}")
}
fn alias_child_round_trip(&self, _count: u32) -> AliasChildRoundTripSubtree {
AliasChildRoundTripSubtree
}
}
#[test]
async fn subtree_aliases_do_not_shadow_sibling_fields() {
let tool =
<SubtreeAliasSiblingIsolationImpl as SubtreeAliasSiblingIsolation>::__tool_descriptor();
let input = encoded_input(
&tool,
&["sibling"],
vec![
golem_rust::SchemaValue::U32(7),
golem_rust::SchemaValue::String("plain".to_string()),
],
);
let result = SubtreeAliasSiblingIsolationImpl::__tool_invoke(
vec!["sibling".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("an alias owned by a different command path must not shadow sibling fields");
let result = result.result.expect("plain return is encoded as a result");
assert_eq!(
golem_rust::schema::wit::direct::decode::<String>(result.value).unwrap(),
"7:plain",
);
}
#[tool_definition]
trait EarlierGlobalAliasParentSubtreeRoundTrip {
#[arg(profile = "global")]
fn earlier_global_alias_parent_subtree_round_trip(
&self,
profile: u32,
target: String,
) -> Result<(), RemoteError>;
#[arg(count = "global", aliases = ["format"])]
#[command(name = "alias-child-round-trip", aliases = ["alias-child"], subtree = AliasChildRoundTrip)]
fn alias_child(&self, count: u32) -> AliasChildRoundTripSubtree;
}
struct EarlierGlobalAliasParentSubtreeRoundTripImpl;
#[tool_implementation]
impl EarlierGlobalAliasParentSubtreeRoundTrip for EarlierGlobalAliasParentSubtreeRoundTripImpl {
fn earlier_global_alias_parent_subtree_round_trip(
&self,
_profile: u32,
_target: String,
) -> Result<(), RemoteError> {
Ok(())
}
fn alias_child(&self, _count: u32) -> AliasChildRoundTripSubtree {
AliasChildRoundTripSubtree
}
}
#[test]
async fn guest_invoke_uses_alias_mapping_not_position_for_subtree_globals() {
let tool = <EarlierGlobalAliasParentSubtreeRoundTripImpl as EarlierGlobalAliasParentSubtreeRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("earlier-global-alias-parent-subtree-round-trip")
.expect("parent tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["alias-child", "leaf"],
vec![
golem_rust::SchemaValue::U32(99),
golem_rust::SchemaValue::U32(7),
golem_rust::SchemaValue::String("aliased-subtree".to_string()),
],
);
let result = invoker(
vec!["alias-child".to_string(), "leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("guest invocation uses alias mapping instead of same-typed absolute position");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "child:7:aliased-subtree");
}
#[tool_definition]
trait ReverseAliasChildRoundTrip {
#[arg(count = "option", aliases = ["format"])]
fn leaf(&self, count: u32, name: String) -> String;
}
struct ReverseAliasChildRoundTripSubtree;
struct ReverseAliasChildRoundTripImpl;
#[tool_implementation]
impl ReverseAliasChildRoundTrip for ReverseAliasChildRoundTripImpl {
fn leaf(&self, count: u32, name: String) -> String {
format!("child:{count}:{name}")
}
}
#[tool_definition]
trait ReverseAliasParentSubtreeRoundTrip {
#[arg(format = "global")]
#[command(subtree = ReverseAliasChildRoundTrip)]
fn reverse_alias_child_round_trip(&self, format: u32) -> ReverseAliasChildRoundTripSubtree;
}
struct ReverseAliasParentSubtreeRoundTripImpl;
#[tool_implementation]
impl ReverseAliasParentSubtreeRoundTrip for ReverseAliasParentSubtreeRoundTripImpl {
fn reverse_alias_child_round_trip(
&self,
_format: u32,
) -> ReverseAliasChildRoundTripSubtree {
ReverseAliasChildRoundTripSubtree
}
}
#[test]
async fn guest_invoke_matches_child_aliases_for_subtree_globals() {
let tool = <ReverseAliasParentSubtreeRoundTripImpl as ReverseAliasParentSubtreeRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("reverse-alias-parent-subtree-round-trip")
.expect("parent tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["reverse-alias-child-round-trip", "leaf"],
vec![
golem_rust::SchemaValue::U32(7),
golem_rust::SchemaValue::String("reverse-alias".to_string()),
],
);
let result = invoker(
vec![
"reverse-alias-child-round-trip".to_string(),
"leaf".to_string(),
],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("guest invocation maps a child alias back to the inherited parent global");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "child:7:reverse-alias");
}
#[tool_definition]
trait ImplParameterNameRoundTrip {
fn leaf(&self, verbose: bool, name: String) -> String;
}
struct ImplParameterNameRoundTripImpl;
#[tool_implementation]
impl ImplParameterNameRoundTrip for ImplParameterNameRoundTripImpl {
fn leaf(&self, v: bool, n: String) -> String {
format!("{v}:{n}")
}
}
#[test]
async fn guest_invoke_uses_trait_parameter_names_not_impl_parameter_names() {
let tool =
<ImplParameterNameRoundTripImpl as ImplParameterNameRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("impl-parameter-name-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["leaf"],
vec![
golem_rust::SchemaValue::String("renamed".to_string()),
golem_rust::SchemaValue::Bool(true),
],
);
let result = invoker(
vec!["leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("guest invocation succeeds when impl parameter names differ from the trait");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "true:renamed");
}
#[tool_definition]
trait AliasInheritedGlobalRoundTrip {
#[arg(count = "global", aliases = ["format"])]
fn alias_inherited_global_round_trip(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, format: u32, name: String) -> String;
}
struct AliasInheritedGlobalRoundTripImpl;
#[tool_implementation]
impl AliasInheritedGlobalRoundTrip for AliasInheritedGlobalRoundTripImpl {
fn alias_inherited_global_round_trip(
&self,
_count: u32,
_target: String,
) -> Result<(), RemoteError> {
Ok(())
}
fn leaf(&self, format: u32, name: String) -> String {
format!("{format}:{name}")
}
}
#[test]
async fn guest_invoke_maps_alias_deprojected_inherited_globals_to_method_parameters() {
let tool =
<AliasInheritedGlobalRoundTripImpl as AliasInheritedGlobalRoundTrip>::__tool_descriptor(
);
let invoker = get_tool_invoker_by_name("alias-inherited-global-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["leaf"],
vec![
golem_rust::SchemaValue::U32(7),
golem_rust::SchemaValue::String("aliased-global".to_string()),
],
);
let result = invoker(
vec!["leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("guest invocation maps an inherited global alias back to the method parameter");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "7:aliased-global");
}
#[tool_definition]
trait ReverseAliasInheritedGlobalRoundTrip {
#[arg(format = "global")]
fn reverse_alias_inherited_global_round_trip(
&self,
format: u32,
target: String,
) -> Result<(), RemoteError>;
#[arg(count = "option", aliases = ["format"])]
fn leaf(&self, count: u32, name: String) -> String;
}
struct ReverseAliasInheritedGlobalRoundTripImpl;
#[tool_implementation]
impl ReverseAliasInheritedGlobalRoundTrip for ReverseAliasInheritedGlobalRoundTripImpl {
fn reverse_alias_inherited_global_round_trip(
&self,
_format: u32,
_target: String,
) -> Result<(), RemoteError> {
Ok(())
}
fn leaf(&self, count: u32, name: String) -> String {
format!("{count}:{name}")
}
}
#[test]
async fn guest_invoke_matches_child_aliases_for_same_trait_inherited_globals() {
let tool = <ReverseAliasInheritedGlobalRoundTripImpl as ReverseAliasInheritedGlobalRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("reverse-alias-inherited-global-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["leaf"],
vec![
golem_rust::SchemaValue::U32(7),
golem_rust::SchemaValue::String("reverse-alias-global".to_string()),
],
);
let result = invoker(
vec!["leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("guest invocation maps a child alias back to the same-trait inherited global");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "7:reverse-alias-global");
}
#[tool_definition]
trait RootBodyAliasDoesNotDeprojectRoundTrip {
#[arg(count = "option", aliases = ["format"])]
fn root_body_alias_does_not_deproject_round_trip(
&self,
count: u32,
) -> Result<(), RemoteError>;
fn leaf(&self, format: u32, name: String) -> String;
}
struct RootBodyAliasDoesNotDeprojectRoundTripImpl;
#[tool_implementation]
impl RootBodyAliasDoesNotDeprojectRoundTrip for RootBodyAliasDoesNotDeprojectRoundTripImpl {
fn root_body_alias_does_not_deproject_round_trip(
&self,
_count: u32,
) -> Result<(), RemoteError> {
Ok(())
}
fn leaf(&self, format: u32, name: String) -> String {
format!("{format}:{name}")
}
}
#[test]
async fn guest_invoke_does_not_deproject_root_body_aliases() {
let tool = <RootBodyAliasDoesNotDeprojectRoundTripImpl as RootBodyAliasDoesNotDeprojectRoundTrip>::__tool_descriptor();
let invoker = get_tool_invoker_by_name("root-body-alias-does-not-deproject-round-trip")
.expect("tool implementation registers an invoker");
let input = encoded_input(
&tool,
&["leaf"],
vec![
golem_rust::SchemaValue::U32(7),
golem_rust::SchemaValue::String("leaf-value".to_string()),
],
);
let result = invoker(
vec!["leaf".to_string()],
input,
None,
None,
None,
anonymous_principal(),
)
.await
.expect("root body aliases are not inherited by sibling commands");
let result = result.result.expect("plain return is encoded as a result");
let result = golem_rust::decode_typed_schema_value(&result).expect("result decodes");
let value = String::from_value(result.value()).expect("result schema matches String");
assert_eq!(value, "7:leaf-value");
}
struct Meters;
impl QuantityUnit for Meters {
fn type_id() -> golem_rust::schema::TypeId {
golem_rust::schema::TypeId::new("golem.test.Meters")
}
fn base_unit() -> &'static str {
"m"
}
}
struct Seconds;
impl QuantityUnit for Seconds {
fn type_id() -> golem_rust::schema::TypeId {
golem_rust::schema::TypeId::new("golem.test.Seconds")
}
fn base_unit() -> &'static str {
"s"
}
}
#[tool_definition]
trait QuantityGlobalMismatch {
#[arg(amount = "global")]
fn quantity_global_mismatch(
&self,
amount: Quantity<Meters>,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, amount: Quantity<Seconds>, name: String) -> Result<(), RemoteError>;
}
#[test]
fn leaf_redeclaring_quantity_root_global_with_different_unit_is_rejected() {
let err = __golem_tool_descriptor_for_QuantityGlobalMismatch(&mut ToolBuildCtx::new())
.expect_err(
"a leaf re-declaring a quantity root global with a different unit spec must be rejected",
);
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "amount" && inherited == "amount" && command == "leaf"
),
"expected an InheritedGlobalConflict for quantity `amount` on `leaf`, got {err:?}",
);
}
#[tool_definition]
trait RootGlobalLeafMismatch {
#[arg(verbose = "global", kind = "flag")]
fn root_global_leaf_mismatch(
&self,
verbose: bool,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, verbose: String, name: String) -> Result<(), RemoteError>;
}
#[test]
fn leaf_redeclaring_root_global_with_mismatched_type_is_rejected() {
let err = __golem_tool_descriptor_for_RootGlobalLeafMismatch(&mut ToolBuildCtx::new())
.expect_err(
"a leaf re-declaring a root global with an incompatible type must be rejected",
);
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "verbose" && inherited == "verbose" && command == "leaf"
),
"expected an InheritedGlobalConflict for `verbose` on `leaf`, got {err:?}",
);
}
#[tool_definition]
trait RootGlobalLeafMatch {
#[arg(verbose = "global", kind = "flag")]
fn root_global_leaf_match(&self, verbose: bool, target: String) -> Result<(), RemoteError>;
fn leaf(&self, verbose: bool, name: String) -> Result<(), RemoteError>;
}
#[tool_definition]
trait RootGlobalLeafMatchInferredBool {
#[arg(verbose = "global")]
fn root_global_leaf_match_inferred_bool(
&self,
verbose: bool,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, verbose: bool, name: String) -> Result<(), RemoteError>;
}
#[test]
fn leaf_redeclaring_root_global_with_matching_type_is_suppressed() {
let tool = __golem_tool_descriptor_for_RootGlobalLeafMatch(&mut ToolBuildCtx::new())
.expect("matching root-global re-declaration builds");
let leaf = tool
.commands
.iter()
.find(|c| c.name == "leaf")
.expect("leaf command");
let body = leaf.body.as_ref().expect("leaf body");
assert!(
!body.flags.iter().any(|f| f.long == "verbose"),
"the inherited `verbose` root global must be suppressed from the leaf body"
);
let root = &tool.commands[0];
assert!(
root.globals.flags.iter().any(|f| f.long == "verbose"),
"the root command keeps the `verbose` global"
);
tool.try_to_tool()
.expect("matching root-global re-declaration is valid");
}
#[test]
fn explicit_global_bool_still_infers_global_flag() {
let tool =
__golem_tool_descriptor_for_RootGlobalLeafMatchInferredBool(&mut ToolBuildCtx::new())
.expect(
"a bool global should project as a flag and de-project a matching leaf flag",
);
let root = &tool.commands[0];
assert!(
root.globals.flags.iter().any(|f| f.long == "verbose"),
"a bool global should be exposed as a global flag"
);
assert!(
root.globals.options.iter().all(|o| o.long != "verbose"),
"a bool global should not be exposed as a value-taking global option"
);
let leaf = tool
.commands
.iter()
.find(|c| c.name == "leaf")
.expect("leaf command");
let body = leaf.body.as_ref().expect("leaf body");
assert!(
!body.flags.iter().any(|f| f.long == "verbose"),
"the matching inherited bool global must be suppressed from the leaf body"
);
tool.try_to_tool()
.expect("matching inferred bool global re-declaration is valid");
}
#[derive(Clone, Debug, IntoSchema, FromSchema, WireSchema, IntoWire, FromWire)]
struct RecursiveNode {
next: Option<Box<RecursiveNode>>,
}
#[tool_definition]
trait RootGlobalRecursiveLeafMatch {
#[arg(node = "global")]
fn root_global_recursive_leaf_match(
&self,
node: RecursiveNode,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, node: RecursiveNode, name: String) -> Result<(), RemoteError>;
}
#[test]
fn leaf_redeclaring_recursive_root_global_with_matching_type_is_suppressed() {
let tool =
__golem_tool_descriptor_for_RootGlobalRecursiveLeafMatch(&mut ToolBuildCtx::new())
.expect("the exact same recursive root-global re-declaration should be compatible");
let leaf = tool
.commands
.iter()
.find(|c| c.name == "leaf")
.expect("leaf command");
let body = leaf.body.as_ref().expect("leaf body");
assert!(
!body.positionals.fixed.iter().any(|p| p.name == "node"),
"the inherited recursive `node` root global must be suppressed from the leaf body"
);
tool.try_to_tool()
.expect("matching recursive root-global re-declaration is valid");
}
#[tool_definition]
trait DispatcherMismatchChild {
fn leaf(&self, name: String) -> Result<(), RemoteError>;
}
struct DispatcherMismatchSubtree;
#[tool_definition]
trait DispatcherMismatchParent {
#[arg(format = "global")]
fn dispatcher_mismatch_parent(
&self,
format: String,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = DispatcherMismatchChild)]
fn dispatcher_mismatch_child(&self, format: bool) -> DispatcherMismatchSubtree;
}
#[test]
fn subtree_dispatcher_param_conflicting_with_root_global_is_rejected() {
let err = __golem_tool_descriptor_for_DispatcherMismatchParent(&mut ToolBuildCtx::new())
.expect_err(
"a subtree dispatcher param conflicting with a root global must be rejected",
);
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, .. } if name == "format"
),
"expected an InheritedGlobalConflict for `format`, got {err:?}",
);
}
#[tool_definition]
trait InheritedExplicitTail {
#[arg(items = "global")]
fn inherited_explicit_tail(
&self,
items: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
#[arg(items = "tail")]
fn collect(&self, items: Vec<String>, rest: Vec<String>) -> Result<(), RemoteError>;
}
#[test]
fn inherited_explicit_tail_does_not_steal_genuine_tail_slot() {
let tool = __golem_tool_descriptor_for_InheritedExplicitTail(&mut ToolBuildCtx::new())
.expect("inherited explicit tail descriptor builds");
let collect = tool
.commands
.iter()
.find(|c| c.name == "collect")
.expect("collect command");
let body = collect.body.as_ref().expect("collect body");
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("rest"),
"the genuine `Vec<T>` body tail must keep the tail slot"
);
assert!(
!body.options.iter().any(|o| o.long == "items")
&& body.positionals.tail.as_ref().map(|t| t.name.as_str()) != Some("items"),
"the inherited `items` re-declaration (lowered to a surrogate option) must be removed"
);
assert!(
tool.commands[0]
.globals
.options
.iter()
.any(|o| o.long == "items"),
"the root keeps the `items` global"
);
tool.try_to_tool()
.expect("inherited explicit tail tool is valid");
}
#[tool_definition]
trait InheritedExplicitTailMismatch {
#[arg(items = "global")]
fn inherited_explicit_tail_mismatch(
&self,
items: Vec<u32>,
target: String,
) -> Result<(), RemoteError>;
#[arg(items = "tail")]
fn collect(&self, items: Vec<String>) -> Result<(), RemoteError>;
}
#[test]
fn inherited_explicit_tail_with_mismatched_item_type_is_rejected() {
let err =
__golem_tool_descriptor_for_InheritedExplicitTailMismatch(&mut ToolBuildCtx::new())
.expect_err(
"an inherited explicit tail of a mismatched item type must be rejected",
);
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "items" && inherited == "items" && command == "collect"
),
"expected an InheritedGlobalConflict for `items` on `collect`, got {err:?}",
);
}
#[tool_definition]
trait AliasConflict {
#[arg(verbose = "global", kind = "flag")]
fn alias_conflict(&self, verbose: bool, target: String) -> Result<(), RemoteError>;
#[arg(local = "option", aliases = ["verbose"])]
fn leaf(&self, local: String) -> Result<(), RemoteError>;
}
#[test]
fn body_option_alias_colliding_with_inherited_global_reports_alias() {
let err = __golem_tool_descriptor_for_AliasConflict(&mut ToolBuildCtx::new())
.expect_err("a body option alias colliding with an inherited global must be rejected");
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "verbose" && inherited == "verbose" && command == "leaf"
),
"expected the conflict to name the colliding alias `verbose`, got {err:?}",
);
}
#[tool_definition]
trait AliasMultiCollisionConflict {
#[arg(format = "global")]
#[arg(count = "global")]
fn alias_multi_collision_conflict(
&self,
format: String,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[arg(format = "option", aliases = ["count"])]
fn leaf(&self, format: String) -> Result<(), RemoteError>;
}
#[test]
fn body_option_alias_conflict_after_compatible_collision_is_rejected() {
let err = __golem_tool_descriptor_for_AliasMultiCollisionConflict(&mut ToolBuildCtx::new())
.expect_err(
"a local option alias colliding with an incompatible inherited global must be rejected even if its long name also collides compatibly",
);
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "count" && inherited == "count" && command == "leaf"
),
"expected an InheritedGlobalConflict for alias `count` on `leaf`, got {err:?}",
);
}
#[tool_definition]
trait AliasMultiCompatibleCollisionConflict {
#[arg(format = "global")]
#[arg(profile = "global")]
fn alias_multi_compatible_collision_conflict(
&self,
format: String,
profile: String,
target: String,
) -> Result<(), RemoteError>;
#[arg(format = "option", aliases = ["profile"])]
fn leaf(&self, format: String) -> Result<(), RemoteError>;
}
#[test]
fn body_option_alias_colliding_with_two_compatible_inherited_globals_is_rejected() {
let err = __golem_tool_descriptor_for_AliasMultiCompatibleCollisionConflict(
&mut ToolBuildCtx::new(),
)
.expect_err(
"one local parameter must not be de-projected onto two distinct inherited globals",
);
assert!(
matches!(err, ToolBuildError::InheritedGlobalConflict { ref command, .. } if command == "leaf"),
"expected an inherited-global conflict for the ambiguous `leaf` parameter, got {err:?}",
);
}
#[tool_definition]
trait RootBodyAliasCollisionChild {
#[arg(format = "option", aliases = ["count"])]
#[constraint(requires_all = value_is("count", 1))]
fn root_body_alias_collision_child(&self, format: String) -> Result<(), RemoteError>;
}
struct RootBodyAliasCollisionSubtree;
#[tool_definition]
trait RootBodyAliasCollisionParent {
#[arg(count = "global")]
fn root_body_alias_collision_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = RootBodyAliasCollisionChild)]
fn root_body_alias_collision_child(&self, format: String) -> RootBodyAliasCollisionSubtree;
}
#[test]
fn grafted_root_body_alias_collision_with_strict_ancestor_is_rejected() {
let err = __golem_tool_descriptor_for_RootBodyAliasCollisionParent(&mut ToolBuildCtx::new())
.expect_err(
"a grafted root body option colliding with the subtree global and an incompatible strict ancestor global must be rejected",
);
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "count"
&& inherited == "count"
&& command == "root-body-alias-collision-child"
),
"expected an InheritedGlobalConflict for alias `count` on the grafted root body, got {err:?}",
);
}
#[tool_definition]
trait RootBodyTailInferenceChild {
fn root_body_tail_inference_child(
&self,
items: Vec<String>,
format: String,
) -> Result<(), RemoteError>;
}
struct RootBodyTailInferenceSubtree;
#[tool_definition]
trait RootBodyTailInferenceParent {
#[command(subtree = RootBodyTailInferenceChild)]
fn root_body_tail_inference_child(&self, format: String) -> RootBodyTailInferenceSubtree;
}
#[test]
fn grafted_root_body_deprojection_preserves_tail_inference() {
let tool =
__golem_tool_descriptor_for_RootBodyTailInferenceParent(&mut ToolBuildCtx::new())
.expect("descriptor builds");
let child_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "root-body-tail-inference-child")
.expect("child subtree is grafted") as usize;
let body = tool.commands[child_idx]
.body
.as_ref()
.expect("grafted root body is preserved");
assert!(
!body.positionals.fixed.iter().any(|p| p.name == "format")
&& !body.options.iter().any(|o| o.long == "format"),
"root-body-local `format` must be removed because the subtree method supplies it as an inherited global",
);
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("items"),
"the trailing inherited-global re-declaration must not prevent `items` from being inferred as the tail positional on a grafted root body",
);
assert!(
!body.options.iter().any(|o| o.long == "items"),
"`items` should not be projected as a repeatable-list option when the only later positional is de-projected",
);
tool.try_to_tool().expect("composed tool is valid");
}
#[tool_definition]
trait OverrideConflictChild {
#[arg(verbose = "option")]
fn override_conflict_child(&self, verbose: String) -> Result<(), RemoteError>;
}
struct OverrideConflictSubtree;
#[tool_definition]
trait OverrideConflictParent {
#[command(subtree = OverrideConflictChild, name = "renamed")]
fn child(&self, verbose: bool) -> OverrideConflictSubtree;
}
#[test]
fn grafted_root_body_conflict_reports_overridden_command_name() {
let err = __golem_tool_descriptor_for_OverrideConflictParent(&mut ToolBuildCtx::new())
.expect_err("incompatible inherited global must be rejected");
assert!(
matches!(
err,
ToolBuildError::InheritedGlobalConflict { ref name, ref inherited, ref command }
if name == "verbose" && inherited == "verbose" && command == "renamed"
),
"expected the inherited-global conflict to be reported against the final overridden command name `renamed`, got {err:?}",
);
}
#[tool_definition]
trait MismatchedConflictChild {
#[arg(verbose = "option")]
fn mismatched_conflict_child(&self, verbose: String) -> Result<(), RemoteError>;
}
struct MismatchedConflictSubtree;
#[tool_definition]
trait MismatchedConflictParent {
#[command(subtree = MismatchedConflictChild)]
fn renamed(&self, verbose: bool) -> MismatchedConflictSubtree;
}
#[test]
fn subtree_root_name_mismatch_is_reported_before_inherited_global_conflict() {
let err = __golem_tool_descriptor_for_MismatchedConflictParent(&mut ToolBuildCtx::new())
.expect_err(
"a subtree root whose name differs from the parent command must be rejected",
);
assert!(
matches!(
err,
ToolBuildError::SubtreeRootNameMismatch { ref expected, ref actual }
if expected == "renamed" && actual == "mismatched-conflict-child"
),
"expected SubtreeRootNameMismatch to win before inherited-global reconciliation, got {err:?}",
);
}
#[tool_definition]
trait MismatchedParentGlobalConflictChild {
fn mismatched_parent_global_conflict_child(&self, value: String)
-> Result<(), RemoteError>;
}
struct MismatchedParentGlobalConflictSubtree;
#[tool_definition]
trait MismatchedParentGlobalConflictParent {
#[arg(verbose = "global")]
fn mismatched_parent_global_conflict_parent(
&self,
verbose: String,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = MismatchedParentGlobalConflictChild)]
fn renamed(&self, verbose: bool) -> MismatchedParentGlobalConflictSubtree;
}
#[test]
fn subtree_root_name_mismatch_beats_parent_global_reconciliation() {
let err = __golem_tool_descriptor_for_MismatchedParentGlobalConflictParent(
&mut ToolBuildCtx::new(),
)
.expect_err("a subtree root whose name differs from the parent command must be rejected");
assert!(
matches!(
err,
ToolBuildError::SubtreeRootNameMismatch { ref expected, ref actual }
if expected == "renamed" && actual == "mismatched-parent-global-conflict-child"
),
"expected SubtreeRootNameMismatch to win before parent-global reconciliation, got {err:?}",
);
}
#[tool_definition]
trait AliasDeprojectedNestedLeaf {
#[arg(format = "option")]
fn alias_deprojected_nested_leaf(&self, format: String) -> Result<(), RemoteError>;
}
struct AliasDeprojectedNestedLeafSubtree;
#[tool_definition]
trait AliasDeprojectedNestedMiddle {
#[command(subtree = AliasDeprojectedNestedLeaf)]
fn alias_deprojected_nested_leaf(&self) -> AliasDeprojectedNestedLeafSubtree;
}
struct AliasDeprojectedNestedMiddleSubtree;
#[tool_definition]
trait AliasDeprojectedNestedParent {
#[arg(count = "global")]
fn alias_deprojected_nested_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = AliasDeprojectedNestedMiddle)]
#[arg(format = "global", aliases = ["count"])]
fn alias_deprojected_nested_middle(
&self,
format: u32,
) -> AliasDeprojectedNestedMiddleSubtree;
}
#[test]
fn deprojected_subtree_global_alias_does_not_shadow_nested_root_body() {
let tool = __golem_tool_descriptor_for_AliasDeprojectedNestedParent(&mut ToolBuildCtx::new())
.expect(
"a subtree global de-projected through an alias must not remain effective under nested grafts",
);
let middle_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "alias-deprojected-nested-middle")
.expect("middle subtree is grafted") as usize;
assert!(
!tool.commands[middle_idx]
.globals
.options
.iter()
.any(|o| o.long == "format"),
"the alias-compatible subtree global is de-projected and must not survive as `format`",
);
let leaf_idx = tool.commands[middle_idx]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "alias-deprojected-nested-leaf")
.expect("leaf subtree is grafted") as usize;
let leaf_body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
leaf_body.options.iter().any(|o| o.long == "format"),
"the nested leaf's local `format` option must remain because no effective inherited global named `format` survives",
);
}
#[tool_definition]
trait AliasDeprojectedRootGlobalNestedLeaf {
#[arg(format = "option")]
fn alias_deprojected_root_global_nested_leaf(
&self,
format: String,
) -> Result<(), RemoteError>;
}
struct AliasDeprojectedRootGlobalNestedLeafSubtree;
#[tool_definition]
trait AliasDeprojectedRootGlobalNestedMiddle {
#[arg(format = "global", aliases = ["count"])]
fn alias_deprojected_root_global_nested_middle(
&self,
format: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = AliasDeprojectedRootGlobalNestedLeaf)]
fn alias_deprojected_root_global_nested_leaf(
&self,
) -> AliasDeprojectedRootGlobalNestedLeafSubtree;
}
struct AliasDeprojectedRootGlobalNestedMiddleSubtree;
#[tool_definition]
trait AliasDeprojectedRootGlobalNestedParent {
#[arg(count = "global")]
fn alias_deprojected_root_global_nested_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = AliasDeprojectedRootGlobalNestedMiddle)]
fn alias_deprojected_root_global_nested_middle(
&self,
) -> AliasDeprojectedRootGlobalNestedMiddleSubtree;
}
#[test]
fn deprojected_child_root_global_alias_does_not_shadow_nested_root_body() {
let tool = __golem_tool_descriptor_for_AliasDeprojectedRootGlobalNestedParent(
&mut ToolBuildCtx::new(),
)
.expect(
"a child root global de-projected through an alias must not remain effective under nested grafts",
);
let middle_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| {
tool.commands[idx as usize].name == "alias-deprojected-root-global-nested-middle"
})
.expect("middle subtree is grafted") as usize;
assert!(
!tool.commands[middle_idx]
.globals
.options
.iter()
.any(|o| o.long == "format"),
"the alias-compatible child root global is de-projected and must not survive as `format`",
);
let leaf_idx = tool.commands[middle_idx]
.subcommands
.iter()
.copied()
.find(|&idx| {
tool.commands[idx as usize].name == "alias-deprojected-root-global-nested-leaf"
})
.expect("leaf subtree is grafted") as usize;
let leaf_body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
leaf_body.options.iter().any(|o| o.long == "format"),
"the nested leaf's local `format` option must remain because no effective inherited global named `format` survives",
);
}
#[tool_definition]
trait AliasDeprojectedRootGlobalTailChild {
#[arg(format = "global", aliases = ["count"])]
fn alias_deprojected_root_global_tail_child(&self, format: u32) -> Result<(), RemoteError>;
fn leaf(&self, items: Vec<String>, format: u32) -> Result<(), RemoteError>;
}
struct AliasDeprojectedRootGlobalTailSubtree;
#[tool_definition]
trait AliasDeprojectedRootGlobalTailParent {
#[arg(count = "global")]
fn alias_deprojected_root_global_tail_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = AliasDeprojectedRootGlobalTailChild)]
fn alias_deprojected_root_global_tail_child(&self)
-> AliasDeprojectedRootGlobalTailSubtree;
}
#[test]
fn deprojected_child_root_global_alias_does_not_affect_leaf_tail_inference() {
let tool = __golem_tool_descriptor_for_AliasDeprojectedRootGlobalTailParent(
&mut ToolBuildCtx::new(),
)
.expect("descriptor builds");
let child_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| {
tool.commands[idx as usize].name == "alias-deprojected-root-global-tail-child"
})
.expect("child subtree is grafted") as usize;
assert!(
!tool.commands[child_idx]
.globals
.options
.iter()
.any(|o| o.long == "format"),
"the child root global `format` is de-projected through alias `count` and must not survive",
);
let leaf_idx = tool.commands[child_idx]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "leaf")
.expect("leaf command is present") as usize;
let body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
body.positionals.fixed.iter().any(|p| p.name == "format"),
"leaf-local `format` remains because no effective inherited global named `format` survives",
);
assert!(
body.positionals.tail.is_none(),
"`items: Vec<_>` is followed by surviving local `format`, so it is not in tail position",
);
assert!(
body.options.iter().any(|o| {
o.long == "items" && matches!(o.shape, ExtendedOptionShape::RepeatableList(_))
}),
"a non-tail Vec parameter projects as a repeatable-list option",
);
}
#[tool_definition]
trait DemotedTailOptionOrderChild {
#[arg(format = "global", aliases = ["count"])]
fn demoted_tail_option_order_child(
&self,
format: u32,
target: String,
) -> Result<(), RemoteError>;
#[arg(mode = "option")]
fn leaf(&self, items: Vec<String>, mode: String, format: u32) -> Result<(), RemoteError>;
}
struct DemotedTailOptionOrderSubtree;
#[tool_definition]
trait DemotedTailOptionOrderParent {
#[arg(count = "global")]
fn demoted_tail_option_order_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = DemotedTailOptionOrderChild)]
fn demoted_tail_option_order_child(&self) -> DemotedTailOptionOrderSubtree;
}
#[test]
fn demoted_tail_option_is_reinserted_in_declaration_order() {
let tool =
__golem_tool_descriptor_for_DemotedTailOptionOrderParent(&mut ToolBuildCtx::new())
.expect("descriptor builds");
let leaf = tool
.commands
.iter()
.find(|command| command.name == "leaf")
.expect("leaf command");
let body = leaf.body.as_ref().expect("leaf body");
assert!(
body.positionals.tail.is_none(),
"items must be demoted because leaf-local format survives",
);
let option_names: Vec<&str> = body
.options
.iter()
.map(|option| option.long.as_str())
.collect();
assert_eq!(
option_names,
vec!["items", "mode"],
"demoting items from tail to repeatable-list option should preserve declaration order among body options",
);
}
#[tool_definition]
trait DemotedTailMinZeroChild {
#[arg(format = "global", aliases = ["count"])]
fn demoted_tail_min_zero_child(
&self,
format: u32,
target: String,
) -> Result<(), RemoteError>;
#[arg(items, min = 0)]
fn leaf(&self, items: Vec<String>, format: u32) -> Result<(), RemoteError>;
}
struct DemotedTailMinZeroSubtree;
#[tool_definition]
trait DemotedTailMinZeroParent {
#[arg(count = "global")]
fn demoted_tail_min_zero_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = DemotedTailMinZeroChild)]
fn demoted_tail_min_zero_child(&self) -> DemotedTailMinZeroSubtree;
}
#[test]
fn demoted_tail_with_authored_min_zero_is_rejected() {
let err = __golem_tool_descriptor_for_DemotedTailMinZeroParent(&mut ToolBuildCtx::new())
.expect_err("authored min/max cannot be reinterpreted when demoting a tail");
assert!(
matches!(err, ToolBuildError::VecSurfaceConflict { ref name, .. } if name == "items"),
"expected VecSurfaceConflict for demoted items with authored min = 0, got {err:?}",
);
}
#[tool_definition]
trait ExplicitTailBeforeSurvivingLocalChild {
#[arg(format = "global", aliases = ["count"])]
fn explicit_tail_before_surviving_local_child(
&self,
format: u32,
target: String,
) -> Result<(), RemoteError>;
#[arg(items = "tail")]
fn leaf(&self, items: Vec<String>, format: u32) -> Result<(), RemoteError>;
}
struct ExplicitTailBeforeSurvivingLocalSubtree;
#[tool_definition]
trait ExplicitTailBeforeSurvivingLocalParent {
#[arg(count = "global")]
fn explicit_tail_before_surviving_local_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = ExplicitTailBeforeSurvivingLocalChild)]
fn explicit_tail_before_surviving_local_child(
&self,
) -> ExplicitTailBeforeSurvivingLocalSubtree;
}
#[test]
fn deprojected_child_root_global_alias_revalidates_explicit_tail_order() {
let result = __golem_tool_descriptor_for_ExplicitTailBeforeSurvivingLocalParent(
&mut ToolBuildCtx::new(),
);
let Ok(tool) = result else {
return;
};
assert!(
tool.try_to_tool().is_err(),
"once child root global `format` is de-projected via alias `count`, leaf-local `format` survives; explicitly-authored tail `items` before that fixed positional must be rejected",
);
}
#[tool_definition]
trait ExplicitInheritedTailSurrogateChild {
#[arg(items = "global", aliases = ["count"])]
fn explicit_inherited_tail_surrogate_child(
&self,
items: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
#[arg(items = "tail")]
fn leaf(&self, items: Vec<String>, format: String) -> Result<(), RemoteError>;
}
struct ExplicitInheritedTailSurrogateSubtree;
#[tool_definition]
trait ExplicitInheritedTailSurrogateParent {
#[arg(count = "global")]
fn explicit_inherited_tail_surrogate_parent(
&self,
count: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = ExplicitInheritedTailSurrogateChild)]
fn explicit_inherited_tail_surrogate_child(&self) -> ExplicitInheritedTailSurrogateSubtree;
}
#[test]
fn explicit_inherited_tail_surrogate_before_surviving_positional_is_rejected() {
let result = __golem_tool_descriptor_for_ExplicitInheritedTailSurrogateParent(
&mut ToolBuildCtx::new(),
);
let err = match result {
Ok(tool) => tool.try_to_tool().expect_err(
"an explicitly-authored tail lowered through an inherited-global surrogate must still be rejected when a later fixed positional survives",
),
Err(err) => err,
};
assert!(
matches!(err, ToolBuildError::FixedPositionalAfterTail(ref name) if name == "format"),
"expected FixedPositionalAfterTail for surviving positional after explicit tail, got {err:?}",
);
}
#[tool_definition]
trait ExplicitInheritedTailSurrogateAttrsChild {
#[arg(items = "global", aliases = ["count"])]
fn explicit_inherited_tail_surrogate_attrs_child(
&self,
items: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
#[arg(items = "tail", separator = "--", accepts_stdio = true)]
fn leaf(&self, items: Vec<String>) -> Result<(), RemoteError>;
}
struct ExplicitInheritedTailSurrogateAttrsSubtree;
#[tool_definition]
trait ExplicitInheritedTailSurrogateAttrsParent {
#[arg(count = "global")]
fn explicit_inherited_tail_surrogate_attrs_parent(
&self,
count: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = ExplicitInheritedTailSurrogateAttrsChild)]
fn explicit_inherited_tail_surrogate_attrs_child(
&self,
) -> ExplicitInheritedTailSurrogateAttrsSubtree;
}
#[test]
fn promoted_explicit_inherited_tail_surrogate_preserves_tail_attrs() {
let tool = __golem_tool_descriptor_for_ExplicitInheritedTailSurrogateAttrsParent(
&mut ToolBuildCtx::new(),
)
.expect("descriptor builds");
let leaf = tool
.commands
.iter()
.find(|command| command.name == "leaf")
.expect("leaf command");
let tail = leaf
.body
.as_ref()
.and_then(|body| body.positionals.tail.as_ref())
.expect("items survives as the leaf tail");
assert_eq!(tail.name, "items");
assert_eq!(
tail.separator.as_deref(),
Some("--"),
"a surviving explicit tail surrogate must keep authored tail separator",
);
assert!(
tail.accepts_stdio,
"a surviving explicit tail surrogate must keep authored accepts_stdio",
);
tool.try_to_tool().expect("tool is valid");
}
#[tool_definition]
trait ExplicitInheritedTailSurrogateVerbatimChild {
#[arg(items = "global", aliases = ["count"])]
fn explicit_inherited_tail_surrogate_verbatim_child(
&self,
items: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
#[arg(items = "tail", verbatim = true)]
fn leaf(&self, items: Vec<String>) -> Result<(), RemoteError>;
}
struct ExplicitInheritedTailSurrogateVerbatimSubtree;
#[tool_definition]
trait ExplicitInheritedTailSurrogateVerbatimParent {
#[arg(count = "global")]
fn explicit_inherited_tail_surrogate_verbatim_parent(
&self,
count: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = ExplicitInheritedTailSurrogateVerbatimChild)]
fn explicit_inherited_tail_surrogate_verbatim_child(
&self,
) -> ExplicitInheritedTailSurrogateVerbatimSubtree;
}
#[test]
fn promoted_explicit_inherited_tail_surrogate_rejects_verbatim_without_separator() {
let result = __golem_tool_descriptor_for_ExplicitInheritedTailSurrogateVerbatimParent(
&mut ToolBuildCtx::new(),
);
let err = match result {
Ok(tool) => tool.try_to_tool().expect_err(
"verbatim=true without a separator must remain invalid when an explicit inherited tail surrogate is promoted",
),
Err(err) => err,
};
assert!(
matches!(err, ToolBuildError::VerbatimWithoutSeparator(ref name) if name == "items"),
"expected VerbatimWithoutSeparator for promoted explicit tail `items`, got {err:?}",
);
}
#[tool_definition]
trait AliasDeprojectedAncestorTailChild {
#[arg(format = "global", aliases = ["count"])]
fn alias_deprojected_ancestor_tail_child(
&self,
format: u32,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, items: Vec<String>, count: u32) -> Result<(), RemoteError>;
}
struct AliasDeprojectedAncestorTailSubtree;
#[tool_definition]
trait AliasDeprojectedAncestorTailParent {
#[arg(count = "global")]
fn alias_deprojected_ancestor_tail_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = AliasDeprojectedAncestorTailChild)]
fn alias_deprojected_ancestor_tail_child(&self) -> AliasDeprojectedAncestorTailSubtree;
}
#[test]
fn deprojected_child_root_global_alias_keeps_strict_ancestor_for_leaf_tail_inference() {
let tool = __golem_tool_descriptor_for_AliasDeprojectedAncestorTailParent(
&mut ToolBuildCtx::new(),
)
.expect("descriptor builds");
let child_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| {
tool.commands[idx as usize].name == "alias-deprojected-ancestor-tail-child"
})
.expect("child subtree is grafted") as usize;
assert!(
!tool.commands[child_idx]
.globals
.options
.iter()
.any(|o| o.long == "format"),
"the child root global `format` is de-projected through alias `count` and must not survive",
);
let leaf_idx = tool.commands[child_idx]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "leaf")
.expect("leaf command is present") as usize;
let body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
!body.positionals.fixed.iter().any(|p| p.name == "count")
&& !body.options.iter().any(|o| o.long == "count"),
"leaf-local `count` must be removed because strict ancestor global `count` remains effective",
);
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("items"),
"the trailing strict-ancestor global re-declaration must not prevent `items` from being inferred as the tail positional",
);
assert!(
!body.options.iter().any(|o| o.long == "items"),
"`items` should not be projected as a repeatable-list option when the only later positional is de-projected",
);
tool.try_to_tool().expect("composed tool is valid");
}
#[tool_definition]
trait PureStrictAncestorAliasTailChild {
fn leaf(&self, items: Vec<String>, format: u32) -> Result<(), RemoteError>;
}
struct PureStrictAncestorAliasTailSubtree;
#[tool_definition]
trait PureStrictAncestorAliasTailParent {
#[arg(count = "global", aliases = ["format"])]
fn pure_strict_ancestor_alias_tail_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = PureStrictAncestorAliasTailChild)]
fn pure_strict_ancestor_alias_tail_child(&self) -> PureStrictAncestorAliasTailSubtree;
}
#[test]
fn strict_ancestor_alias_deprojection_keeps_leaf_tail_inference_without_child_root_global() {
let tool =
__golem_tool_descriptor_for_PureStrictAncestorAliasTailParent(&mut ToolBuildCtx::new())
.expect("descriptor builds");
let child_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| {
tool.commands[idx as usize].name == "pure-strict-ancestor-alias-tail-child"
})
.expect("child subtree is grafted") as usize;
let leaf_idx = tool.commands[child_idx]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "leaf")
.expect("leaf command is present") as usize;
let body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
!body.positionals.fixed.iter().any(|p| p.name == "format")
&& !body.options.iter().any(|o| o.long == "format"),
"leaf-local `format` must be removed because strict ancestor global alias `format` remains effective",
);
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("items"),
"the trailing strict-ancestor alias re-declaration must not prevent `items` from being inferred as the tail positional",
);
assert!(
!body.options.iter().any(|o| o.long == "items"),
"`items` should not be projected as a repeatable-list option when the only later positional is de-projected",
);
tool.try_to_tool().expect("composed tool is valid");
}
#[tool_definition]
trait AliasDeprojectedOptionalPositionalChild {
#[arg(format = "global", aliases = ["count"])]
fn alias_deprojected_optional_positional_child(
&self,
format: u32,
target: String,
) -> Result<(), RemoteError>;
#[arg(format = "positional", required = false)]
fn leaf(&self, format: String, name: String) -> Result<(), RemoteError>;
}
struct AliasDeprojectedOptionalPositionalSubtree;
#[tool_definition]
trait AliasDeprojectedOptionalPositionalParent {
#[arg(count = "global")]
fn alias_deprojected_optional_positional_parent(
&self,
count: u32,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = AliasDeprojectedOptionalPositionalChild)]
fn alias_deprojected_optional_positional_child(
&self,
) -> AliasDeprojectedOptionalPositionalSubtree;
}
#[test]
fn deprojected_child_root_global_alias_does_not_hide_optional_before_required() {
let result = __golem_tool_descriptor_for_AliasDeprojectedOptionalPositionalParent(
&mut ToolBuildCtx::new(),
);
let Ok(tool) = result else {
return;
};
assert!(
tool.try_to_tool().is_err(),
"an optional fixed positional before a required fixed positional must be rejected either while building the descriptor or during runtime validation after de-projection leaves it local",
);
}
#[tool_definition]
trait Fetcher {
#[arg(items = "tail")]
#[arg(format = "option")]
fn collect(&self, items: Vec<String>, format: String) -> Result<(), RemoteError>;
}
struct FetcherSubtree;
#[tool_definition]
trait Downloader {
#[command(subtree = Fetcher)]
fn fetcher(&self, format: String) -> FetcherSubtree;
}
#[test]
fn explicit_child_tail_survives_subtree_global_hoisting() {
let tool = __golem_tool_descriptor_for_Downloader(&mut ToolBuildCtx::new())
.expect("downloader subtree descriptor builds");
let collect = tool
.commands
.iter()
.find(|c| c.name == "collect")
.expect("collect subcommand");
let body = collect.body.as_ref().expect("collect body");
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("items"),
"the explicit child tail must survive composition under the hoisted global"
);
assert!(
!body.options.iter().any(|o| o.long == "format"),
"the child `format` option must be reconciled away under the inherited `format` global"
);
tool.try_to_tool().expect("downloader tool is valid");
}
#[tool_definition]
trait TailWithInheritedTrailingGlobal {
#[arg(format = "global")]
fn tail_with_inherited_trailing_global(
&self,
format: String,
target: String,
) -> Result<(), RemoteError>;
fn collect(&self, items: Vec<String>, format: String) -> Result<(), RemoteError>;
}
#[test]
fn tail_inference_survives_trailing_inherited_global() {
let tool =
__golem_tool_descriptor_for_TailWithInheritedTrailingGlobal(&mut ToolBuildCtx::new())
.expect("descriptor builds");
let collect = tool
.commands
.iter()
.find(|c| c.name == "collect")
.expect("collect command");
let body = collect.body.as_ref().expect("collect body");
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("items"),
"a trailing parameter repeating an inherited global must not prevent tail inference"
);
assert!(
!body.positionals.fixed.iter().any(|p| p.name == "format")
&& !body.options.iter().any(|o| o.long == "format"),
"the inherited `format` re-declaration must be removed from the collect body"
);
tool.try_to_tool().expect("tool is valid");
}
#[test]
fn inferred_tail_after_inherited_trailing_global_accepts_tail_separator() {
let output = cargo_check_tool_crate(
"inferred-tail-separator-after-inherited-global",
r#"
use golem_rust::{tool_definition, ToolError};
#[derive(ToolError)]
enum E {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(format = "global")]
fn good_tool(&self, format: String, target: String) -> Result<(), E>;
#[arg(items, separator = "--")]
fn collect(&self, items: Vec<String>, format: String) -> Result<(), E>;
}
"#,
);
assert!(
output.status.success(),
"after inherited `format` is de-projected, `items` is the inferred tail; `separator` is a valid tail attr and should be consumed by the final tail shape, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn inferred_tail_after_inherited_trailing_global_accepts_tail_min() {
let output = cargo_check_tool_crate(
"inferred-tail-min-after-inherited-global",
r#"
use golem_rust::{tool_definition, ToolError};
#[derive(ToolError)]
enum E {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(format = "global")]
fn good_tool(&self, format: String, target: String) -> Result<(), E>;
#[arg(items, min = 1)]
fn collect(&self, items: Vec<String>, format: String) -> Result<(), E>;
}
"#,
);
assert!(
output.status.success(),
"after inherited `format` is de-projected, `items` is the inferred tail; `min` is a valid tail occurrence bound and should be consumed by the final tail shape, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn non_tail_vec_option_signed_min_does_not_typecheck_unused_tail_form() {
let output = cargo_check_tool_crate(
"vec-option-signed-min-unused-tail",
r#"
use golem_rust::{tool_definition, ToolError};
#[derive(ToolError)]
enum E {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(nums, min = -5_i64)]
fn run(&self, nums: Vec<i64>, suffix: String) -> Result<(), E>;
}
"#,
);
assert!(
output.status.success(),
"a non-tail Vec<i64> should treat min as an item numeric bound; the unused tail form must not typecheck it as u32\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn inferred_tail_custom_item_occurrence_min_does_not_typecheck_unused_option_form() {
let output = cargo_check_tool_crate(
"inferred-tail-custom-item-min-unused-option",
r#"
use golem_rust::{tool_definition, FromSchema, FromWire, IntoSchema, IntoWire, ToolError, WireSchema};
#[derive(Clone, IntoSchema, FromSchema, WireSchema, IntoWire, FromWire)]
struct Item {
value: String,
}
#[derive(ToolError)]
enum E {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(items, min = 1)]
fn run(&self, items: Vec<Item>) -> Result<(), E>;
}
"#,
);
assert!(
output.status.success(),
"an inferred tail over a custom item should treat min as an occurrence bound; the unused option form must not typecheck it as Item\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait TailBeforeGlobal {
#[arg(format = "global")]
fn collect(&self, items: Vec<String>, format: String) -> Result<(), RemoteError>;
}
#[test]
fn trailing_global_argument_does_not_prevent_vec_tail_inference() {
let tool = __golem_tool_descriptor_for_TailBeforeGlobal(&mut ToolBuildCtx::new())
.expect("tail-before-global descriptor builds");
let collect = tool
.commands
.iter()
.find(|c| c.name == "collect")
.expect("collect command");
let body = collect.body.as_ref().expect("collect body");
assert!(
collect.globals.options.iter().any(|o| o.long == "format"),
"the trailing scalar global should project to a global option"
);
assert!(
body.options.iter().all(|o| o.long != "items"),
"the final non-global Vec<T> parameter should not be projected as a repeatable option"
);
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("items"),
"a global parameter after the Vec<T> should not prevent tail positional inference"
);
}
#[tool_definition]
trait TailInference {
fn collect(&self, prefix: String, items: Vec<String>) -> Result<(), RemoteError>;
}
struct TailInferenceImpl;
#[tool_implementation]
impl TailInference for TailInferenceImpl {
fn collect(&self, _prefix: String, _items: Vec<String>) -> Result<(), RemoteError> {
Ok(())
}
}
#[test]
fn vec_parameter_in_tail_position_is_tail_positional_without_attribute() {
let tool = __golem_tool_descriptor_for_TailInference(&mut ToolBuildCtx::new())
.expect("tail inference descriptor builds");
let collect = tool
.commands
.iter()
.find(|c| c.name == "collect")
.expect("collect command");
let body = collect.body.as_ref().expect("collect body");
assert!(
body.options.iter().all(|o| o.long != "items"),
"a trailing Vec<T> parameter should not be projected as a repeatable option"
);
assert_eq!(
body.positionals.tail.as_ref().map(|t| t.name.as_str()),
Some("items"),
"a trailing Vec<T> parameter should be projected as the tail positional"
);
}
#[tool_definition]
trait EmptyMapDefault {
#[arg(env = "option", default = [])]
fn set_env(&self, env: BTreeMap<String, String>) -> Result<(), RemoteError>;
}
#[test]
fn empty_array_default_for_map_option_builds_empty_map() {
let tool = __golem_tool_descriptor_for_EmptyMapDefault(&mut ToolBuildCtx::new())
.expect("an empty array default on a map option should build an empty map default");
let set_env = tool
.commands
.iter()
.find(|c| c.name == "set-env")
.expect("set-env command");
let body = set_env.body.as_ref().expect("set-env body");
let env = body
.options
.iter()
.find(|o| o.long == "env")
.expect("env option");
assert!(
env.default.is_some(),
"map option default should be present"
);
tool.try_to_tool()
.expect("tool with empty map default should be valid");
}
#[test]
fn non_bool_flag_parameter_is_rejected() {
let output = cargo_check_tool_crate(
"non-bool-flag",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(level = "flag")]
fn run(&self, level: u32) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _level: u32) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a bool flag projected from a u32 parameter should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn count_flag_parameter_must_be_u32() {
let output = cargo_check_tool_crate(
"count-flag-u64",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(verbose = "flag", kind = "count-flag")]
fn run(&self, verbose: u64) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _verbose: u64) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a count flag is exposed as a u32 input field, so a u64 implementation parameter should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn count_flag_parameter_must_not_be_optional_u32() {
let output = cargo_check_tool_crate(
"count-flag-option-u32",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(verbose = "flag", kind = "count-flag")]
fn run(&self, verbose: Option<u32>) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _verbose: Option<u32>) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a count flag is exposed as a u32 input field, so an Option<u32> implementation parameter should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn explicit_tail_parameter_before_fixed_positional_is_rejected() {
let output = cargo_test_tool_crate(
"tail-before-fixed",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(items = "tail")]
fn run(&self, items: Vec<String>, suffix: String) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _items: Vec<String>, _suffix: String) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"an explicit tail positional before a later fixed positional should be rejected, but cargo test succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn optional_vec_tail_is_rejected_instead_of_dropping_optionality() {
let output = cargo_check_tool_crate(
"optional-vec-tail",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(items = "tail")]
fn run(&self, items: Option<Vec<String>>) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _items: Option<Vec<String>>) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"an Option<Vec<T>> tail positional cannot be represented in ExtendedTailPositional and must be rejected instead of silently dropping optionality, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn scalar_global_argument_projects_to_global_option() {
let output = cargo_check_tool_crate(
"scalar-global-option",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(format = "global")]
fn bad_tool(&self, format: String) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn bad_tool(&self, _format: String) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
output.status.success(),
"an explicitly global scalar argument should project to a global option, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn many_root_globals_with_leaf_command_compile() {
let global_count = usize::BITS as usize;
let mut source = String::from("use golem_rust::tool_definition;\n\n");
source.push_str("#[tool_definition]\ntrait ManyGlobals {\n");
for i in 0..global_count {
source.push_str(&format!(" #[arg(g{i} = \"global\")]\n"));
}
source.push_str(" fn many_globals(\n &self,\n");
for i in 0..global_count {
source.push_str(&format!(" g{i}: String,\n"));
}
source.push_str(" target: String,\n );\n");
source.push_str(" fn leaf(&self, value: String);\n}\n");
let output = cargo_check_tool_crate("many-root-globals", &source);
assert!(
output.status.success(),
"a tool definition should not panic or fail to compile merely because it has {global_count} root globals and a leaf command, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait ManyContextSensitiveLeafPositionals {
fn leaf(
&self,
v0: Vec<String>,
v1: Vec<String>,
v2: Vec<String>,
v3: Vec<String>,
v4: Vec<String>,
v5: Vec<String>,
v6: Vec<String>,
v7: Vec<String>,
v8: Vec<String>,
v9: Vec<String>,
v10: Vec<String>,
v11: Vec<String>,
v12: Vec<String>,
) -> Result<(), RemoteError>;
}
#[test]
fn many_context_sensitive_leaf_positionals_build_without_inherited_globals() {
let tool = __golem_tool_descriptor_for_ManyContextSensitiveLeafPositionals(
&mut ToolBuildCtx::new(),
)
.expect(
"a standalone leaf with many Vec positionals and no inherited globals should use normal option/tail projection",
);
let leaf = tool
.commands
.iter()
.find(|command| command.name == "leaf")
.expect("leaf command exists");
let body = leaf.body.as_ref().expect("leaf body");
assert_eq!(
body.positionals
.tail
.as_ref()
.map(|tail| tail.name.as_str()),
Some("v12"),
"the final Vec parameter should infer as the tail positional",
);
for i in 0..12 {
let name = format!("v{i}");
assert!(
body.options.iter().any(|option| {
option.long == name
&& matches!(option.shape, ExtendedOptionShape::RepeatableList(_))
}),
"non-final Vec parameter {name} should project as a repeatable-list option",
);
}
tool.try_to_tool().expect("standalone tool is valid");
}
#[test]
fn many_context_sensitive_leaf_positionals_preserve_option_attrs_without_inherited_globals() {
let output = cargo_check_tool_crate(
"many-vec-option-attrs",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait ManyVecOptionAttrs {
#[arg(v0, short = 'a')]
fn leaf(
&self,
v0: Vec<String>,
v1: Vec<String>,
v2: Vec<String>,
v3: Vec<String>,
v4: Vec<String>,
v5: Vec<String>,
v6: Vec<String>,
v7: Vec<String>,
v8: Vec<String>,
v9: Vec<String>,
v10: Vec<String>,
v11: Vec<String>,
v12: Vec<String>,
);
}
"#,
);
assert!(
output.status.success(),
"with no inherited globals, only the final Vec is a tail; earlier Vec parameters are repeatable-list options and option attributes such as `short` must be accepted, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait SparseInheritedManyVecChild {
fn leaf(
&self,
v0: Vec<String>,
v1: Vec<String>,
v2: Vec<String>,
v3: Vec<String>,
v4: Vec<String>,
v5: Vec<String>,
v6: Vec<String>,
v7: Vec<String>,
v8: Vec<String>,
v9: Vec<String>,
v10: Vec<String>,
v11: Vec<String>,
v12: Vec<String>,
) -> Result<(), RemoteError>;
}
struct SparseInheritedManyVecSubtree;
#[tool_definition]
trait SparseInheritedManyVecParent {
#[arg(v0 = "global")]
#[command(subtree = SparseInheritedManyVecChild)]
fn sparse_inherited_many_vec_child(&self, v0: Vec<String>)
-> SparseInheritedManyVecSubtree;
}
#[test]
fn many_context_sensitive_leaf_positionals_allow_sparse_inherited_subset() {
let tool =
__golem_tool_descriptor_for_SparseInheritedManyVecParent(&mut ToolBuildCtx::new())
.expect("only v0 is inherited; the remaining Vec parameters have a valid shape");
let child_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "sparse-inherited-many-vec-child")
.expect("child subtree is grafted") as usize;
let leaf_idx = tool.commands[child_idx]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "leaf")
.expect("leaf command is present") as usize;
let body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
!body.options.iter().any(|option| option.long == "v0"),
"v0 is de-projected onto the inherited global",
);
assert_eq!(
body.positionals
.tail
.as_ref()
.map(|tail| tail.name.as_str()),
Some("v12"),
"the final non-inherited Vec parameter remains the tail positional",
);
tool.try_to_tool().expect("composed tool is valid");
}
#[tool_definition]
trait SparseInheritedTrailingManyVecChild {
fn leaf(
&self,
v0: Vec<String>,
v1: Vec<String>,
v2: Vec<String>,
v3: Vec<String>,
v4: Vec<String>,
v5: Vec<String>,
v6: Vec<String>,
v7: Vec<String>,
v8: Vec<String>,
v9: Vec<String>,
v10: Vec<String>,
v11: Vec<String>,
v12: Vec<String>,
) -> Result<(), RemoteError>;
}
struct SparseInheritedTrailingManyVecSubtree;
#[tool_definition]
trait SparseInheritedTrailingManyVecParent {
#[arg(v12 = "global")]
#[command(subtree = SparseInheritedTrailingManyVecChild)]
fn sparse_inherited_trailing_many_vec_child(
&self,
v12: Vec<String>,
) -> SparseInheritedTrailingManyVecSubtree;
}
#[test]
fn many_context_sensitive_leaf_positionals_allow_sparse_trailing_inherited_subset() {
let tool = __golem_tool_descriptor_for_SparseInheritedTrailingManyVecParent(
&mut ToolBuildCtx::new(),
)
.expect("only v12 is inherited; v11 should become the remaining tail positional");
let child_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| {
tool.commands[idx as usize].name == "sparse-inherited-trailing-many-vec-child"
})
.expect("child subtree is grafted") as usize;
let leaf_idx = tool.commands[child_idx]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "leaf")
.expect("leaf command is present") as usize;
let body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
!body.options.iter().any(|option| option.long == "v12")
&& body
.positionals
.tail
.as_ref()
.map(|tail| tail.name.as_str())
!= Some("v12"),
"v12 is de-projected onto the inherited global",
);
assert_eq!(
body.positionals
.tail
.as_ref()
.map(|tail| tail.name.as_str()),
Some("v11"),
"after the trailing inherited Vec is de-projected, the preceding Vec parameter must be inferred as the tail positional",
);
tool.try_to_tool().expect("composed tool is valid");
}
#[tool_definition]
trait MixedRootAndStrictSparseManyVecChild {
#[arg(v0 = "global")]
fn mixed_root_and_strict_sparse_many_vec_child(
&self,
v0: Vec<String>,
target: String,
) -> Result<(), RemoteError>;
fn leaf(
&self,
v0: Vec<String>,
v1: Vec<String>,
v2: Vec<String>,
v3: Vec<String>,
v4: Vec<String>,
v5: Vec<String>,
v6: Vec<String>,
v7: Vec<String>,
v8: Vec<String>,
v9: Vec<String>,
v10: Vec<String>,
v11: Vec<String>,
v12: Vec<String>,
) -> Result<(), RemoteError>;
}
struct MixedRootAndStrictSparseManyVecSubtree;
#[tool_definition]
trait MixedRootAndStrictSparseManyVecParent {
#[arg(v12 = "global")]
#[command(subtree = MixedRootAndStrictSparseManyVecChild)]
fn mixed_root_and_strict_sparse_many_vec_child(
&self,
v12: Vec<String>,
) -> MixedRootAndStrictSparseManyVecSubtree;
}
#[test]
fn many_context_sensitive_leaf_positionals_allow_mixed_root_and_sparse_strict_subset() {
let tool = __golem_tool_descriptor_for_MixedRootAndStrictSparseManyVecParent(
&mut ToolBuildCtx::new(),
)
.expect(
"a bounded root global plus one sparse strict inherited global still leaves a valid leaf shape",
);
let child_idx = tool.commands[0]
.subcommands
.iter()
.copied()
.find(|&idx| {
tool.commands[idx as usize].name == "mixed-root-and-strict-sparse-many-vec-child"
})
.expect("child subtree is grafted") as usize;
let leaf_idx = tool.commands[child_idx]
.subcommands
.iter()
.copied()
.find(|&idx| tool.commands[idx as usize].name == "leaf")
.expect("leaf command is present") as usize;
let body = tool.commands[leaf_idx].body.as_ref().expect("leaf body");
assert!(
!body.options.iter().any(|option| option.long == "v12")
&& body
.positionals
.tail
.as_ref()
.map(|tail| tail.name.as_str())
!= Some("v12"),
"v12 is de-projected onto the strict inherited global",
);
assert_eq!(
body.positionals
.tail
.as_ref()
.map(|tail| tail.name.as_str()),
Some("v11"),
"after the sparse strict inherited Vec is de-projected, v11 must be inferred as the tail positional even when another context-sensitive root global exists",
);
tool.try_to_tool().expect("composed tool is valid");
}
#[tool_definition]
trait ManyOverlappingGraftedRootGlobalsChild {
#[arg(g0 = "global")]
#[arg(g1 = "global")]
#[arg(g2 = "global")]
#[arg(g3 = "global")]
#[arg(g4 = "global")]
#[arg(g5 = "global")]
#[arg(g6 = "global")]
#[arg(g7 = "global")]
#[arg(g8 = "global")]
#[arg(g9 = "global")]
#[arg(g10 = "global")]
#[arg(g11 = "global")]
#[arg(g12 = "global")]
fn many_overlapping_grafted_root_globals_child(
&self,
g0: String,
g1: String,
g2: String,
g3: String,
g4: String,
g5: String,
g6: String,
g7: String,
g8: String,
g9: String,
g10: String,
g11: String,
g12: String,
target: String,
) -> Result<(), RemoteError>;
fn leaf(
&self,
g0: String,
g1: String,
g2: String,
g3: String,
g4: String,
g5: String,
g6: String,
g7: String,
g8: String,
g9: String,
g10: String,
g11: String,
g12: String,
value: String,
) -> Result<(), RemoteError>;
}
struct ManyOverlappingGraftedRootGlobalsSubtree;
#[tool_definition]
trait ManyOverlappingGraftedRootGlobalsParent {
#[arg(g0 = "global")]
fn many_overlapping_grafted_root_globals_parent(
&self,
g0: String,
target: String,
) -> Result<(), RemoteError>;
#[command(subtree = ManyOverlappingGraftedRootGlobalsChild)]
fn many_overlapping_grafted_root_globals_child(
&self,
) -> ManyOverlappingGraftedRootGlobalsSubtree;
}
#[test]
fn many_overlapping_grafted_root_globals_build_when_one_is_deprojected() {
let tool = __golem_tool_descriptor_for_ManyOverlappingGraftedRootGlobalsParent(
&mut ToolBuildCtx::new(),
)
.expect(
"a valid grafted child with many overlapping root globals should build when one child root global is de-projected",
);
let child = tool
.commands
.iter()
.find(|command| command.name == "many-overlapping-grafted-root-globals-child")
.expect("child subtree root is grafted");
assert!(
!child
.globals
.options
.iter()
.any(|option| option.long == "g0"),
"the child root's duplicate g0 global is de-projected onto the parent root global"
);
tool.try_to_tool().expect("composed tool is valid");
}
#[test]
fn optional_fixed_positional_before_required_fixed_positional_is_rejected() {
let output = cargo_test_tool_crate(
"optional-before-required-positional",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
fn run(&self, maybe_prefix: Option<String>, required_name: String) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(
&self,
_maybe_prefix: Option<String>,
_required_name: String,
) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"an optional fixed positional before a required fixed positional should be rejected, but cargo test succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn text_refinement_on_non_text_parameter_is_rejected() {
let output = cargo_check_tool_crate(
"text-refinement-on-non-text",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(count = "positional", regex = "^[0-9]+$")]
fn run(&self, count: u32) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _count: u32) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a text refinement on a non-text Rust parameter should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn tail_min_max_are_occurrence_bounds_not_item_numeric_bounds() {
let output = cargo_check_tool_crate(
"tail-min-max-string-items",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(items = "tail", min = 1, max = 3)]
fn run(&self, items: Vec<String>) -> Result<(), BadError>;
}
struct GoodToolImpl;
#[tool_implementation]
impl GoodTool for GoodToolImpl {
fn run(&self, _items: Vec<String>) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
output.status.success(),
"tail min/max should constrain occurrence count only, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait NonFiniteNumericBound {
#[arg(value = "option", min = f64::NAN)]
fn run(&self, value: f64) -> Result<(), RemoteError>;
}
#[test]
fn non_finite_numeric_bound_returns_tool_build_error_instead_of_panicking() {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
__golem_tool_descriptor_for_NonFiniteNumericBound(&mut ToolBuildCtx::new())
}));
assert!(
result.is_ok(),
"descriptor build should return a ToolBuildError for a non-finite numeric bound, not panic"
);
assert!(
result.expect("panic checked above").is_err(),
"a non-finite numeric bound should be rejected"
);
}
#[test]
fn usize_option_numeric_bounds_compile() {
let output = cargo_check_tool_crate(
"usize-option-numeric-bounds",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(count = "option", min = 1, max = 3)]
fn run(&self, count: usize) -> Result<(), BadError>;
}
struct GoodToolImpl;
#[tool_implementation]
impl GoodTool for GoodToolImpl {
fn run(&self, _count: usize) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
output.status.success(),
"usize is a supported schema type, so numeric bounds on a usize option should compile\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait ValueIsRefinedLocalOption {
#[arg(format = "option", regex = "^(json|yaml)$")]
#[constraint(requires_all = value_is("format", "json"))]
fn run(&self, format: String) -> Result<(), RemoteError>;
}
#[test]
fn value_is_constraint_uses_refined_local_option_schema() {
let tool = __golem_tool_descriptor_for_ValueIsRefinedLocalOption(&mut ToolBuildCtx::new())
.expect("descriptor builds");
tool.try_to_tool().expect(
"value_is on a refined option should validate against the refined option schema",
);
}
type TagsAlias = Vec<String>;
#[tool_definition]
trait ValueIsListAliasOption {
#[arg(tags = "option")]
#[constraint(requires_all = value_is("tags", "prod"))]
fn run(&self, tags: TagsAlias) -> Result<(), RemoteError>;
}
#[test]
fn value_is_item_literal_works_for_list_alias_option() {
let tool = __golem_tool_descriptor_for_ValueIsListAliasOption(&mut ToolBuildCtx::new())
.expect("a value_is item literal should build for a list-shaped option alias");
tool.try_to_tool()
.expect("a value_is item literal should validate for a list-shaped option alias");
}
#[tool_definition]
trait DeferredValueIsChild {
#[constraint(requires_all = value_is("format", "json"))]
fn leaf(&self, name: String) -> Result<(), RemoteError>;
}
struct DeferredValueIsSubtree;
#[tool_definition]
trait DeferredValueIsParent {
#[command(subtree = DeferredValueIsChild)]
fn deferred_value_is_child(&self, format: String) -> DeferredValueIsSubtree;
}
#[test]
fn deferred_value_is_resolves_when_parent_composes_child() {
let tool = __golem_tool_descriptor_for_DeferredValueIsParent(&mut ToolBuildCtx::new())
.expect("parent composition should build the descriptor");
tool.try_to_tool()
.expect("the composed tool with a resolved deferred value_is should build");
}
#[tool_definition]
trait DeferredValueIsListAliasChild {
#[constraint(requires_all = value_is("tags", "prod"))]
fn leaf(&self, name: String) -> Result<(), RemoteError>;
}
struct DeferredValueIsListAliasSubtree;
#[tool_definition]
trait DeferredValueIsListAliasParent {
#[arg(tags = "option")]
#[command(subtree = DeferredValueIsListAliasChild)]
fn deferred_value_is_list_alias_child(
&self,
tags: TagsAlias,
) -> DeferredValueIsListAliasSubtree;
}
#[test]
fn deferred_value_is_item_literal_works_for_parent_list_alias_global() {
let tool = __golem_tool_descriptor_for_DeferredValueIsListAliasParent(
&mut ToolBuildCtx::new(),
)
.expect(
"a deferred value_is item literal should resolve for a list-shaped parent global alias",
);
tool.try_to_tool().expect(
"a resolved deferred item literal should validate for a list-shaped parent global alias",
);
}
#[test]
fn standalone_subtree_child_with_parent_only_value_is_fails_to_build() {
let tool = __golem_tool_descriptor_for_DeferredValueIsChild(&mut ToolBuildCtx::new())
.expect("standalone child descriptor builds with the literal still deferred");
let err = tool.try_to_tool().unwrap_err();
assert!(
matches!(err, ToolBuildError::UnresolvedConstraintRef(ref name) if name == "format"),
"expected an unresolved-constraint-ref error for `format`, got {err:?}",
);
}
#[tool_definition]
trait RestrictionWidenChild {
#[arg(count = "option", min = 0, max = 5)]
#[constraint(requires_all = value_is("count", 15))]
fn leaf(&self, count: u32) -> Result<(), RemoteError>;
}
struct RestrictionWidenSubtree;
#[tool_definition]
trait RestrictionWidenParent {
#[arg(count = "option", min = 10, max = 20)]
#[command(subtree = RestrictionWidenChild)]
fn restriction_widen_child(&self, count: u32) -> RestrictionWidenSubtree;
}
#[test]
fn nested_child_value_is_resolves_against_widened_parent_global_restriction() {
let tool = __golem_tool_descriptor_for_RestrictionWidenParent(&mut ToolBuildCtx::new())
.expect(
"parent composition should build: the child `value_is(\"count\", 15)` resolves \
against the parent global's 10..=20 restriction, which accepts 15",
);
tool.try_to_tool().expect(
"the composed tool must build: 15 is valid against the widened parent global restriction",
);
}
#[tool_definition]
trait TextRestrictionGlobalRedeclaration {
#[arg(format = "global", regex = "^(json|yaml)$")]
fn text_restriction_global_redeclaration(
&self,
format: String,
target: String,
) -> Result<(), RemoteError>;
fn leaf(&self, format: String, name: String) -> Result<(), RemoteError>;
}
#[test]
fn leaf_redeclaring_text_refined_string_global_is_suppressed() {
let tool =
__golem_tool_descriptor_for_TextRestrictionGlobalRedeclaration(&mut ToolBuildCtx::new())
.expect(
"a leaf re-declaring the same String argument should be compatible with an inherited text-refined global; regex is a refinable restriction",
);
let leaf = tool
.commands
.iter()
.find(|c| c.name == "leaf")
.expect("leaf command");
let body = leaf.body.as_ref().expect("leaf body");
assert!(
!body.positionals.fixed.iter().any(|p| p.name == "format"),
"the inherited text-refined `format` global must be suppressed from the leaf body"
);
tool.try_to_tool()
.expect("matching String redeclaration under a text refinement is valid");
}
#[test]
fn standalone_child_value_is_violating_local_restriction_is_rejected() {
match __golem_tool_descriptor_for_RestrictionWidenChild(&mut ToolBuildCtx::new()) {
Err(ToolBuildError::ValueIsTypeMismatch(name)) if name == "count" => {}
Err(other) => panic!("expected ValueIsTypeMismatch for `count`, got {other:?}"),
Ok(tool) => panic!(
"standalone child accepted a value_is literal that violates its local restriction; \
try_to_tool returned {:?}",
tool.try_to_tool()
),
}
}
#[test]
fn value_is_constraint_can_reference_inherited_global_option() {
let output = cargo_check_tool_crate(
"value-is-inherited-global-option",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(format = "global")]
fn good_tool(&self, format: String, target: String) -> Result<(), BadError>;
#[constraint(requires_all = value_is("format", "json"))]
fn leaf(&self, name: String) -> Result<(), BadError>;
}
struct GoodToolImpl;
#[tool_implementation]
impl GoodTool for GoodToolImpl {
fn good_tool(&self, _format: String, _target: String) -> Result<(), BadError> {
Ok(())
}
fn leaf(&self, _name: String) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
output.status.success(),
"a value_is constraint should be able to reference an inherited global option, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn value_is_constraint_can_reference_parent_subtree_global_option() {
let output = cargo_check_tool_crate(
"value-is-parent-subtree-global-option",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait ChildTool {
#[constraint(requires_all = value_is("format", "json"))]
fn leaf(&self, name: String) -> Result<(), BadError>;
}
struct ChildSubtree;
#[tool_definition]
trait ParentTool {
#[command(subtree = ChildTool)]
fn child_tool(&self, format: String) -> ChildSubtree;
}
struct ChildImpl;
#[tool_implementation]
impl ChildTool for ChildImpl {
fn leaf(&self, _name: String) -> Result<(), BadError> {
Ok(())
}
}
struct ParentImpl;
#[tool_implementation]
impl ParentTool for ParentImpl {
fn child_tool(&self, _format: String) -> ChildSubtree {
ChildSubtree
}
}
"#,
);
assert!(
output.status.success(),
"a child subtree constraint should be able to value_is a global supplied by the parent subtree method, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn subtree_dispatcher_param_explicit_positional_is_rejected() {
let output = cargo_check_tool_crate(
"subtree-dispatcher-positional-param",
r#"
use golem_rust::{tool_definition, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait ChildTool {
fn leaf(&self, name: String) -> Result<(), BadError>;
}
struct ChildToolSubtree;
#[tool_definition]
trait ParentTool {
#[arg(format = "positional")]
#[command(subtree = ChildTool)]
fn child_tool(&self, format: String) -> ChildToolSubtree;
}
"#,
);
assert!(
!output.status.success(),
"a subtree dispatcher parameter explicitly placed as positional cannot be represented on a pure dispatcher and should be rejected instead of silently becoming a global option\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn path_refinement_on_non_path_parameter_is_rejected() {
let output = cargo_check_tool_crate(
"path-refinement-on-non-path",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(count = "option", kind = "file")]
fn run(&self, count: u32) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _count: u32) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a path refinement on a non-path Rust parameter should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn numeric_unit_on_non_numeric_parameter_is_rejected() {
let output = cargo_check_tool_crate(
"numeric-unit-on-non-numeric",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(name = "option", unit = "ms")]
fn run(&self, name: String) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _name: String) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a numeric unit on a non-numeric Rust parameter should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait TailNumericUnit {
#[arg(values = "tail", unit = "ms")]
fn run(&self, values: Vec<u32>) -> Result<(), RemoteError>;
}
#[test]
fn tail_numeric_unit_on_numeric_items_is_preserved() {
let tool = __golem_tool_descriptor_for_TailNumericUnit(&mut ToolBuildCtx::new())
.expect("numeric tail descriptor builds");
let run = tool
.commands
.iter()
.find(|c| c.name == "run")
.expect("run command");
let tail = run
.body
.as_ref()
.and_then(|body| body.positionals.tail.as_ref())
.expect("tail positional");
assert_eq!(
tail.item_type
.root
.numeric_restrictions()
.and_then(|restrictions| restrictions.unit.as_deref()),
Some("ms"),
"numeric refinements on a tail positional's item type must not be silently dropped"
);
}
#[tool_definition]
trait TailItemBoundsWithOccurrenceMax {
#[arg(values = "tail", bounds = (1, 3), max = 10)]
fn run(&self, values: Vec<u32>) -> Result<(), RemoteError>;
}
#[test]
fn tail_item_bounds_coexist_with_occurrence_max() {
let tool =
__golem_tool_descriptor_for_TailItemBoundsWithOccurrenceMax(&mut ToolBuildCtx::new())
.expect("tail descriptor builds");
let run = tool
.commands
.iter()
.find(|c| c.name == "run")
.expect("run command");
let tail = run
.body
.as_ref()
.and_then(|body| body.positionals.tail.as_ref())
.expect("tail positional");
assert_eq!(tail.max, Some(10), "occurrence max should be preserved");
let restrictions = tail
.item_type
.root
.numeric_restrictions()
.expect("item carries numeric restrictions");
assert_eq!(
restrictions.min,
Some(golem_rust::schema::schema_type::NumericBound::Unsigned(1)),
"item lower bound should come from `bounds`"
);
assert_eq!(
restrictions.max,
Some(golem_rust::schema::schema_type::NumericBound::Unsigned(3)),
"item upper bound should come from `bounds`"
);
}
#[tool_definition]
trait TailOccurrenceMinGreaterThanMax {
#[arg(values = "tail", min = 5, max = 3)]
fn run(&self, values: Vec<String>) -> Result<(), RemoteError>;
}
#[test]
fn tail_occurrence_min_greater_than_max_is_rejected() {
let tool =
__golem_tool_descriptor_for_TailOccurrenceMinGreaterThanMax(&mut ToolBuildCtx::new())
.expect("descriptor should build far enough to validate tail occurrence bounds");
assert!(
tool.try_to_tool().is_err(),
"a tail positional with occurrence min greater than max is impossible and should be rejected"
);
}
#[test]
fn tail_numeric_bounds_on_non_numeric_items_are_rejected() {
let output = cargo_check_tool_crate(
"tail-numeric-bounds-on-non-numeric-items",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(values = "tail", bounds = (1, 3))]
fn run(&self, values: Vec<String>) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _values: Vec<String>) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"numeric bounds on a non-numeric tail item should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn text_refinement_on_bool_flag_is_rejected() {
let output = cargo_check_tool_crate(
"text-refinement-on-bool-flag",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(force = "flag", regex = "yes|no")]
fn run(&self, force: bool) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _force: bool) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a text refinement on a bool flag should be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn option_with_count_flag_kind_is_rejected_instead_of_reclassified_as_flag() {
let output = cargo_check_tool_crate(
"option-with-count-flag-kind",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(level = "option", kind = "count-flag")]
fn run(&self, level: u32) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _level: u32) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"an arg explicitly placed as an option must not be silently reclassified as a count flag by `kind = \"count-flag\"`, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn tail_default_is_rejected_instead_of_silently_dropped() {
let output = cargo_check_tool_crate(
"tail-default-is-rejected",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(items = "tail", default = ["a", "b"])]
fn run(&self, items: Vec<String>) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _items: Vec<String>) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a default on a tail positional cannot be represented in ExtendedTailPositional and must be rejected instead of silently dropped, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn parenthesized_bool_flag_default_is_accepted_like_other_metadata_literals() {
let output = cargo_check_tool_crate(
"parenthesized-bool-flag-default",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait GoodTool {
#[arg(force = "flag", default = (true))]
fn run(&self, force: bool) -> Result<(), BadError>;
}
struct GoodToolImpl;
#[tool_implementation]
impl GoodTool for GoodToolImpl {
fn run(&self, _force: bool) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
output.status.success(),
"parenthesized bool literals are accepted by the metadata-literal grammar and should work as flag defaults, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn option_accepts_stdio_is_rejected_instead_of_silently_dropped() {
let output = cargo_check_tool_crate(
"option-accepts-stdio",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(input = "option", accepts_stdio = true)]
fn run(&self, input: String) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _input: String) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"accepts_stdio has no field on options and should be rejected instead of silently dropped, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn value_name_on_flag_is_rejected_instead_of_silently_dropped() {
let output = cargo_check_tool_crate(
"value-name-on-flag",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(force = "flag", value_name = "FORCE")]
fn run(&self, force: bool) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _force: bool) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a flag has no value_name field and it should be rejected instead of silently dropped, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn result_attr_on_unit_success_is_rejected_instead_of_silently_dropped() {
let output = cargo_check_tool_crate(
"result-attr-on-unit-success",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[result(formatters = ["json"], default = "json")]
fn run(&self) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"#[result] on a unit-success method has no wire result slot and should be rejected instead of silently dropped, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn explicit_arg_attr_on_stdin_stream_is_rejected_instead_of_silently_dropped() {
let output = cargo_check_tool_crate(
"stream-arg-attr",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
use golem_rust::agentic::InputStream;
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(input = "option")]
fn run(&self, input: InputStream) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _input: InputStream) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a stdin/stdout stream is projected only from its type and any `#[arg]` field would be silently dropped, so it must be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_auto_injected_principal_rejects_arg_attributes_that_would_be_ignored() {
let output = cargo_check_tool_crate(
"principal-arg-attr",
r#"
use golem_rust::{tool_definition, tool_implementation};
#[tool_definition]
trait BadTool {
#[arg(principal = "option")]
fn run(&self, principal: golem_rust::agentic::Principal, name: String) -> String;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _principal: golem_rust::agentic::Principal, name: String) -> String {
name
}
}
"#,
);
assert!(
!output.status.success(),
"an auto-injected Principal is projected only from its exact SDK/WIT type and any `#[arg]` field would be silently dropped, so it must be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn tool_client_accepts_native_stdin_stream() {
let output = cargo_check_tool_crate(
"tool-client-native-stdin",
r#"
use golem_rust::agentic::{InputStream, invoke_and_await_infallible, pump_tool_stdin};
use golem_rust::golem_agentic::golem::tool::host::ToolRpc;
fn forward_stdin(rpc: &ToolRpc, input: &golem_rust::TypedSchemaValue, stdin: InputStream) {
let _ = invoke_and_await_infallible(rpc, &[], input, Some(pump_tool_stdin(stdin)), None, None);
}
"#,
);
assert!(
output.status.success(),
"tool client helpers must accept the same native stream type that tool definitions use, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn tool_client_allows_command_named_new() {
let output = cargo_check_tool_crate(
"tool-client-command-named-new",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Project {
fn new(&self, name: String) -> String;
}
fn build_client() {
let client = ProjectClient::default();
let _future = client.new("demo".to_string());
}
"#,
);
assert!(
output.status.success(),
"a valid tool command named `new` must not collide with the generated client constructor, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_command_named_new_does_not_reserve_fallback_constructor_name() {
let output = cargo_check_tool_crate(
"tool-client-new-fallback-name-collision",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Project {
fn new(&self, name: String) -> String;
fn __golem_tool_client_new(&self, name: String) -> String;
}
fn build_client() {
let client = ProjectClient::default();
let _first = client.new("demo".to_string());
let _second = client.__golem_tool_client_new("demo".to_string());
}
"#,
);
assert!(
output.status.success(),
"renaming the generated constructor away from `new` must not reserve a valid command method name; cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_command_named_with_parts_does_not_collide_with_hidden_client_helper() {
let output = cargo_check_tool_crate(
"tool-client-with-parts-name-collision",
r#"
use golem_rust::tool_definition;
#[tool_definition]
trait Project {
fn __golem_tool_client_with_parts(&self, name: String) -> String;
}
fn build_client() {
let client = ProjectClient::default();
let _future = client.__golem_tool_client_with_parts("demo".to_string());
}
"#,
);
assert!(
output.status.success(),
"valid command names must not collide with generated client helpers, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn subtree_placeholder_return_type_does_not_need_magic_suffix() {
let output = cargo_check_tool_crate(
"subtree-placeholder-return-type",
r#"
use golem_rust::{tool_definition, tool_implementation};
#[tool_definition]
trait ChildTool {
fn leaf(&self) -> String;
}
struct ChildHandle;
#[tool_definition]
trait ParentTool {
#[command(subtree = ChildTool)]
fn child_tool(&self) -> ChildHandle;
}
struct ParentToolImpl;
#[tool_implementation]
impl ParentTool for ParentToolImpl {
fn child_tool(&self) -> ChildHandle {
ChildHandle
}
}
"#,
);
assert!(
output.status.success(),
"the #[command(subtree = ...)] attribute, not a return-type suffix, marks a subtree dispatcher placeholder, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn tool_implementation_on_named_field_struct_still_compiles() {
let output = cargo_check_tool_crate(
"stateful-tool-implementation",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum E {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait StatefulTool {
fn run(&self, input: String) -> Result<String, E>;
}
struct StatefulToolImpl {
prefix: String,
}
#[tool_implementation]
impl StatefulTool for StatefulToolImpl {
fn run(&self, input: String) -> Result<String, E> {
Ok(format!("{}{}", self.prefix, input))
}
}
"#,
);
assert!(
output.status.success(),
"#[tool_implementation] is applied to trait impls and should not impose an undocumented unit-struct-only restriction, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn tool_implementation_allows_wildcard_impl_parameter_names() {
let output = cargo_check_tool_crate(
"wildcard-impl-parameter",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum E {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait UnusedParamTool {
fn run(&self, input: String) -> Result<String, E>;
}
struct UnusedParamToolImpl;
#[tool_implementation]
impl UnusedParamTool for UnusedParamToolImpl {
fn run(&self, _: String) -> Result<String, E> {
Ok("ok".to_string())
}
}
"#,
);
assert!(
output.status.success(),
"#[tool_implementation] should accept ordinary Rust impl parameter patterns such as `_`, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn user_value_type_named_principal_is_not_auto_injected() {
let output = cargo_check_tool_crate(
"user-principal-value-type",
r#"
use golem_rust::{tool_definition, tool_implementation, FromSchema, FromWire, IntoSchema, IntoWire, WireSchema};
mod domain {
use super::*;
#[derive(IntoSchema, FromSchema, WireSchema, IntoWire, FromWire)]
pub struct Principal {
pub id: String,
}
}
#[tool_definition]
trait DomainTool {
fn run(&self, principal: domain::Principal) -> String;
}
struct DomainToolImpl;
#[tool_implementation]
impl DomainTool for DomainToolImpl {
fn run(&self, principal: domain::Principal) -> String {
principal.id
}
}
fn build_client() {
let client = DomainToolClient::default();
let _future = client.run(domain::Principal { id: "u".to_string() });
}
"#,
);
assert!(
output.status.success(),
"a user-defined value type named `Principal` should be treated by path as a normal schema value, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_domain_principal_parameter_is_schema_input() {
let output = cargo_check_tool_crate(
"bug-finder-domain-principal-input",
r#"
use golem_rust::{tool_definition, tool_implementation, FromSchema, FromWire, IntoSchema, IntoWire, WireSchema};
mod domain {
use super::*;
#[derive(IntoSchema, FromSchema, WireSchema, IntoWire, FromWire)]
pub struct Principal {
pub id: String,
}
}
#[tool_definition]
trait DomainPrincipalTool {
fn lookup(&self, principal: domain::Principal) -> String;
}
struct DomainPrincipalToolImpl;
#[tool_implementation]
impl DomainPrincipalTool for DomainPrincipalToolImpl {
fn lookup(&self, principal: domain::Principal) -> String {
principal.id
}
}
fn compile_client_and_impl() {
let client = DomainPrincipalToolClient::default();
let _future = client.lookup(domain::Principal { id: "user-1".to_string() });
}
"#,
);
assert!(
output.status.success(),
"a user-defined schema type whose last path segment is `Principal` must not be treated as the host principal; cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn bug_finder_local_principal_type_is_schema_input() {
let output = cargo_check_tool_crate(
"bug-finder-local-principal-input",
r#"
use golem_rust::{tool_definition, tool_implementation, FromSchema, FromWire, IntoSchema, IntoWire, WireSchema};
#[derive(IntoSchema, FromSchema, WireSchema, IntoWire, FromWire)]
pub struct Principal {
pub id: String,
}
#[tool_definition]
trait LocalPrincipalTool {
fn lookup(&self, principal: Principal) -> String;
}
struct LocalPrincipalToolImpl;
#[tool_implementation]
impl LocalPrincipalTool for LocalPrincipalToolImpl {
fn lookup(&self, principal: Principal) -> String {
principal.id
}
}
fn compile_client_and_impl() {
let client = LocalPrincipalToolClient::default();
let _future = client.lookup(Principal { id: "user-1".to_string() });
}
"#,
);
assert!(
output.status.success(),
"a local user-defined schema type named `Principal` should be treated as a normal schema value, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn agentic_world_tool_rpc_is_accepted_by_tool_client_helpers() {
let output = cargo_check_tool_crate(
"agentic-world-tool-rpc-client-helper",
r#"
use golem_rust::agentic::invoke_and_await_infallible;
use golem_rust::golem_agentic::golem::tool::host::ToolRpc;
fn call_tool(rpc: &ToolRpc, input: &golem_rust::TypedSchemaValue) {
let _ = invoke_and_await_infallible(rpc, &[], input, None, None, None);
}
"#,
);
assert!(
output.status.success(),
"tool client helpers must accept the ToolRpc type exposed by the exported agentic world, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn exported_agentic_world_uses_native_streams() {
let output = cargo_check_tool_crate(
"agentic-world-native-streams",
r#"
use golem_rust::agentic::InputStream;
use golem_rust::golem_agentic::exports::golem::tool::guest::{Guest, InvocationResult, Tool, ToolError, TypedSchemaValue};
use golem_rust::golem_agentic::golem::agent::common::Principal;
struct Component;
impl Guest for Component {
fn discover_tools() -> Result<Vec<Tool>, ToolError> {
unimplemented!()
}
fn get_tool(_name: String) -> Result<Tool, ToolError> {
unimplemented!()
}
async fn invoke(
_tool_name: String,
_command_path: Vec<String>,
_input: TypedSchemaValue,
_stdin: Option<InputStream>,
_stdout: Option<golem_rust::golem_agentic::golem::tool::streams::ToolOutputWriter>,
_stderr: Option<golem_rust::golem_agentic::golem::tool::streams::ToolOutputWriter>,
_principal: Principal,
) -> Result<InvocationResult, ToolError> {
unimplemented!()
}
}
"#,
);
assert!(
output.status.success(),
"the exported agentic world should expose the same native stream types as tool definitions and tool clients, but cargo check failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[test]
fn delim_without_delimited_repeatable_mode_is_rejected() {
let output = cargo_check_tool_crate(
"delim-without-mode",
r#"
use golem_rust::{tool_definition, tool_implementation, ToolError};
#[derive(ToolError)]
enum BadError {
#[tool_error(kind = "usage-error", exit_code = 2)]
Bad(String),
}
#[tool_definition]
trait BadTool {
#[arg(tags = "option", delim = ',')]
fn run(&self, tags: Vec<String>) -> Result<(), BadError>;
}
struct BadToolImpl;
#[tool_implementation]
impl BadTool for BadToolImpl {
fn run(&self, _tags: Vec<String>) -> Result<(), BadError> {
Ok(())
}
}
"#,
);
assert!(
!output.status.success(),
"a `delim` with the default `repeatable = \"repeated\"` mode would be silently dropped and must be rejected, but cargo check succeeded\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
#[tool_definition]
trait ValueIsFlag {
#[arg(force = "flag")]
#[constraint(requires_all = value_is("force", true))]
fn run(&self, force: bool) -> Result<(), RemoteError>;
}
#[test]
fn value_is_against_flag_is_rejected_by_descriptor_build() {
let err = __golem_tool_descriptor_for_ValueIsFlag(&mut ToolBuildCtx::new())
.expect_err("value_is against a flag should be rejected during descriptor build");
assert!(
matches!(err, ToolBuildError::ValueIsTypeMismatch(ref name) if name == "force"),
"expected ValueIsTypeMismatch for flag `force`, got {err:?}",
);
}
#[tool_definition]
trait ValueIsWrongLiteralType {
#[arg(count = "option")]
#[constraint(requires_all = value_is("count", "not-a-number"))]
fn run(&self, count: u32) -> Result<(), RemoteError>;
}
#[test]
fn value_is_literal_type_mismatch_reports_value_is_not_default() {
let err = __golem_tool_descriptor_for_ValueIsWrongLiteralType(&mut ToolBuildCtx::new())
.expect_err("bad value_is literal should be rejected during descriptor build");
assert!(
matches!(err, ToolBuildError::ValueIsTypeMismatch(ref name) if name == "count"),
"expected ValueIsTypeMismatch for `count`, got {err:?}",
);
}
#[tool_definition]
trait ValueIsMapPositional {
#[arg(config = "positional")]
#[constraint(requires_all = value_is("config", 1))]
fn run(&self, config: BTreeMap<String, u32>) -> Result<(), RemoteError>;
}
#[test]
fn value_is_map_positional_uses_map_value_type_consistently() {
let tool = __golem_tool_descriptor_for_ValueIsMapPositional(&mut ToolBuildCtx::new())
.expect(
"descriptor build resolves the map value_is literal against the map value type",
);
tool.try_to_tool().expect(
"runtime validation should use the same map value comparand as descriptor build",
);
}
#[derive(Clone)]
struct FixedPair;
impl golem_rust::schema::IntoSchema for FixedPair {
fn type_id() -> golem_rust::schema::TypeId {
golem_rust::schema::TypeId::new("tool_test.FixedPair")
}
fn register_in(
_builder: &mut golem_rust::schema::SchemaBuilder,
) -> golem_rust::schema::SchemaType {
golem_rust::schema::SchemaType::fixed_list(golem_rust::schema::SchemaType::u32(), 2)
}
fn to_value(&self) -> golem_rust::schema::SchemaValue {
golem_rust::schema::SchemaValue::FixedList {
elements: vec![
golem_rust::schema::SchemaValue::U32(1),
golem_rust::schema::SchemaValue::U32(2),
],
}
}
}
impl golem_rust::schema::FromSchema for FixedPair {
fn from_value(
value: &golem_rust::schema::SchemaValue,
) -> Result<Self, golem_rust::schema::FromSchemaError> {
match value {
golem_rust::schema::SchemaValue::FixedList { elements }
| golem_rust::schema::SchemaValue::List { elements }
if elements.len() == 2 =>
{
Ok(FixedPair)
}
_ => Err(golem_rust::schema::FromSchemaError::custom(
"expected a two-element fixed list",
)),
}
}
}
impl golem_rust::WireSchema for FixedPair {
fn append_schema(builder: &mut golem_rust::schema::wit::direct::WireSchemaBuilder) -> i32 {
let element = <u32 as golem_rust::WireSchema>::append_schema(builder);
builder.push(
golem_rust::schema::wit::wire::SchemaTypeBody::FixedListType(
golem_rust::schema::wit::wire::FixedListSpec { element, length: 2 },
),
)
}
}
impl golem_rust::IntoWire for FixedPair {
fn write_wire(
&self,
writer: &mut golem_rust::schema::wit::direct::WireWriter,
) -> Result<i32, golem_rust::schema::wit::direct::WireError> {
let first = 1u32.write_wire(writer)?;
let second = 2u32.write_wire(writer)?;
Ok(writer.push(
golem_rust::schema::wit::wire::SchemaValueNode::FixedListValue(vec![first, second]),
))
}
}
impl golem_rust::FromWire for FixedPair {
fn read_wire(
reader: &mut golem_rust::schema::wit::direct::WireReader,
index: i32,
) -> Result<Self, golem_rust::schema::wit::direct::WireError> {
let golem_rust::schema::wit::wire::SchemaValueNode::FixedListValue(values) =
reader.take(index)?
else {
return Err(golem_rust::schema::wit::direct::WireError::Shape(
"fixed list",
));
};
if values.len() != 2 {
return Err(golem_rust::schema::wit::direct::WireError::Shape(
"fixed list length",
));
}
for value in values {
let _ = <u32 as golem_rust::FromWire>::read_wire(reader, value)?;
}
Ok(FixedPair)
}
}
#[tool_definition]
trait ValueIsFixedListWholeValue {
#[arg(pair = "positional")]
#[constraint(requires_all = value_is("pair", [1, 2]))]
fn run(&self, pair: FixedPair) -> Result<(), RemoteError>;
}
#[test]
fn value_is_accepts_whole_fixed_list_literal() {
let tool = __golem_tool_descriptor_for_ValueIsFixedListWholeValue(&mut ToolBuildCtx::new())
.expect("a value_is literal compatible with the whole fixed-list type should build");
tool.try_to_tool()
.expect("runtime validation should also accept the whole fixed-list value");
}
#[tool_definition]
trait ValueIsMapPositionalWithListValue {
#[arg(config = "positional")]
#[constraint(requires_all = value_is("config", 1))]
fn run(&self, config: BTreeMap<String, Vec<u32>>) -> Result<(), RemoteError>;
}
#[test]
fn value_is_map_positional_does_not_accept_nested_list_element_literal() {
let result =
__golem_tool_descriptor_for_ValueIsMapPositionalWithListValue(&mut ToolBuildCtx::new());
match result {
Err(ToolBuildError::ValueIsTypeMismatch(name)) if name == "config" => {}
Err(other) => panic!(
"expected ValueIsTypeMismatch for nested map-value element literal, got {other:?}"
),
Ok(tool) => {
let validation = tool.try_to_tool();
panic!(
"descriptor accepted a value_is literal nested inside a list-valued map entry; runtime validation returned {validation:?}"
);
}
}
}
#[tool_definition]
trait ValueIsNestedListOption {
#[arg(items = "option")]
#[constraint(requires_all = value_is("items", 1))]
fn run(&self, items: Vec<Vec<u32>>) -> Result<(), RemoteError>;
}
#[tool_definition]
trait ValueIsMapOptionWithListValue {
#[arg(config = "option")]
#[constraint(requires_all = value_is("config", 1))]
fn run(&self, config: BTreeMap<String, Vec<u32>>) -> Result<(), RemoteError>;
}
#[tool_definition]
trait ValueIsNestedListTail {
#[arg(items = "tail")]
#[constraint(requires_all = value_is("items", 1))]
fn run(&self, items: Vec<Vec<u32>>) -> Result<(), RemoteError>;
}
#[tool_definition]
trait DeferredNestedListValueIsChild {
#[constraint(requires_all = value_is("items", 1))]
fn leaf(&self, name: String) -> Result<(), RemoteError>;
}
struct DeferredNestedListSubtree;
#[tool_definition]
trait DeferredNestedListValueIsParent {
#[arg(items = "option")]
#[command(subtree = DeferredNestedListValueIsChild)]
fn deferred_nested_list_value_is_child(
&self,
items: Vec<Vec<u32>>,
) -> DeferredNestedListSubtree;
}
#[test]
fn value_is_repeatable_surfaces_do_not_accept_nested_element_literals() {
let failures = [
value_is_mismatch_failure(
__golem_tool_descriptor_for_ValueIsNestedListOption(&mut ToolBuildCtx::new()),
"items",
"Vec<Vec<u32>> repeatable option",
),
value_is_mismatch_failure(
__golem_tool_descriptor_for_ValueIsMapOptionWithListValue(&mut ToolBuildCtx::new()),
"config",
"BTreeMap<String, Vec<u32>> repeatable-map option",
),
value_is_mismatch_failure(
__golem_tool_descriptor_for_ValueIsNestedListTail(&mut ToolBuildCtx::new()),
"items",
"Vec<Vec<u32>> tail positional",
),
value_is_mismatch_failure(
__golem_tool_descriptor_for_DeferredNestedListValueIsParent(
&mut ToolBuildCtx::new(),
),
"items",
"deferred parent-supplied Vec<Vec<u32>> global option",
),
]
.into_iter()
.flatten()
.collect::<Vec<_>>();
assert!(
failures.is_empty(),
"nested collection value_is literals should be rejected at descriptor build:\n{}",
failures.join("\n")
);
}
#[tool_definition]
trait ValueIsRepeatableListWholeCollection {
#[arg(tags = "option")]
#[constraint(requires_all = value_is("tags", ["prod"]))]
fn run(&self, tags: Vec<String>) -> Result<(), RemoteError>;
}
#[tool_definition]
trait ValueIsRepeatableMapWholeCollection {
#[arg(config = "option")]
#[constraint(requires_all = value_is("config", [("env", 1)]))]
fn run(&self, config: BTreeMap<String, u32>) -> Result<(), RemoteError>;
}
#[tool_definition]
trait ValueIsTailWholeCollection {
#[arg(items = "tail")]
#[constraint(requires_all = value_is("items", ["prod"]))]
fn run(&self, items: Vec<String>) -> Result<(), RemoteError>;
}
#[test]
fn value_is_repeatable_and_tail_refs_reject_whole_collection_literals() {
let failures = [
value_is_mismatch_failure(
__golem_tool_descriptor_for_ValueIsRepeatableListWholeCollection(
&mut ToolBuildCtx::new(),
),
"tags",
"Vec<String> repeatable option with a whole-list literal",
),
value_is_mismatch_failure(
__golem_tool_descriptor_for_ValueIsRepeatableMapWholeCollection(
&mut ToolBuildCtx::new(),
),
"config",
"BTreeMap<String, u32> repeatable-map option with a whole-map literal",
),
value_is_mismatch_failure(
__golem_tool_descriptor_for_ValueIsTailWholeCollection(&mut ToolBuildCtx::new()),
"items",
"Vec<String> tail positional with a whole-list literal",
),
]
.into_iter()
.flatten()
.collect::<Vec<_>>();
assert!(
failures.is_empty(),
"value_is refs for repeatable options and tail positionals must compare one occurrence/item/value, not the whole collected container:\n{}",
failures.join("\n")
);
}
fn value_is_mismatch_failure(
result: Result<ExtendedToolType, ToolBuildError>,
name: &str,
context: &str,
) -> Option<String> {
match result {
Err(ToolBuildError::ValueIsTypeMismatch(actual)) if actual == name => None,
Err(other) => Some(format!(
"expected ValueIsTypeMismatch for `{name}` in {context}, got {other:?}"
)),
Ok(tool) => {
let validation = tool.try_to_tool();
Some(format!(
"descriptor accepted scalar value_is for a nested collection occurrence in {context}; runtime validation returned {validation:?}"
))
}
}
}
type StringAliasForNumericRefinement = String;
#[tool_definition]
trait NumericRefinementOnStringAlias {
#[arg(name = "option", unit = "ms")]
fn run(&self, name: StringAliasForNumericRefinement) -> Result<(), RemoteError>;
}
#[test]
fn numeric_refinement_on_string_type_alias_is_rejected() {
let result =
__golem_tool_descriptor_for_NumericRefinementOnStringAlias(&mut ToolBuildCtx::new());
assert!(
result.is_err(),
"numeric refinements on a non-numeric type alias must be rejected instead of being silently dropped; descriptor was {result:#?}",
);
}
type StringAliasForTextRefinement = String;
#[tool_definition]
trait TextRefinementOnStringAlias {
#[arg(name = "option", regex = "a+")]
fn run(&self, name: StringAliasForTextRefinement) -> Result<(), RemoteError>;
}
#[test]
fn text_refinement_on_string_type_alias_is_applied() {
let tool =
__golem_tool_descriptor_for_TextRefinementOnStringAlias(&mut ToolBuildCtx::new())
.expect("text refinement on a string alias builds");
let run = tool
.commands
.iter()
.find(|c| c.name == "run")
.expect("run command");
let option = run
.body
.as_ref()
.and_then(|body| body.options.first())
.expect("option");
let graph = match &option.shape {
golem_rust::agentic::ExtendedOptionShape::Scalar(g) => g,
other => panic!("expected a scalar option shape, got {other:#?}"),
};
assert!(
matches!(graph.root, golem_rust::schema::SchemaType::Text { .. }),
"a regex-refined string alias should resolve to a Text schema, got {:#?}",
graph.root
);
}
type StringAliasForUrlRefinement = String;
#[tool_definition]
trait UrlRefinementOnStringAlias {
#[arg(name = "option", schemes = ["https"])]
fn run(&self, name: StringAliasForUrlRefinement) -> Result<(), RemoteError>;
}
#[test]
fn url_refinement_on_string_type_alias_is_rejected() {
let result =
__golem_tool_descriptor_for_UrlRefinementOnStringAlias(&mut ToolBuildCtx::new());
assert!(
result.is_err(),
"url refinements on a string alias must be rejected instead of silently rewriting the schema; descriptor was {result:#?}",
);
}
#[tool_definition]
trait ValueIsRegexRestrictionMismatch {
#[arg(format = "option", regex = "^(json|yaml)$")]
#[constraint(requires_all = value_is("format", "toml"))]
fn run(&self, format: String) -> Result<(), RemoteError>;
}
#[test]
fn value_is_regex_restriction_mismatch_is_rejected_during_descriptor_build() {
match __golem_tool_descriptor_for_ValueIsRegexRestrictionMismatch(&mut ToolBuildCtx::new())
{
Err(ToolBuildError::ValueIsTypeMismatch(name)) if name == "format" => {}
Err(other) => panic!("expected ValueIsTypeMismatch for `format`, got {other:?}"),
Ok(tool) => panic!(
"descriptor accepted a value_is literal that violates the referenced regex restriction; try_to_tool returned {:?}",
tool.try_to_tool()
),
}
}
#[tool_definition]
trait ValueIsRefinedRepeatableListOption {
#[arg(tags = "option", regex = "^prod$")]
#[constraint(requires_all = value_is("tags", "prod"))]
fn run(&self, tags: Vec<String>) -> Result<(), RemoteError>;
}
#[test]
fn value_is_on_refined_repeatable_list_option_builds() {
let tool = __golem_tool_descriptor_for_ValueIsRefinedRepeatableListOption(
&mut ToolBuildCtx::new(),
)
.expect(
"value_is on a refined repeatable-list option should interpret the literal against the refined item type",
);
tool.try_to_tool().expect(
"runtime validation should accept the same refined repeatable-list value_is literal",
);
}
fn cargo_check_tool_crate(name: &str, source: &str) -> std::process::Output {
cargo_tool_crate(name, source, "check")
}
fn cargo_test_tool_crate(name: &str, source: &str) -> std::process::Output {
cargo_tool_crate(name, source, "test")
}
fn cargo_test_tool_crate_with_fake_sdk(name: &str, source: &str) -> std::process::Output {
cargo_tool_crate_with_dependency(
name,
"golem-rust",
source,
"test",
"golem-rust-actual = { package = \"golem-rust\", path = PATH, features = [\"export_golem_agentic\"] }",
)
}
fn cargo_tool_crate(name: &str, source: &str, command: &str) -> std::process::Output {
cargo_tool_crate_with_dependency(
name,
name,
source,
command,
"golem-rust = { path = PATH, features = [\"export_golem_agentic\"] }",
)
}
fn cargo_tool_crate_with_dependency(
name: &str,
package_name: &str,
source: &str,
command: &str,
dependency_template: &str,
) -> std::process::Output {
let package_version = if package_name == "golem-rust" {
"0.0.1"
} else {
"0.0.0"
};
let root = std::env::temp_dir().join(format!(
"golem-rust-tool-{name}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system clock should be after UNIX_EPOCH")
.as_nanos()
));
fs::create_dir_all(root.join("src")).unwrap();
let golem_rust_path = Path::new(env!("CARGO_MANIFEST_DIR"));
let dependency = dependency_template.replace("PATH", &toml_string(golem_rust_path));
fs::write(
root.join("Cargo.toml"),
format!(
r#"
[package]
name = "{package_name}"
version = "{package_version}"
edition = "2024"
[dependencies]
{dependency}
"#,
),
)
.unwrap();
fs::write(root.join("src/lib.rs"), source).unwrap();
let target_dir = golem_rust_path
.parent()
.expect("golem-rust crate should have an SDK workspace parent")
.join("target");
let output = Command::new("cargo")
.arg(command)
.arg("--quiet")
.env("CARGO_TARGET_DIR", target_dir)
.current_dir(&root)
.output()
.unwrap_or_else(|error| {
panic!("failed to run cargo {command} for temporary tool crate: {error}")
});
fs::remove_dir_all(&root).unwrap_or_else(|error| {
panic!(
"failed to remove temporary workspace {}: {error}",
root.display()
)
});
output
}
fn toml_string(path: &Path) -> String {
format!("{:?}", path.display().to_string())
}
}