fix(adapters): reject unbound placeholders when from_jsonschema input_schema has no properties
The eager placeholder-binding check in spec_name_references_undeclared
short-circuited on an empty properties set, letting
FromJsonSchema::new accept a template whose placeholder could never be
bound (every later call failed INTERNAL, or INVALID_INPUT if a peer
supplied the key). Drop the early return so any placeholder against a
declared-nothing (or properties-less) input_schema is unbound, matching
the from_openapi unbound_placeholders path.
- add construction-time test: no-properties schema + {id} template
fails with the placeholder error; no-properties schema + placeholder-
free template still constructs and imports as Visibility::Internal
Verification: scripts/verify.sh (342 passed), scripts/verify.sh
--all-features (454 passed); clippy -D warnings and fmt --check clean
This commit is contained in:
@@ -170,9 +170,6 @@ fn spec_name_references_undeclared(spec: &OperationSpec, path_template: &str) ->
|
||||
.and_then(|p| p.as_object())
|
||||
.map(|p| p.keys().cloned().collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
if properties.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let mut rest = path_template;
|
||||
while let Some(start) = rest.find('{') {
|
||||
let Some(end_rel) = rest[start..].find('}') else {
|
||||
@@ -722,4 +719,56 @@ mod tests {
|
||||
Err(e) => panic!("expected SchemaParse, got {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construction_rejects_placeholder_when_input_schema_has_no_properties() {
|
||||
let spec = OperationSpec::new(
|
||||
"svc/getWidget",
|
||||
OperationType::Query,
|
||||
Visibility::Internal,
|
||||
serde_json::json!({"type":"object"}),
|
||||
serde_json::json!({"type":"object"}),
|
||||
vec![],
|
||||
AccessControl::default(),
|
||||
None,
|
||||
);
|
||||
let result = FromJsonSchema::new(
|
||||
spec,
|
||||
test_config("svc", "https://api.example.com"),
|
||||
"/widgets/{id}".to_string(),
|
||||
"GET".to_string(),
|
||||
test_http_client(),
|
||||
);
|
||||
match result {
|
||||
Err(AdapterError::SchemaParse { message }) => {
|
||||
assert!(message.contains("placeholder"), "message was: {message}");
|
||||
assert!(message.contains("{id}"), "message was: {message}");
|
||||
}
|
||||
Ok(_) => {
|
||||
panic!("no-properties schema with a placeholder template must fail at construction")
|
||||
}
|
||||
Err(e) => panic!("expected SchemaParse, got {e}"),
|
||||
}
|
||||
|
||||
let spec = OperationSpec::new(
|
||||
"svc/listWidgets",
|
||||
OperationType::Query,
|
||||
Visibility::Internal,
|
||||
serde_json::json!({"type":"object"}),
|
||||
serde_json::json!({"type":"object"}),
|
||||
vec![],
|
||||
AccessControl::default(),
|
||||
None,
|
||||
);
|
||||
let adapter = FromJsonSchema::new(
|
||||
spec,
|
||||
test_config("svc", "https://api.example.com"),
|
||||
"/widgets".to_string(),
|
||||
"GET".to_string(),
|
||||
test_http_client(),
|
||||
)
|
||||
.expect("placeholder-free template with a no-properties schema builds");
|
||||
let bundles = futures::executor::block_on(adapter.import()).unwrap();
|
||||
assert_eq!(bundles[0].spec.visibility, Visibility::Internal);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user