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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/plasm-runtime/src/execution/compile_preflight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ fn preflight_compile_create(
message: e.to_string(),
}
})?;
apply_preflight_compile_stubs(&mut env, capability);
apply_preflight_compile_stubs(&mut env, capability, cgs);
merge_plasm_execute_session_env(&mut env);
compile_operation_dispatch(&capability_template, &env).map(|_| ())
}
Expand Down Expand Up @@ -237,7 +237,7 @@ fn preflight_compile_invoke(invoke: &InvokeExpr, cgs: &CGS) -> Result<(), Runtim
}
})?;
merge_entity_id_from_into_input_env(&mut env, target_ent, capability);
apply_preflight_compile_stubs(&mut env, capability);
apply_preflight_compile_stubs(&mut env, capability, cgs);
merge_plasm_execute_session_env(&mut env);
compile_operation_dispatch(&capability_template, &env).map(|_| ())
}
31 changes: 26 additions & 5 deletions crates/plasm-runtime/src/preflight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ pub(crate) async fn apply_preflight_steps(
}

/// Compile-only preflight: inject stub merge keys so CML templates compile without HTTP hydration.
pub(crate) fn apply_preflight_compile_stubs(env: &mut CmlEnv, capability: &CapabilitySchema) {
pub(crate) fn apply_preflight_compile_stubs(
env: &mut CmlEnv,
capability: &CapabilitySchema,
cgs: &CGS,
) {
let Some(PreflightPlan(steps)) = capability.preflight.as_ref() else {
return;
};
Expand All @@ -133,7 +137,10 @@ pub(crate) fn apply_preflight_compile_stubs(env: &mut CmlEnv, capability: &Capab
}
for wire_key in merge.keys() {
if env.get(wire_key).is_none() {
env.insert(wire_key.clone(), preflight_compile_stub_value(wire_key));
env.insert(
wire_key.clone(),
preflight_wire_key_compile_stub_value(wire_key),
);
}
}
}
Expand All @@ -145,7 +152,10 @@ pub(crate) fn apply_preflight_compile_stubs(env: &mut CmlEnv, capability: &Capab
}
for wire_key in merge.keys() {
if env.get(wire_key).is_none() {
env.insert(wire_key.clone(), preflight_compile_stub_value(wire_key));
env.insert(
wire_key.clone(),
preflight_wire_key_compile_stub_value(wire_key),
);
}
}
}
Expand All @@ -161,13 +171,24 @@ pub(crate) fn apply_preflight_compile_stubs(env: &mut CmlEnv, capability: &Capab
env.insert(merge.clone(), Value::Array(Vec::new()));
}
}
PreflightStep::HydrateInvokeTarget { .. } => {}
PreflightStep::HydrateInvokeTarget { get, prefix } => {
let Some(get_cap) = cgs.get_capability(get) else {
continue;
};
let Some(entity) = cgs.get_entity(get_cap.domain.as_str()) else {
continue;
};
for field_name in entity.fields.keys() {
let key = format!("{prefix}_{field_name}");
env.insert(key.clone(), preflight_wire_key_compile_stub_value(&key));
}
}
Comment thread
neithanmo marked this conversation as resolved.
PreflightStep::ExistenceCheck { .. } => {}
}
}
}

