1use crate::Value;
2use crate::error::CamelError;
3use crate::script_source;
4
5#[derive(
7 Debug,
8 Clone,
9 PartialEq,
10 Eq,
11 serde::Serialize,
12 serde::Deserialize,
13 schemars::JsonSchema,
14 ts_rs::TS,
15)]
16#[serde(rename_all = "snake_case")]
17#[ts(rename_all = "snake_case")]
18pub struct LanguageExpressionDef {
19 pub language: String,
20 pub source: String,
21}
22
23impl LanguageExpressionDef {
24 pub fn file(path: impl AsRef<std::path::Path>) -> Result<Self, CamelError> {
32 let path = path.as_ref();
33 let language = script_source::infer_script_language(path)?;
34 let source = script_source::read_script_source(path)?;
35 Ok(Self { language, source })
36 }
37
38 pub fn file_with_language(
44 path: impl AsRef<std::path::Path>,
45 language: impl Into<String>,
46 ) -> Result<Self, CamelError> {
47 let source = script_source::read_script_source(path.as_ref())?;
48 Ok(Self {
49 language: language.into(),
50 source,
51 })
52 }
53}
54
55#[derive(Debug, Clone, PartialEq)]
57#[non_exhaustive]
58pub enum ValueSourceDef {
59 Literal(Value),
60 Expression(LanguageExpressionDef),
61}
62
63#[cfg(test)]
64mod tests {
65 use std::fs;
66 use std::path::Path;
67
68 use tempfile::TempDir;
69
70 use super::LanguageExpressionDef;
71
72 #[test]
73 fn file_constructor_infers_language() {
74 let dir: TempDir = tempfile::tempdir().expect("create tempdir");
75 let scripts = dir.path().join("scripts");
76 fs::create_dir(&scripts).expect("create scripts dir");
77 let path = scripts.join("x.rhai");
78 let contents = "let value = 1;\n";
79 fs::write(&path, contents).expect("write fixture");
80
81 let def = LanguageExpressionDef::file(&path).expect("build from script file");
82
83 assert_eq!(def.language, "rhai");
84 assert_eq!(def.source, contents);
85 }
86
87 #[test]
88 fn file_constructor_explicit_language_wins() {
89 let dir: TempDir = tempfile::tempdir().expect("create tempdir");
90 let path = dir.path().join("notes.txt");
91 let contents = "hello\n";
92 fs::write(&path, contents).expect("write fixture");
93
94 let def = LanguageExpressionDef::file_with_language(&path, "simple")
95 .expect("build with explicit language");
96
97 assert_eq!(def.language, "simple");
98 assert_eq!(def.source, contents);
99 }
100
101 #[test]
102 fn file_constructor_missing_file_errors() {
103 let dir: TempDir = tempfile::tempdir().expect("create tempdir");
104 let path = dir.path().join("absent.rhai");
105
106 let error = LanguageExpressionDef::file(&path).expect_err("missing file must fail");
107 let message = error.to_string();
108
109 assert!(
110 message.contains(&path.display().to_string()),
111 "message: {message}"
112 );
113 }
114
115 #[test]
116 fn file_constructor_relative_missing_file_names_absolute_path() {
117 let cwd = std::env::current_dir().expect("read current dir");
118 let relative = Path::new("nonexistent_dir_xyz/absent.rhai");
119
120 let error = LanguageExpressionDef::file(relative).expect_err("missing file must fail");
121 let message = error.to_string();
122
123 let expected = cwd.join(relative);
124 assert!(expected.is_absolute());
125 assert!(
126 message.contains(&expected.display().to_string()),
127 "message: {message}"
128 );
129 }
130}