Skip to content

Commit cbdfae8

Browse files
committed
Fully integrate reqlang for all templating
This replaces template references like {{?prompt}} to use the reqlang-expr vm now
1 parent 3fcc34f commit cbdfae8

6 files changed

Lines changed: 137 additions & 47 deletions

File tree

examples/valid/expr_reference_env.reqlang

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
GET https://httpbin.org/headers HTTP/1.1
77
Content-Type: application/json
88
x-env: {(id @env)}
9+
x-env2: {{@env}}
910
```

reqlang-client/src/main.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,16 +283,21 @@ impl InputParamsState {
283283
ui.separator();
284284

285285
if ui.button("Run").clicked() {
286+
let mut provider_values = HashMap::new();
287+
288+
let env = if self.env.is_empty() {
289+
None
290+
} else {
291+
provider_values.insert("env".to_string(), self.env.clone());
292+
Some(self.env.as_str())
293+
};
294+
286295
let reqfile = template(
287296
&client_ctx.source.clone().unwrap(),
288-
if self.env.is_empty() {
289-
None
290-
} else {
291-
Some(&self.env)
292-
},
297+
env,
293298
&self.prompts.clone(),
294299
&self.secrets.clone(),
295-
&HashMap::new(),
300+
&provider_values,
296301
)
297302
.unwrap();
298303

reqlang-lsp/src/main.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,20 @@ impl LanguageServer for Backend {
187187
let from_client_params =
188188
Into::<RequestParamsFromClient>::into(from_client_params_value.clone());
189189

190+
let mut provider_values: HashMap<String, String> = HashMap::new();
191+
192+
let env = from_client_params.env.as_deref();
193+
194+
if let Some(env) = env {
195+
provider_values.insert("env".to_string(), env.to_string());
196+
}
197+
190198
let reqfile = template(
191199
&from_client_params.reqfile,
192200
from_client_params.env.as_deref(),
193201
&from_client_params.prompts,
194202
&from_client_params.secrets,
195-
&Default::default(),
203+
&provider_values,
196204
)
197205
.expect("Should get templated request file");
198206

reqlang/src/fetch.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,18 @@ impl From<HttpRequest> for HttpRequestFetcher {
126126
/// ```
127127
impl From<RequestParamsFromClient> for HttpRequestFetcher {
128128
fn from(params: RequestParamsFromClient) -> Self {
129+
let mut provider_values: HashMap<String, String> = HashMap::new();
130+
131+
if let Some(env) = &params.env {
132+
provider_values.insert("env".to_string(), env.clone());
133+
}
134+
129135
let reqfile = template(
130136
&params.reqfile,
131137
params.env.as_deref(),
132138
&params.prompts,
133139
&params.secrets,
134-
&HashMap::new(),
140+
&provider_values,
135141
)
136142
.unwrap();
137143

reqlang/src/templater.rs

Lines changed: 97 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub fn template(
1616
env: Option<&str>,
1717
prompts: &HashMap<String, String>,
1818
secrets: &HashMap<String, String>,
19-
provider_values: &HashMap<String, String>,
19+
provider_values: &HashMap<String, String>, // TODO: Wire this up
2020
) -> Result<TemplatedRequestFile, Vec<Spanned<ReqlangError>>> {
2121
let ast = Ast::from(reqfile_string);
2222
let parsed_reqfile = parse(&ast)?;
@@ -90,66 +90,70 @@ pub fn template(
9090
// Gather list of template references along with each reference's type
9191
//
9292
// e.g. ("{{:var_name}}", ReferenceType::Variable("var_name"))
93-
let template_refs_to_replace: Vec<(String, ReferenceType)> = reqfile
93+
let template_refs_to_replace: Vec<(String, ReferenceType, Span)> = reqfile
9494
.refs
9595
.iter()
96-
.map(|(template_reference, _)| {
97-
(format!("{template_reference}"), template_reference.clone())
96+
.map(|(template_reference, template_reference_span)| {
97+
(
98+
format!("{template_reference}"),
99+
template_reference.clone(),
100+
template_reference_span.clone(),
101+
)
98102
})
99103
.collect();
100104

101105
// If the environment is provided, use it to resolve template references
106+
let default_variable_values = parsed_reqfile.default_variable_values();
102107
let vars = match env {
103-
Some(env) => reqfile.env(env).unwrap_or_default(),
108+
Some(env) => reqfile.env(env).unwrap_or(default_variable_values),
104109
None => HashMap::new(),
105110
};
106111

107-
let default_variable_values = parsed_reqfile.default_variable_values();
108-
let default_prompt_values = parsed_reqfile.default_prompt_values();
109-
110-
// Replace template references with the resolved values
111-
for (template_ref, ref_type) in &template_refs_to_replace {
112-
let value = match ref_type {
113-
ReferenceType::Variable(name) => {
114-
vars.get(name).or(default_variable_values.get(name))
115-
}
116-
ReferenceType::Prompt(name) => {
117-
prompts.get(name).or(default_prompt_values.get(name))
118-
}
119-
ReferenceType::Secret(name) => secrets.get(name),
120-
ReferenceType::Provider(name) => provider_values.get(name),
121-
_ => None,
122-
};
123-
124-
// If reference can not be resolved, keep the template reference as is
125-
if value.is_some() {
126-
input = input.replace(template_ref, value.unwrap());
127-
}
128-
}
129-
130-
let mut compiler_env =
131-
CompileTimeEnv::new(reqfile.vars(), reqfile.prompts(), reqfile.secrets(), vec![]);
112+
let mut compiler_env = CompileTimeEnv::new(
113+
reqfile.vars(),
114+
reqfile.prompts(),
115+
reqfile.secrets(),
116+
provider_values.keys().map(|x| x.clone()).collect(),
117+
);
132118

133119
let mut runtime_env = {
134-
let var_values = match env {
120+
let var_values: Vec<String> = match env {
135121
Some(env) => reqfile
136122
.vars()
137123
.iter()
138124
.map(|x| {
139125
let config = parsed_reqfile.config.clone().unwrap().0.env(env).unwrap();
140-
let value = config.get(x).unwrap();
126+
let value = config
127+
.get(&x.clone().clone())
128+
.unwrap_or(&vars.get(&x.clone().clone()).unwrap());
141129

142130
value.clone()
143131
})
144132
.collect(),
145-
None => vec![],
133+
None => provider_values.values().map(|x| x.clone()).collect(),
146134
};
147135

148-
let prompt_values: Vec<Option<String>> = reqfile
149-
.prompts()
150-
.iter()
151-
.map(|x| prompts.get(x).cloned())
152-
.collect();
136+
let prompt_values = {
137+
let default_prompt_values = parsed_reqfile.default_prompt_values();
138+
139+
let prompt_values = reqfile
140+
.prompts()
141+
.iter()
142+
.map(|x| {
143+
let value = prompts.get(x).cloned().unwrap_or(
144+
default_prompt_values
145+
.get(x)
146+
.cloned()
147+
.unwrap_or_default()
148+
.clone(),
149+
);
150+
151+
value.clone()
152+
})
153+
.collect();
154+
155+
prompt_values
156+
};
153157

154158
let secret_values: Vec<Option<String>> = reqfile
155159
.secrets()
@@ -159,7 +163,7 @@ pub fn template(
159163

160164
RuntimeEnv {
161165
vars: var_values.clone(),
162-
prompts: prompt_values.iter().filter_map(|x| x.clone()).collect(),
166+
prompts: prompt_values,
163167
secrets: secret_values.iter().filter_map(|x| x.clone()).collect(),
164168
client_context: vec![],
165169
}
@@ -173,6 +177,60 @@ pub fn template(
173177

174178
let mut vm = Vm::new();
175179

180+
for (_, ref_type, ref_span) in &template_refs_to_replace {
181+
match reqlang_expr::parser::parse(&ref_type.lookup_name()) {
182+
Ok(expr) => match compile(&mut (expr, ref_span.clone()), &compiler_env) {
183+
Ok(bytecode) => {
184+
let result = vm.interpret(bytecode.into(), &compiler_env, &runtime_env);
185+
186+
let replacement_string = match result {
187+
Ok(expr_value) => expr_value
188+
.get_string()
189+
.expect("should be string")
190+
.to_string(),
191+
Err(expr_errs) => {
192+
templating_errors.push((
193+
ReqlangError::ResolverError(
194+
ResolverError::ExpressionEvaluationError(
195+
ref_type.lookup_name().clone(),
196+
format!("{expr_errs:#?}"),
197+
),
198+
),
199+
ref_span.clone(),
200+
));
201+
202+
// In the case of an error, replacement string
203+
// is the original string
204+
ref_type.lookup_name().clone()
205+
}
206+
};
207+
208+
let x = format!("{{{{{}}}}}", ref_type.lookup_name());
209+
210+
input = input.replace(&x, &replacement_string);
211+
}
212+
Err(expr_errs) => {
213+
templating_errors.push((
214+
ReqlangError::ResolverError(ResolverError::ExpressionEvaluationError(
215+
ref_type.lookup_name().clone(),
216+
format!("{expr_errs:#?}"),
217+
)),
218+
ref_span.clone(),
219+
));
220+
}
221+
},
222+
Err(expr_errs) => {
223+
templating_errors.push((
224+
ReqlangError::ResolverError(ResolverError::ExpressionEvaluationError(
225+
ref_type.lookup_name().clone(),
226+
format!("{expr_errs:#?}"),
227+
)),
228+
ref_span.clone(),
229+
));
230+
}
231+
}
232+
}
233+
176234
let items = &reqfile.exprs.to_vec();
177235
for (expr_ref, expr_span) in items {
178236
match reqlang_expr::parser::parse(&format!("({expr_ref})")) {

reqlang/src/types/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ pub enum ReferenceType {
2222
Unknown(String),
2323
}
2424

25+
impl ReferenceType {
26+
pub fn lookup_name(&self) -> String {
27+
match self {
28+
ReferenceType::Variable(name) => format!(":{name}"),
29+
ReferenceType::Prompt(name) => format!("?{name}"),
30+
ReferenceType::Secret(name) => format!("!{name}"),
31+
ReferenceType::Provider(name) => format!("@{name}"),
32+
ReferenceType::Unknown(_) => panic!("Invalid reference type"),
33+
}
34+
}
35+
}
36+
2537
impl Display for ReferenceType {
2638
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2739
write!(

0 commit comments

Comments
 (0)