fn preflight_compile_stub_value(wire_key: &str) -> Value {
fn preflight_wire_key_compile_stub_value(wire_key: &str) -> Value {
if wire_key.ends_with("Id")
|| wire_key.ends_with("_id")
|| wire_key == "id"
Expand Down
45 changes: 45 additions & 0 deletions crates/plasm-runtime/tests/hydrate_invoke_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use plasm_core::{Expr, InvokeExpr, Value, CGS};
use plasm_runtime::{preflight_compile_expr, ViewAmbientContext};

fn fixture() -> CGS {
let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fixtures/schemas/hydrate_invoke_target");
plasm_core::load_schema(&dir).expect("load hydrate_invoke_target fixture")
}

fn run_expr(capability: &str) -> Expr {
let mut input = indexmap::indexmap! {
"expr".to_string() => Value::String("up".to_string()),
};
if capability == "datasource_run" {
input.insert(
"from".to_string(),
Value::String("2026-08-03T03:50:00Z".to_string()),
);
input.insert(
"to".to_string(),
Value::String("2026-08-03T04:00:00Z".to_string()),
);
}
Expr::Invoke(InvokeExpr::new(
capability,
"Datasource",
"prometheus",
Some(Value::Object(input)),
))
}

#[test]
fn static_compile_hydrates_declared_entity_fields_and_honors_prefix() {
let cgs = fixture();
let ambient = ViewAmbientContext::default();

preflight_compile_expr(&run_expr("datasource_run"), &cgs, &ambient)
.expect("ds_type comes from Datasource fields even though provides omits it");
preflight_compile_expr(&run_expr("datasource_run_source_prefix"), &cgs, &ambient)
.expect("source_type should honor the configured prefix");

let error = preflight_compile_expr(&run_expr("datasource_run_typo"), &cgs, &ambient)
.expect_err("ds_typo must remain an unknown CML variable");
assert!(error.to_string().contains("ds_typo"), "{error}");
}
95 changes: 95 additions & 0 deletions fixtures/schemas/hydrate_invoke_target/domain.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
version: 1
http_backend: http://localhost:1080

values:
nv_datasource_uid:
type: string
string_semantics: short
nv_datasource_type:
type: string
string_semantics: short
nv_query_expr:
type: string
string_semantics: short
nv_query_time:
type: string
string_semantics: short
nv_query_results:
type: json

entities:
Datasource:
id_field: uid
description: Datasource used to test invoke-target preflight hydration.
fields:
uid:
required: true
value_ref: nv_datasource_uid
type:
required: true
value_ref: nv_datasource_type
query_results:
required: false
value_ref: nv_query_results

capabilities:
datasource_get:
kind: get
entity: Datasource
provides:
- uid
# `type` is deliberately omitted: provides must not restrict decoded-row hydration.

datasource_run:
kind: action
entity: Datasource
preflight:
- kind: hydrate_invoke_target
get: datasource_get
prefix: ds
parameters:
- name: expr
value_ref: nv_query_expr
required: true
- name: from
value_ref: nv_query_time
required: true
- name: to
value_ref: nv_query_time
required: true
provides:
- uid
- query_results

datasource_run_typo:
kind: action
entity: Datasource
preflight:
- kind: hydrate_invoke_target
get: datasource_get
prefix: ds
parameters:
- name: expr
value_ref: nv_query_expr
required: true
provides:
- uid
- query_results

datasource_run_source_prefix:
kind: action
entity: Datasource
preflight:
- kind: hydrate_invoke_target
get: datasource_get
prefix: source
parameters:
- name: expr
value_ref: nv_query_expr
required: true
provides:
- uid
- query_results

auth:
scheme: none
60 changes: 60 additions & 0 deletions fixtures/schemas/hydrate_invoke_target/mappings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
datasource_get:
method: GET
path:
- { type: literal, value: api }
- { type: literal, value: datasources }
- { type: literal, value: uid }
- { type: var, name: id }
response:
single: true

datasource_run:
method: POST
path:
- { type: literal, value: api }
- { type: literal, value: ds }
- { type: literal, value: query }
body:
type: object
fields:
- [queries, { type: array, elements: [
{ type: object, fields: [
[datasource, { type: object, fields: [
[type, { type: var, name: ds_type }],
[uid, { type: var, name: id }]
]}],
[expr, { type: var, name: expr }]
]}
]}]
- [from, { type: var, name: from }]
- [to, { type: var, name: to }]
response:
single: true

datasource_run_typo:
method: POST
path:
- { type: literal, value: api }
- { type: literal, value: ds }
- { type: literal, value: query }
body:
type: object
fields:
- [datasourceType, { type: var, name: ds_typo }]
- [expr, { type: var, name: expr }]
response:
single: true

datasource_run_source_prefix:
method: POST
path:
- { type: literal, value: api }
- { type: literal, value: ds }
- { type: literal, value: query }
body:
type: object
fields:
- [datasourceType, { type: var, name: source_type }]
- [expr, { type: var, name: expr }]
response:
single: true
Loading