1use crate::ast::{
2 ActionDef, AgentFile, ConnectionEntry, Expr, LanguageEntry, Type, VariableDecl, VariableKind,
3};
4use serde::Serialize;
5use std::ops::Range;
6
7#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
8pub enum Severity {
9 Error,
10 Warning,
11}
12
13#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
14pub struct SemanticError {
15 pub message: String,
16 pub span: Option<Range<usize>>,
17 pub severity: Severity,
18 pub hint: Option<String>,
19}
20
21pub fn validate_ast(ast: &AgentFile) -> Vec<SemanticError> {
22 let mut errors = Vec::new();
23
24 if let Some(vars_block) = &ast.variables {
26 for var in &vars_block.node.variables {
27 validate_variable(&var.node, &mut errors);
28 }
29 }
30
31 if let Some(lang_block) = &ast.language {
33 for entry in &lang_block.node.entries {
34 validate_language_entry(&entry.node, &mut errors);
35 }
36 }
37
38 for conn_block in &ast.connections {
40 for entry in &conn_block.node.entries {
41 validate_connection_entry(&entry.node, &mut errors);
42 }
43 }
44
45 if let Some(start_agent) = &ast.start_agent {
48 if let Some(actions) = &start_agent.node.actions {
49 for action in &actions.node.actions {
50 validate_action_def(&action.node, &mut errors);
51 }
52 }
53 }
54
55 for topic in &ast.topics {
56 if let Some(actions) = &topic.node.actions {
57 for action in &actions.node.actions {
58 validate_action_def(&action.node, &mut errors);
59 }
60 }
61 }
62
63 errors
64}
65
66fn validate_variable(var: &VariableDecl, errors: &mut Vec<SemanticError>) {
67 if let VariableKind::Mutable = var.kind {
69 match var.ty.node {
70 Type::Integer | Type::Long | Type::Datetime | Type::Time => {
71 errors.push(SemanticError {
72 message: format!(
73 "Variable '{}' with type {:?} is not supported for mutable variables. This may be supported in the future.",
74 var.name.node, var.ty.node
75 ),
76 span: Some(var.ty.span.clone()),
77 severity: Severity::Error,
78 hint: Some("Allowed mutable types: String, Boolean, Number, Currency, Date, Id, Object, Timestamp".to_string()),
79 });
80 }
81 _ => {}
82 }
83 }
84
85 if let VariableKind::Linked = var.kind {
88 if let Some(source) = &var.source {
89 if source.node.namespace == "context" {
90 if let Type::Object = var.ty.node {
91 errors.push(SemanticError {
92 message: format!(
93 "Context variable '{}' cannot be an object type",
94 var.name.node
95 ),
96 span: Some(var.ty.span.clone()),
97 severity: Severity::Error,
98 hint: None,
99 });
100 }
101 }
102 }
103 }
104}
105
106fn validate_language_entry(entry: &LanguageEntry, errors: &mut Vec<SemanticError>) {
107 if entry.name.node == "additional_locales" {
109 if let Expr::String(ref s) = entry.value.node {
110 let valid_locales = [
111 "ar", "bg", "ca", "cs", "da", "de", "el", "en_AU", "en_GB", "en_US", "es", "es_MX",
112 "et", "fi", "fr", "fr_CA", "hi", "hr", "hu", "in", "it", "iw", "ja", "ko", "nl_NL",
113 "no", "pl", "pt_BR", "pt_PT", "ro", "sv", "th", "tl", "tr", "vi", "zh_CN", "zh_TW",
114 ];
115
116 let codes: Vec<&str> = s.split(',').map(|s| s.trim()).collect();
117 for code in codes {
118 if !valid_locales.contains(&code) {
119 errors.push(SemanticError {
120 message: format!("Invalid additional_locale '{}'.", code),
121 span: Some(entry.value.span.clone()),
122 severity: Severity::Error,
123 hint: Some(format!("Valid locales are: {}", valid_locales.join(", "))),
124 });
125 }
126 }
127 }
128 }
129}
130
131fn validate_connection_entry(entry: &ConnectionEntry, errors: &mut Vec<SemanticError>) {
132 if entry.name.node == "outbound_route_type" && entry.value.node != "OmniChannelFlow" {
134 errors.push(SemanticError {
135 message: format!(
136 "invalid outbound_route_type, found '{}' expected 'OmniChannelFlow'",
137 entry.value.node
138 ),
139 span: Some(entry.value.span.clone()),
140 severity: Severity::Error,
141 hint: None,
142 });
143 }
144}
145
146fn validate_action_def(action: &ActionDef, errors: &mut Vec<SemanticError>) {
147 if let Some(inputs) = &action.inputs {
149 for param in &inputs.node {
150 let name = ¶m.node.name.node;
151 match name.as_str() {
152 "description" | "label" | "target" | "inputs" | "outputs" => {
153 errors.push(SemanticError {
154 message: format!(
155 "Action input parameter '{}' collides with keyword '{}' and may cause platform parse errors",
156 name, name
157 ),
158 span: Some(param.node.name.span.clone()),
159 severity: Severity::Warning,
160 hint: None,
161 });
162 }
163 _ => {}
164 }
165 }
166 }
167}