pharos_runtime_compiler/declarative.rs
1#![allow(clippy::too_many_arguments)]
2
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use serde_json::{Map, Value};
7
8/// Parses YAML file.
9fn parse_yaml_file(path: &Path) -> Option<Value> {
10 pharos_runtime_core::yaml::parse_yaml_file(path)
11}
12
13/// Builds source path.
14///
15/// # Parameters
16///
17/// - `base_dir`: Input consumed by [`crate::declarative::build_source_path`].
18/// - `rel_path`: Input consumed by [`crate::declarative::build_source_path`].
19/// - `ext`: Input consumed by [`crate::declarative::build_source_path`].
20///
21/// # Returns
22///
23/// Returns [`Some`] when [`crate::declarative::build_source_path`] can produce a value and [`None`] otherwise.
24///
25/// # Examples
26///
27/// ```rust,ignore
28/// let _result = pharos_runtime_compiler::declarative::build_source_path(std::path::Path::new("apps"), todo!(), todo!());
29/// ```
30///
31/// # Related
32///
33/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
34pub fn build_source_path(base_dir: &Path, rel_path: &str, ext: &str) -> Option<PathBuf> {
35 (!rel_path.is_empty() && !ext.is_empty()).then(|| base_dir.join(format!("{rel_path}{ext}")))
36}
37
38/// Loads YAML source.
39///
40/// # Parameters
41///
42/// - `base_dir`: Input consumed by [`crate::declarative::load_yaml_source`].
43/// - `rel_path`: Input consumed by [`crate::declarative::load_yaml_source`].
44///
45/// # Returns
46///
47/// Returns [`Some`] when [`crate::declarative::load_yaml_source`] can produce a value and [`None`] otherwise.
48///
49/// # Performance
50///
51/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::load_yaml_source`].
52///
53/// # Examples
54///
55/// ```rust,ignore
56/// let _result = pharos_runtime_compiler::declarative::load_yaml_source(std::path::Path::new("apps"), todo!());
57/// ```
58///
59/// # Related
60///
61/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
62pub fn load_yaml_source(base_dir: &Path, rel_path: &str) -> Option<Value> {
63 if rel_path.is_empty() {
64 return None;
65 }
66 let stem = rel_path
67 .strip_suffix(".yml")
68 .or_else(|| rel_path.strip_suffix(".yaml"))
69 .unwrap_or(rel_path);
70 let json_path = base_dir.join(format!("{stem}.json"));
71 if json_path.is_file() {
72 return serde_json::from_str(&fs::read_to_string(json_path).ok()?).ok();
73 }
74 let yaml_path = base_dir.join(format!("{stem}.yml"));
75 yaml_path
76 .is_file()
77 .then(|| parse_yaml_file(&yaml_path))
78 .flatten()
79}
80
81/// Performs declarative source load page decl.
82///
83/// # Parameters
84///
85/// - `base_dir`: Input consumed by [`crate::declarative::declarative_source_load_page_decl`].
86/// - `page_name`: Input consumed by [`crate::declarative::declarative_source_load_page_decl`].
87///
88/// # Returns
89///
90/// Returns [`Some`] when [`crate::declarative::declarative_source_load_page_decl`] can produce a value and [`None`] otherwise.
91///
92/// # Examples
93///
94/// ```rust,ignore
95/// let _result = pharos_runtime_compiler::declarative::declarative_source_load_page_decl(std::path::Path::new("apps"), todo!());
96/// ```
97///
98/// # Related
99///
100/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
101pub fn declarative_source_load_page_decl(base_dir: &Path, page_name: &str) -> Option<Value> {
102 if page_name.is_empty() {
103 return None;
104 }
105 load_yaml_source(base_dir, &format!("{page_name}.page"))
106}
107
108/// Performs declarative source load fixture.
109///
110/// # Parameters
111///
112/// - `base_dir`: Input consumed by [`crate::declarative::declarative_source_load_fixture`].
113/// - `rel_path`: Input consumed by [`crate::declarative::declarative_source_load_fixture`].
114///
115/// # Returns
116///
117/// Returns [`Some`] when [`crate::declarative::declarative_source_load_fixture`] can produce a value and [`None`] otherwise.
118///
119/// # Examples
120///
121/// ```rust,ignore
122/// let _result = pharos_runtime_compiler::declarative::declarative_source_load_fixture(std::path::Path::new("apps"), todo!());
123/// ```
124///
125/// # Related
126///
127/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
128pub fn declarative_source_load_fixture(base_dir: &Path, rel_path: &str) -> Option<Value> {
129 if rel_path.is_empty() {
130 return None;
131 }
132 if rel_path.ends_with(".json") {
133 let path = build_source_path(
134 base_dir,
135 rel_path.strip_suffix(".json").unwrap_or(rel_path),
136 ".json",
137 )?;
138 let metadata = fs::metadata(&path).ok()?;
139 if metadata.len() > 1024 * 1024 {
140 return None;
141 }
142 let text = fs::read_to_string(path).ok()?;
143 return serde_json::from_str(&text).ok();
144 }
145 load_yaml_source(base_dir, rel_path.strip_suffix(".yml").unwrap_or(rel_path))
146}
147
148/// Validates the minimum fields required by a declarative app manifest.
149///
150/// # Parameters
151///
152/// - `manifest_text`: Input consumed by [`crate::declarative::declarative_manifest_has_required_fields`].
153///
154/// # Returns
155///
156/// Returns `true` when the condition checked by [`crate::declarative::declarative_manifest_has_required_fields`] holds and `false` otherwise.
157///
158/// # Performance
159///
160/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_manifest_has_required_fields`].
161///
162/// # Examples
163///
164/// ```rust,ignore
165/// let _result = pharos_runtime_compiler::declarative::declarative_manifest_has_required_fields(todo!());
166/// ```
167///
168/// # Related
169///
170/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
171pub fn declarative_manifest_has_required_fields(manifest_text: &str) -> bool {
172 let Ok(root) = serde_json::from_str::<Value>(manifest_text) else {
173 return false;
174 };
175 let Some(object) = root.as_object() else {
176 return false;
177 };
178 ["app_id", "name", "version"].iter().all(|key| {
179 object
180 .get(*key)
181 .and_then(Value::as_str)
182 .is_some_and(|value| !value.trim().is_empty())
183 })
184}
185
186/// Loads the declarative manifest text from an app root with a bounded read.
187///
188/// # Parameters
189///
190/// - `app_root`: Input consumed by [`crate::declarative::declarative_source_load_manifest`].
191///
192/// # Returns
193///
194/// Returns [`Some`] when [`crate::declarative::declarative_source_load_manifest`] can produce a value and [`None`] otherwise.
195///
196/// # Performance
197///
198/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_load_manifest`].
199///
200/// # Examples
201///
202/// ```rust,ignore
203/// let _result = pharos_runtime_compiler::declarative::declarative_source_load_manifest(std::path::Path::new("apps/dev_docs"));
204/// ```
205///
206/// # Related
207///
208/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
209pub fn declarative_source_load_manifest(app_root: &Path) -> Option<String> {
210 let path = app_root.join("pharos.app.json");
211 let metadata = fs::metadata(&path).ok()?;
212 (metadata.len() <= 1024 * 1024)
213 .then(|| fs::read_to_string(path).ok())
214 .flatten()
215 .filter(|text| declarative_manifest_has_required_fields(text))
216}
217
218/// Performs declarative source has YAML page.
219///
220/// # Parameters
221///
222/// - `base_dir`: Input consumed by [`crate::declarative::declarative_source_has_yaml_page`].
223/// - `page_name`: Input consumed by [`crate::declarative::declarative_source_has_yaml_page`].
224///
225/// # Returns
226///
227/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_has_yaml_page`] holds and `false` otherwise.
228///
229/// # Examples
230///
231/// ```rust,ignore
232/// let _result = pharos_runtime_compiler::declarative::declarative_source_has_yaml_page(std::path::Path::new("apps"), todo!());
233/// ```
234///
235/// # Related
236///
237/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
238pub fn declarative_source_has_yaml_page(base_dir: &Path, page_name: &str) -> bool {
239 if page_name.is_empty() {
240 return false;
241 }
242 build_source_path(base_dir, &format!("{page_name}.page"), ".yml")
243 .is_some_and(|path| path.exists())
244}
245
246/// Performs declarative source has YAML fixture.
247///
248/// # Parameters
249///
250/// - `base_dir`: Input consumed by [`crate::declarative::declarative_source_has_yaml_fixture`].
251/// - `rel_path`: Input consumed by [`crate::declarative::declarative_source_has_yaml_fixture`].
252///
253/// # Returns
254///
255/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_has_yaml_fixture`] holds and `false` otherwise.
256///
257/// # Examples
258///
259/// ```rust,ignore
260/// let _result = pharos_runtime_compiler::declarative::declarative_source_has_yaml_fixture(std::path::Path::new("apps"), todo!());
261/// ```
262///
263/// # Related
264///
265/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
266pub fn declarative_source_has_yaml_fixture(base_dir: &Path, rel_path: &str) -> bool {
267 if rel_path.is_empty() {
268 return false;
269 }
270 build_source_path(base_dir, rel_path, ".yml").is_some_and(|path| path.exists())
271}
272
273/// Finds the first object in an array whose named field equals the expected string.
274///
275/// # Parameters
276///
277/// - `array_value`: Input consumed by [`crate::declarative::declarative_source_find_object_by_string_field`].
278/// - `field_name`: Input consumed by [`crate::declarative::declarative_source_find_object_by_string_field`].
279/// - `expected_value`: Input consumed by [`crate::declarative::declarative_source_find_object_by_string_field`].
280///
281/// # Returns
282///
283/// Returns [`Some`] when [`crate::declarative::declarative_source_find_object_by_string_field`] can produce a value and [`None`] otherwise.
284///
285/// # Performance
286///
287/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_find_object_by_string_field`].
288///
289/// # Examples
290///
291/// ```rust,ignore
292/// let _result = pharos_runtime_compiler::declarative::declarative_source_find_object_by_string_field(todo!(), todo!(), todo!());
293/// ```
294///
295/// # Related
296///
297/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
298pub fn declarative_source_find_object_by_string_field<'a>(
299 array_value: &'a Value,
300 field_name: &str,
301 expected_value: &str,
302) -> Option<&'a Value> {
303 array_value
304 .as_array()?
305 .iter()
306 .find(|item| item.get(field_name).and_then(Value::as_str) == Some(expected_value))
307}
308
309/// Performs declarative source route path to surface path.
310///
311/// # Parameters
312///
313/// - `route_path`: Input consumed by [`crate::declarative::declarative_source_route_path_to_surface_path`].
314///
315/// # Returns
316///
317/// Returns [`Some`] when [`crate::declarative::declarative_source_route_path_to_surface_path`] can produce a value and [`None`] otherwise.
318///
319/// # Performance
320///
321/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_route_path_to_surface_path`].
322///
323/// # Examples
324///
325/// ```rust,ignore
326/// let _result = pharos_runtime_compiler::declarative::declarative_source_route_path_to_surface_path(todo!());
327/// ```
328///
329/// # Related
330///
331/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
332pub fn declarative_source_route_path_to_surface_path(route_path: &str) -> Option<String> {
333 if route_path.is_empty() {
334 return Some(String::new());
335 }
336 let mut result = String::with_capacity(route_path.len());
337 let chars: Vec<char> = route_path.chars().collect();
338 let mut index = 0usize;
339 while index < chars.len() {
340 if chars[index] == ':' {
341 index += 1;
342 result.push('{');
343 while index < chars.len() && chars[index] != '/' {
344 result.push(chars[index]);
345 index += 1;
346 }
347 result.push('}');
348 continue;
349 }
350 result.push(chars[index]);
351 index += 1;
352 }
353 Some(result)
354}
355
356/// Performs declarative source copy optional member.
357///
358/// # Parameters
359///
360/// - `target_obj`: Input consumed by [`crate::declarative::declarative_source_copy_optional_member`].
361/// - `source_obj`: Input consumed by [`crate::declarative::declarative_source_copy_optional_member`].
362/// - `key`: Input consumed by [`crate::declarative::declarative_source_copy_optional_member`].
363///
364/// # Returns
365///
366/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_copy_optional_member`] holds and `false` otherwise.
367///
368/// # Examples
369///
370/// ```rust,ignore
371/// let _result = pharos_runtime_compiler::declarative::declarative_source_copy_optional_member(todo!(), todo!(), "key");
372/// ```
373///
374/// # Related
375///
376/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
377pub fn declarative_source_copy_optional_member(
378 target_obj: &mut Map<String, Value>,
379 source_obj: &Value,
380 key: &str,
381) -> bool {
382 if key.is_empty() {
383 return false;
384 }
385 if let Some(value) = source_obj.as_object().and_then(|map| map.get(key)) {
386 target_obj.insert(key.to_owned(), value.clone());
387 }
388 true
389}
390
391/// Performs declarative source array has string.
392///
393/// # Parameters
394///
395/// - `array_value`: Input consumed by [`crate::declarative::declarative_source_array_has_string`].
396/// - `expected_value`: Input consumed by [`crate::declarative::declarative_source_array_has_string`].
397///
398/// # Returns
399///
400/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_array_has_string`] holds and `false` otherwise.
401///
402/// # Performance
403///
404/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_array_has_string`].
405///
406/// # Examples
407///
408/// ```rust,ignore
409/// let _result = pharos_runtime_compiler::declarative::declarative_source_array_has_string(todo!(), todo!());
410/// ```
411///
412/// # Related
413///
414/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
415pub fn declarative_source_array_has_string(array_value: &Value, expected_value: &str) -> bool {
416 !expected_value.is_empty()
417 && array_value.as_array().is_some_and(|items| {
418 items
419 .iter()
420 .any(|item| item.as_str() == Some(expected_value))
421 })
422}
423
424/// Performs API prefixed route path.
425///
426/// # Parameters
427///
428/// - `route_path`: Input consumed by [`crate::declarative::api_prefixed_route_path`].
429///
430/// # Returns
431///
432/// Returns [`Some`] when [`crate::declarative::api_prefixed_route_path`] can produce a value and [`None`] otherwise.
433///
434/// # Examples
435///
436/// ```rust,ignore
437/// let _result = pharos_runtime_compiler::declarative::api_prefixed_route_path(todo!());
438/// ```
439///
440/// # Related
441///
442/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
443pub fn api_prefixed_route_path(route_path: &str) -> Option<String> {
444 Some(format!("/api{route_path}"))
445}
446
447/// Performs declarative source build operation mut.
448///
449/// # Parameters
450///
451/// - `method`: Input consumed by [`crate::declarative::declarative_source_build_operation_mut`].
452/// - `path`: Input consumed by [`crate::declarative::declarative_source_build_operation_mut`].
453/// - `capability`: Input consumed by [`crate::declarative::declarative_source_build_operation_mut`].
454///
455/// # Returns
456///
457/// Returns [`Some`] when [`crate::declarative::declarative_source_build_operation_mut`] can produce a value and [`None`] otherwise.
458///
459/// # Examples
460///
461/// ```rust,ignore
462/// let _result = pharos_runtime_compiler::declarative::declarative_source_build_operation_mut(todo!(), std::path::Path::new("fixture.json"), todo!());
463/// ```
464///
465/// # Related
466///
467/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
468pub fn declarative_source_build_operation_mut(
469 method: &str,
470 path: &str,
471 capability: &str,
472) -> Option<Value> {
473 if method.is_empty() || path.is_empty() || capability.is_empty() {
474 return None;
475 }
476 let surface_path = declarative_source_route_path_to_surface_path(path)?;
477 Some(serde_json::json!({
478 "method": method,
479 "path": surface_path,
480 "capability": capability,
481 }))
482}
483
484/// Performs declarative source build route operation mut.
485///
486/// # Parameters
487///
488/// - `route_root`: Input consumed by [`crate::declarative::declarative_source_build_route_operation_mut`].
489///
490/// # Returns
491///
492/// Returns [`Some`] when [`crate::declarative::declarative_source_build_route_operation_mut`] can produce a value and [`None`] otherwise.
493///
494/// # Examples
495///
496/// ```rust,ignore
497/// let _result = pharos_runtime_compiler::declarative::declarative_source_build_route_operation_mut(todo!());
498/// ```
499///
500/// # Related
501///
502/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
503pub fn declarative_source_build_route_operation_mut(route_root: &Value) -> Option<Value> {
504 let kind = route_root.get("kind").and_then(Value::as_str)?;
505 let path = route_root.get("path").and_then(Value::as_str)?;
506 let capability = route_root.get("policy_gate").and_then(Value::as_str)?;
507 let method = if kind == "page" { "GET" } else { "POST" };
508 declarative_source_build_operation_mut(method, path, capability)
509}
510
511/// Performs declarative source identity param.
512///
513/// # Parameters
514///
515/// - `edit_path`: Input consumed by [`crate::declarative::declarative_source_identity_param`].
516/// - `context_param`: Input consumed by [`crate::declarative::declarative_source_identity_param`].
517///
518/// # Returns
519///
520/// Returns [`Some`] when [`crate::declarative::declarative_source_identity_param`] can produce a value and [`None`] otherwise.
521///
522/// # Examples
523///
524/// ```rust,ignore
525/// let _result = pharos_runtime_compiler::declarative::declarative_source_identity_param(todo!(), todo!());
526/// ```
527///
528/// # Related
529///
530/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
531pub fn declarative_source_identity_param(
532 edit_path: &str,
533 context_param: Option<&str>,
534) -> Option<String> {
535 if edit_path.is_empty() {
536 return None;
537 }
538 let mut result = None;
539 let bytes = edit_path.as_bytes();
540 let mut index = 0usize;
541 while index < bytes.len() {
542 if bytes[index] != b':' {
543 index += 1;
544 continue;
545 }
546 let start = index + 1;
547 let mut end = start;
548 while end < bytes.len() && bytes[end] != b'/' {
549 end += 1;
550 }
551 if end > start {
552 let candidate = &edit_path[start..end];
553 if context_param != Some(candidate) {
554 result = Some(candidate.to_owned());
555 }
556 }
557 index = end;
558 }
559 result
560}
561
562/// Converts an identifier into the human-readable label used by generated surfaces.
563///
564/// # Parameters
565///
566/// - `identifier`: Input consumed by [`crate::declarative::declarative_source_label`].
567/// - `title_case`: Input consumed by [`crate::declarative::declarative_source_label`].
568///
569/// # Returns
570///
571/// Returns the computed value produced by [`crate::declarative::declarative_source_label`].
572///
573/// # Performance
574///
575/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_label`].
576///
577/// # Examples
578///
579/// ```rust,ignore
580/// let _result = pharos_runtime_compiler::declarative::declarative_source_label(todo!(), todo!());
581/// ```
582///
583/// # Related
584///
585/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
586pub fn declarative_source_label(identifier: &str, title_case: bool) -> String {
587 let mut next_word = true;
588 identifier
589 .chars()
590 .map(|ch| {
591 let mut out = if ch == '_' { ' ' } else { ch };
592 if title_case && next_word && out.is_ascii_lowercase() {
593 out = out.to_ascii_uppercase();
594 }
595 next_word = out == ' ';
596 out
597 })
598 .collect()
599}
600
601/// Returns the default item title field.
602///
603/// # Parameters
604///
605/// - `entity_fields`: Input consumed by [`crate::declarative::default_item_title_field`].
606/// - `identity_field`: Input consumed by [`crate::declarative::default_item_title_field`].
607/// - `scope_field`: Input consumed by [`crate::declarative::default_item_title_field`].
608///
609/// # Returns
610///
611/// Returns [`Some`] when [`crate::declarative::default_item_title_field`] can produce a value and [`None`] otherwise.
612///
613/// # Performance
614///
615/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::default_item_title_field`].
616///
617/// # Examples
618///
619/// ```rust,ignore
620/// let _result = pharos_runtime_compiler::declarative::default_item_title_field(todo!(), todo!(), todo!());
621/// ```
622///
623/// # Related
624///
625/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
626pub fn default_item_title_field(
627 entity_fields: &Value,
628 identity_field: Option<&str>,
629 scope_field: Option<&str>,
630) -> Option<String> {
631 let preferred = ["name", "title", "label", "slug", "day_of_week"];
632 let fields = entity_fields.as_array()?;
633 for candidate in preferred {
634 if fields
635 .iter()
636 .any(|item| item.get("field_id").and_then(Value::as_str) == Some(candidate))
637 {
638 return Some(candidate.to_owned());
639 }
640 }
641 for item in fields {
642 let Some(field_id) = item.get("field_id").and_then(Value::as_str) else {
643 continue;
644 };
645 if Some(field_id) == identity_field || Some(field_id) == scope_field {
646 continue;
647 }
648 return Some(field_id.to_owned());
649 }
650 Some(identity_field.unwrap_or("id").to_owned())
651}
652
653/// Performs declarative source default list columns mut.
654///
655/// # Parameters
656///
657/// - `response_fields`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_mut`].
658/// - `identity_field`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_mut`].
659/// - `scope_field`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_mut`].
660///
661/// # Returns
662///
663/// Returns [`Some`] when [`crate::declarative::declarative_source_default_list_columns_mut`] can produce a value and [`None`] otherwise.
664///
665/// # Performance
666///
667/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_default_list_columns_mut`].
668///
669/// # Examples
670///
671/// ```rust,ignore
672/// let _result = pharos_runtime_compiler::declarative::declarative_source_default_list_columns_mut(todo!(), todo!(), todo!());
673/// ```
674///
675/// # Related
676///
677/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
678pub fn declarative_source_default_list_columns_mut(
679 response_fields: &Value,
680 identity_field: Option<&str>,
681 scope_field: Option<&str>,
682) -> Option<Value> {
683 let fields = response_fields.as_array()?;
684 let mut columns = Vec::new();
685 for item in fields {
686 let Some(field_name) = item.as_str() else {
687 continue;
688 };
689 if Some(field_name) == identity_field || Some(field_name) == scope_field {
690 continue;
691 }
692 columns.push(serde_json::json!({
693 "field": field_name,
694 "label": declarative_source_label(field_name, true),
695 }));
696 if columns.len() >= 3 {
697 break;
698 }
699 }
700 Some(Value::Array(columns))
701}
702
703/// Performs declarative source default list columns from form mut.
704///
705/// # Parameters
706///
707/// - `form_fields`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_from_form_mut`].
708/// - `relation_inputs`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_from_form_mut`].
709/// - `identity_field`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_from_form_mut`].
710/// - `scope_field`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_from_form_mut`].
711/// - `soft_delete_field`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_from_form_mut`].
712/// - `auto_sequence_field`: Input consumed by [`crate::declarative::declarative_source_default_list_columns_from_form_mut`].
713///
714/// # Returns
715///
716/// Returns [`Some`] when [`crate::declarative::declarative_source_default_list_columns_from_form_mut`] can produce a value and [`None`] otherwise.
717///
718/// # Performance
719///
720/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_default_list_columns_from_form_mut`].
721///
722/// # Examples
723///
724/// ```rust,ignore
725/// let _result = pharos_runtime_compiler::declarative::declarative_source_default_list_columns_from_form_mut(todo!(), todo!(), todo!(), todo!(), todo!(), todo!());
726/// ```
727///
728/// # Related
729///
730/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
731pub fn declarative_source_default_list_columns_from_form_mut(
732 form_fields: Option<&Value>,
733 relation_inputs: Option<&Value>,
734 identity_field: Option<&str>,
735 scope_field: Option<&str>,
736 soft_delete_field: Option<&str>,
737 auto_sequence_field: Option<&str>,
738) -> Option<Value> {
739 let mut columns = Vec::new();
740 if let Some(fields) = form_fields.and_then(Value::as_array) {
741 for item in fields {
742 let Some(field_name) = item.get("field_id").and_then(Value::as_str) else {
743 continue;
744 };
745 if Some(field_name) == identity_field || Some(field_name) == scope_field {
746 continue;
747 }
748 columns.push(serde_json::json!({
749 "field": field_name,
750 "label": declarative_source_label(field_name, true),
751 }));
752 if columns.len() >= 3 {
753 return Some(Value::Array(columns));
754 }
755 }
756 }
757 if let Some(field_name) = auto_sequence_field.filter(|value| !value.is_empty())
758 && columns.len() < 3
759 {
760 columns.push(serde_json::json!({
761 "field": field_name,
762 "label": declarative_source_label(field_name, true),
763 }));
764 if columns.len() >= 3 {
765 return Some(Value::Array(columns));
766 }
767 }
768 if let Some(field_name) = soft_delete_field.filter(|value| !value.is_empty())
769 && columns.len() < 3
770 {
771 columns.push(serde_json::json!({
772 "field": field_name,
773 "label": declarative_source_label(field_name, true),
774 }));
775 if columns.len() >= 3 {
776 return Some(Value::Array(columns));
777 }
778 }
779 if let Some(inputs) = relation_inputs.and_then(Value::as_array) {
780 for item in inputs {
781 let Some(field_name) = item.get("field").and_then(Value::as_str) else {
782 continue;
783 };
784 columns.push(serde_json::json!({
785 "field": field_name,
786 "label": declarative_source_label(field_name, true),
787 }));
788 if columns.len() >= 3 {
789 break;
790 }
791 }
792 }
793 Some(Value::Array(columns))
794}
795
796/// Performs declarative source default context binding mut.
797///
798/// # Parameters
799///
800/// - `scope_field`: Input consumed by [`crate::declarative::declarative_source_default_context_binding_mut`].
801///
802/// # Returns
803///
804/// Returns [`Some`] when [`crate::declarative::declarative_source_default_context_binding_mut`] can produce a value and [`None`] otherwise.
805///
806/// # Examples
807///
808/// ```rust,ignore
809/// let _result = pharos_runtime_compiler::declarative::declarative_source_default_context_binding_mut(todo!());
810/// ```
811///
812/// # Related
813///
814/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
815pub fn declarative_source_default_context_binding_mut(scope_field: Option<&str>) -> Option<Value> {
816 if scope_field != Some("business_id") {
817 return None;
818 }
819 Some(serde_json::json!({
820 "state_collection": "businesses",
821 "param": "business_slug",
822 "match_field": "slug",
823 "context_id_field": "id",
824 "context_label_field": "name",
825 "tenant_collection": "tenants",
826 "tenant_match_field": "source_business_id",
827 "tenant_match_from_context_field": "id",
828 }))
829}
830
831/// Performs declarative source derived defaults mut.
832///
833/// # Parameters
834///
835/// - `form_fields`: Input consumed by [`crate::declarative::declarative_source_derived_defaults_mut`].
836/// - `relation_inputs`: Input consumed by [`crate::declarative::declarative_source_derived_defaults_mut`].
837/// - `soft_delete_field`: Input consumed by [`crate::declarative::declarative_source_derived_defaults_mut`].
838///
839/// # Returns
840///
841/// Returns [`Some`] when [`crate::declarative::declarative_source_derived_defaults_mut`] can produce a value and [`None`] otherwise.
842///
843/// # Performance
844///
845/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derived_defaults_mut`].
846///
847/// # Examples
848///
849/// ```rust,ignore
850/// let _result = pharos_runtime_compiler::declarative::declarative_source_derived_defaults_mut(todo!(), todo!(), todo!());
851/// ```
852///
853/// # Related
854///
855/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
856pub fn declarative_source_derived_defaults_mut(
857 form_fields: Option<&Value>,
858 relation_inputs: Option<&Value>,
859 soft_delete_field: Option<&str>,
860) -> Option<Value> {
861 let mut defaults = Map::new();
862 if let Some(fields) = form_fields.and_then(Value::as_array) {
863 for item in fields {
864 let Some(field_name) = item.get("field_id").and_then(Value::as_str) else {
865 continue;
866 };
867 let widget = item.get("widget").and_then(Value::as_str);
868 let required = item
869 .get("required")
870 .and_then(Value::as_bool)
871 .unwrap_or(false);
872 if matches!(widget, Some("textarea" | "text" | "email" | "password")) {
873 defaults.insert(field_name.to_owned(), Value::String(String::new()));
874 continue;
875 }
876 if required {
877 continue;
878 }
879 }
880 }
881 if let Some(soft_delete_field) = soft_delete_field.filter(|value| !value.is_empty()) {
882 defaults.insert(soft_delete_field.to_owned(), Value::Bool(true));
883 }
884 if let Some(inputs) = relation_inputs.and_then(Value::as_array) {
885 for item in inputs {
886 if let Some(field_name) = item.get("field").and_then(Value::as_str)
887 && !field_name.is_empty()
888 {
889 defaults.insert(field_name.to_owned(), Value::Array(Vec::new()));
890 }
891 }
892 }
893 Some(Value::Object(defaults))
894}
895
896/// Performs declarative source derived audit events mut.
897///
898/// # Parameters
899///
900/// - `entity_id`: Input consumed by [`crate::declarative::declarative_source_derived_audit_events_mut`].
901/// - `hard_delete`: Input consumed by [`crate::declarative::declarative_source_derived_audit_events_mut`].
902///
903/// # Returns
904///
905/// Returns [`Some`] when [`crate::declarative::declarative_source_derived_audit_events_mut`] can produce a value and [`None`] otherwise.
906///
907/// # Examples
908///
909/// ```rust,ignore
910/// let _result = pharos_runtime_compiler::declarative::declarative_source_derived_audit_events_mut("entity", todo!());
911/// ```
912///
913/// # Related
914///
915/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
916pub fn declarative_source_derived_audit_events_mut(
917 entity_id: &str,
918 hard_delete: bool,
919) -> Option<Value> {
920 if entity_id.is_empty() {
921 return None;
922 }
923 Some(serde_json::json!({
924 "create": format!("generated_{entity_id}_created"),
925 "update": format!("generated_{entity_id}_updated"),
926 "delete": if hard_delete {
927 format!("generated_{entity_id}_deleted")
928 } else {
929 format!("generated_{entity_id}_deactivated")
930 },
931 }))
932}
933
934const MAX_DECLARATIVE_FILE_BYTES: u64 = 16 * 1024 * 1024;
935const MAX_DECLARATIVE_FILES: usize = 4096;
936
937/// Builds a safe relative path.
938fn safe_relative_path(text: &str) -> Option<&Path> {
939 let path = Path::new(text);
940 if text.is_empty()
941 || path.is_absolute()
942 || path.components().any(|component| {
943 matches!(
944 component,
945 std::path::Component::ParentDir
946 | std::path::Component::RootDir
947 | std::path::Component::Prefix(_)
948 )
949 })
950 {
951 return None;
952 }
953 Some(path)
954}
955
956/// Reads JSON or YAML.
957fn read_json_or_yaml(path: &Path) -> Option<Value> {
958 let metadata = fs::metadata(path).ok()?;
959 if !metadata.is_file() || metadata.len() > MAX_DECLARATIVE_FILE_BYTES {
960 return None;
961 }
962 let text = fs::read_to_string(path).ok()?;
963 if path.extension().and_then(|value| value.to_str()) == Some("yml")
964 || path.extension().and_then(|value| value.to_str()) == Some("yaml")
965 {
966 pharos_runtime_core::yaml::parse_yaml_text(&text)
967 } else {
968 serde_json::from_str(&text).ok()
969 }
970}
971
972/// Retains exact scalar declaration locations for generated JSON diagnostics.
973fn attach_yaml_source_map(value: &mut Value, text: &str, source_path: &Path) -> Option<()> {
974 let object = value.as_object_mut()?;
975 let mut locations = Map::new();
976 for (index, line) in text.lines().enumerate() {
977 let trimmed = line.trim_start().trim_start_matches("- ");
978 let Some((key, raw_value)) = trimmed.split_once(':') else {
979 continue;
980 };
981 let scalar = raw_value.trim().trim_matches(['\'', '"']);
982 if scalar.is_empty()
983 || !matches!(
984 key.trim(),
985 "route_id"
986 | "action_id"
987 | "entity_id"
988 | "form_id"
989 | "workflow_id"
990 | "adapter_id"
991 | "page_id"
992 )
993 {
994 continue;
995 }
996 let column = line.find(key.trim()).unwrap_or(0) + 1;
997 locations.insert(
998 format!("{}:{scalar}", key.trim()),
999 serde_json::json!({"line": index + 1, "column": column}),
1000 );
1001 }
1002 object.insert(
1003 "__pharos_source".to_owned(),
1004 serde_json::json!({
1005 "path": source_path.to_string_lossy(),
1006 "locations": locations,
1007 }),
1008 );
1009 Some(())
1010}
1011
1012/// Builds a safe path to one framework-owned contract file.
1013///
1014/// # Parameters
1015///
1016/// - `artifact_dir`: Input consumed by [`crate::declarative::declarative_source_framework_path`].
1017/// - `file_name`: Input consumed by [`crate::declarative::declarative_source_framework_path`].
1018///
1019/// # Returns
1020///
1021/// Returns [`Some`] when [`crate::declarative::declarative_source_framework_path`] can produce a value and [`None`] otherwise.
1022///
1023/// # Examples
1024///
1025/// ```rust,ignore
1026/// let _result = pharos_runtime_compiler::declarative::declarative_source_framework_path(std::path::Path::new("build/dev_docs"), todo!());
1027/// ```
1028///
1029/// # Related
1030///
1031/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1032pub fn declarative_source_framework_path(artifact_dir: &Path, file_name: &str) -> Option<PathBuf> {
1033 let relative = safe_relative_path(file_name)?;
1034 Some(artifact_dir.join("data/contracts").join(relative))
1035}
1036
1037/// Reads framework contract.
1038fn read_framework_contract(artifact_dir: &Path, file_name: &str) -> Option<Value> {
1039 let path = declarative_source_framework_path(artifact_dir, file_name)?;
1040 read_json_or_yaml(&path)
1041}
1042
1043/// Resolves a dot-separated object or array path without creating intermediate values.
1044///
1045/// # Parameters
1046///
1047/// - `root`: Input consumed by [`crate::declarative::declarative_source_resolve_dotted_path`].
1048/// - `dotted_path`: Input consumed by [`crate::declarative::declarative_source_resolve_dotted_path`].
1049///
1050/// # Returns
1051///
1052/// Returns [`Some`] when [`crate::declarative::declarative_source_resolve_dotted_path`] can produce a value and [`None`] otherwise.
1053///
1054/// # Performance
1055///
1056/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_resolve_dotted_path`].
1057///
1058/// # Examples
1059///
1060/// ```rust,ignore
1061/// let _result = pharos_runtime_compiler::declarative::declarative_source_resolve_dotted_path(std::path::Path::new("."), todo!());
1062/// ```
1063///
1064/// # Related
1065///
1066/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1067pub fn declarative_source_resolve_dotted_path<'a>(
1068 root: &'a Value,
1069 dotted_path: &str,
1070) -> Option<&'a Value> {
1071 if dotted_path.is_empty() {
1072 return None;
1073 }
1074 dotted_path.split('.').try_fold(root, |current, segment| {
1075 if segment.is_empty() {
1076 None
1077 } else if let Ok(index) = segment.parse::<usize>() {
1078 current.as_array()?.get(index)
1079 } else {
1080 current.as_object()?.get(segment)
1081 }
1082 })
1083}
1084
1085/// Loads a bounded framework contract reference such as `fields.yml#entity.fields`.
1086///
1087/// # Parameters
1088///
1089/// - `artifact_dir`: Input consumed by [`crate::declarative::declarative_source_load_ref_target`].
1090/// - `ref_text`: Input consumed by [`crate::declarative::declarative_source_load_ref_target`].
1091///
1092/// # Returns
1093///
1094/// Returns [`Some`] when [`crate::declarative::declarative_source_load_ref_target`] can produce a value and [`None`] otherwise.
1095///
1096/// # Examples
1097///
1098/// ```rust,ignore
1099/// let _result = pharos_runtime_compiler::declarative::declarative_source_load_ref_target(std::path::Path::new("build/dev_docs"), todo!());
1100/// ```
1101///
1102/// # Related
1103///
1104/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1105pub fn declarative_source_load_ref_target(artifact_dir: &Path, ref_text: &str) -> Option<Value> {
1106 let (file_name, dotted_path) = ref_text.split_once('#')?;
1107 let file_name = if file_name.ends_with(".json") {
1108 file_name.to_owned()
1109 } else {
1110 let stem = file_name
1111 .strip_suffix(".yml")
1112 .or_else(|| file_name.strip_suffix(".yaml"))
1113 .unwrap_or(file_name);
1114 format!("{stem}.json")
1115 };
1116 let root = read_framework_contract(artifact_dir, &file_name)?;
1117 Some(declarative_source_resolve_dotted_path(&root, dotted_path)?.clone())
1118}
1119
1120/// Returns whether the YAML app manifest exists at an app root.
1121///
1122/// # Parameters
1123///
1124/// - `app_root`: Input consumed by [`crate::declarative::declarative_source_has_yaml_manifest`].
1125///
1126/// # Returns
1127///
1128/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_has_yaml_manifest`] holds and `false` otherwise.
1129///
1130/// # Examples
1131///
1132/// ```rust,ignore
1133/// let _result = pharos_runtime_compiler::declarative::declarative_source_has_yaml_manifest(std::path::Path::new("apps/dev_docs"));
1134/// ```
1135///
1136/// # Related
1137///
1138/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1139pub fn declarative_source_has_yaml_manifest(app_root: &Path) -> bool {
1140 app_root.join("pharos.app.yml").is_file()
1141}
1142
1143/// Resolves the source manifest-selected database contract for scoped compiler policy.
1144fn database_authoring_path(root: &Path) -> Option<PathBuf> {
1145 let manifest = pharos_runtime_core::yaml::parse_yaml_file(&root.join("pharos.app.yml"))?;
1146 let selected = manifest.pointer("/database/migration_contract")?.as_str()?;
1147 let relative = Path::new(selected);
1148 if !relative
1149 .components()
1150 .all(|component| matches!(component, std::path::Component::Normal(_)))
1151 {
1152 return None;
1153 }
1154 Some(root.join(relative))
1155}
1156
1157/// Compiles YAML matches in directory inner.
1158fn compile_yaml_matches_in_directory_inner(
1159 root: &Path,
1160 dir_path: &Path,
1161 yaml_suffix: &str,
1162 depth: usize,
1163 count: &mut usize,
1164 database_path: Option<&Path>,
1165) -> i32 {
1166 if depth > 64 {
1167 return 69;
1168 }
1169 let Ok(entries) = fs::read_dir(dir_path) else {
1170 return 0;
1171 };
1172 for entry in entries {
1173 let Ok(entry) = entry else {
1174 return 69;
1175 };
1176 let path = entry.path();
1177 if path.is_dir() {
1178 let code = compile_yaml_matches_in_directory_inner(
1179 root,
1180 &path,
1181 yaml_suffix,
1182 depth + 1,
1183 count,
1184 database_path,
1185 );
1186 if code != 0 {
1187 return code;
1188 }
1189 continue;
1190 }
1191 let Some(name) = path.file_name().and_then(|value| value.to_str()) else {
1192 continue;
1193 };
1194 let database_source = database_path == Some(path.as_path());
1195 if !name.ends_with(yaml_suffix)
1196 && !(yaml_suffix == ".yml" && database_source && name.ends_with(".yaml"))
1197 {
1198 continue;
1199 }
1200 *count = count.saturating_add(1);
1201 if *count > MAX_DECLARATIVE_FILES {
1202 return 69;
1203 }
1204 let parsed = if database_source {
1205 crate::database::compile_file(&path).ok()
1206 } else {
1207 read_json_or_yaml(&path)
1208 };
1209 let Some(mut value) = parsed else {
1210 return 69;
1211 };
1212 let Ok(yaml_text) = fs::read_to_string(&path) else {
1213 return 69;
1214 };
1215 let relative_source = path.strip_prefix(root).unwrap_or(&path);
1216 if !database_source
1217 && attach_yaml_source_map(&mut value, &yaml_text, relative_source).is_none()
1218 {
1219 return 69;
1220 }
1221 let output = path.with_extension("json");
1222 let Ok(text) = serde_json::to_string_pretty(&value) else {
1223 return 69;
1224 };
1225 if database_source && text.len() > 8 * 1024 * 1024 {
1226 return 69;
1227 }
1228 if pharos_runtime_core::fs::write_binary_file(&output, text.as_bytes(), 0o644).is_err() {
1229 return 69;
1230 }
1231 }
1232 0
1233}
1234
1235/// Compiles matching YAML files to sibling JSON artifacts without invoking a toolchain.
1236///
1237/// # Parameters
1238///
1239/// - `dir_path`: Input consumed by [`crate::declarative::compile_yaml_matches_in_directory`].
1240/// - `yaml_suffix`: Input consumed by [`crate::declarative::compile_yaml_matches_in_directory`].
1241///
1242/// # Returns
1243///
1244/// Returns the computed value produced by [`crate::declarative::compile_yaml_matches_in_directory`].
1245///
1246/// # Examples
1247///
1248/// ```rust,ignore
1249/// let _result = pharos_runtime_compiler::declarative::compile_yaml_matches_in_directory(todo!(), todo!());
1250/// ```
1251///
1252/// # Related
1253///
1254/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1255pub fn compile_yaml_matches_in_directory(dir_path: &Path, yaml_suffix: &str) -> i32 {
1256 if dir_path.as_os_str().is_empty() || yaml_suffix.is_empty() {
1257 return 69;
1258 }
1259 let mut count = 0;
1260 let database_path = database_authoring_path(dir_path);
1261 let manifest = if dir_path.join("pharos.app.yml").is_file() {
1262 let Some(manifest) =
1263 pharos_runtime_core::yaml::parse_yaml_file(&dir_path.join("pharos.app.yml"))
1264 else {
1265 return 69;
1266 };
1267 Some(manifest)
1268 } else {
1269 None
1270 };
1271 if yaml_suffix == ".yml"
1272 && manifest
1273 .as_ref()
1274 .is_some_and(|value| value.pointer("/database/migration_contract").is_some())
1275 && database_path.as_ref().is_none_or(|path| {
1276 !matches!(
1277 path.extension().and_then(|value| value.to_str()),
1278 Some("json" | "yml" | "yaml")
1279 ) || !fs::symlink_metadata(path).is_ok_and(|meta| meta.is_file())
1280 })
1281 {
1282 return 69;
1283 }
1284 let status = compile_yaml_matches_in_directory_inner(
1285 dir_path,
1286 dir_path,
1287 yaml_suffix,
1288 0,
1289 &mut count,
1290 database_path.as_deref(),
1291 );
1292 if status != 0 || yaml_suffix != ".yml" {
1293 return status;
1294 }
1295 if let Some(path) = database_path {
1296 let Some(app_id) = manifest
1297 .as_ref()
1298 .and_then(|value| value.get("app_id"))
1299 .and_then(Value::as_str)
1300 else {
1301 return 69;
1302 };
1303 if pharos_runtime_database::contract::load_contract(&path.with_extension("json"), app_id)
1304 .is_err()
1305 {
1306 return 69;
1307 }
1308 }
1309 0
1310}
1311
1312/// Performs declarative source compile pages in directory.
1313///
1314/// # Parameters
1315///
1316/// - `dir_path`: Input consumed by [`crate::declarative::declarative_source_compile_pages_in_directory`].
1317///
1318/// # Returns
1319///
1320/// Returns the computed value produced by [`crate::declarative::declarative_source_compile_pages_in_directory`].
1321///
1322/// # Examples
1323///
1324/// ```rust,ignore
1325/// let _result = pharos_runtime_compiler::declarative::declarative_source_compile_pages_in_directory(todo!());
1326/// ```
1327///
1328/// # Related
1329///
1330/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1331pub fn declarative_source_compile_pages_in_directory(dir_path: &Path) -> i32 {
1332 compile_yaml_matches_in_directory(dir_path, ".page.yml")
1333}
1334
1335/// Performs declarative source compile fixtures in directory.
1336///
1337/// # Parameters
1338///
1339/// - `dir_path`: Input consumed by [`crate::declarative::declarative_source_compile_fixtures_in_directory`].
1340///
1341/// # Returns
1342///
1343/// Returns the computed value produced by [`crate::declarative::declarative_source_compile_fixtures_in_directory`].
1344///
1345/// # Examples
1346///
1347/// ```rust,ignore
1348/// let _result = pharos_runtime_compiler::declarative::declarative_source_compile_fixtures_in_directory(todo!());
1349/// ```
1350///
1351/// # Related
1352///
1353/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1354pub fn declarative_source_compile_fixtures_in_directory(dir_path: &Path) -> i32 {
1355 compile_yaml_matches_in_directory(dir_path, ".yml")
1356}
1357
1358/// Normalizes compiled reference text.
1359fn normalize_compiled_reference_text(root: &Path, text: &str) -> Option<String> {
1360 let (path_text, fragment) = text.split_once('#').unwrap_or((text, ""));
1361 let stem = path_text
1362 .strip_suffix(".yml")
1363 .or_else(|| path_text.strip_suffix(".yaml"))?;
1364 let json_path = root.join(format!("{stem}.json"));
1365 if !json_path.is_file() {
1366 return None;
1367 }
1368 Some(if fragment.is_empty() {
1369 format!("{stem}.json")
1370 } else {
1371 format!("{stem}.json#{fragment}")
1372 })
1373}
1374
1375/// Normalizes compiled JSON value.
1376fn normalize_compiled_json_value(root: &Path, value: &mut Value) {
1377 match value {
1378 Value::Array(items) => {
1379 for item in items {
1380 normalize_compiled_json_value(root, item);
1381 }
1382 }
1383 Value::Object(fields) => {
1384 for item in fields.values_mut() {
1385 normalize_compiled_json_value(root, item);
1386 }
1387 }
1388 Value::String(text) => {
1389 if let Some(normalized) = normalize_compiled_reference_text(root, text) {
1390 *text = normalized;
1391 }
1392 }
1393 Value::Null | Value::Bool(_) | Value::Number(_) => {}
1394 }
1395}
1396
1397/// Normalizes compiled JSON references inner.
1398fn normalize_compiled_json_references_inner(root: &Path, dir: &Path, depth: usize) -> i32 {
1399 if depth > 64 {
1400 return 69;
1401 }
1402 let Ok(entries) = fs::read_dir(dir) else {
1403 return 69;
1404 };
1405 for entry in entries {
1406 let Ok(entry) = entry else {
1407 return 69;
1408 };
1409 let path = entry.path();
1410 let Ok(metadata) = fs::symlink_metadata(&path) else {
1411 return 69;
1412 };
1413 if metadata.file_type().is_symlink() {
1414 return 78;
1415 }
1416 if metadata.is_dir() {
1417 let code = normalize_compiled_json_references_inner(root, &path, depth + 1);
1418 if code != 0 {
1419 return code;
1420 }
1421 continue;
1422 }
1423 if !metadata.is_file() || path.extension().and_then(|value| value.to_str()) != Some("json")
1424 {
1425 continue;
1426 }
1427 let is_schema_surface = path
1428 .file_name()
1429 .and_then(|value| value.to_str())
1430 .is_some_and(|value| value.ends_with(".schema.json"))
1431 || path
1432 .parent()
1433 .and_then(Path::file_name)
1434 .and_then(|value| value.to_str())
1435 .is_some_and(|value| value == "schema");
1436 if is_schema_surface
1437 || database_authoring_path(root)
1438 .is_some_and(|source| source.with_extension("json") == path)
1439 {
1440 continue;
1441 }
1442 let Ok(text) = fs::read_to_string(&path) else {
1443 return 69;
1444 };
1445 let Ok(mut value) = serde_json::from_str::<Value>(&text) else {
1446 return 69;
1447 };
1448 let reference_root = path.parent().unwrap_or(root);
1449 normalize_compiled_json_value(reference_root, &mut value);
1450 let Ok(normalized) = serde_json::to_string_pretty(&value) else {
1451 return 75;
1452 };
1453 if pharos_runtime_core::fs::write_file_within(root, &path, normalized.as_bytes(), 0o644)
1454 .is_err()
1455 {
1456 return 69;
1457 }
1458 }
1459 0
1460}
1461
1462/// Rewrites compiled YAML references to sibling JSON artifacts in a deployment tree.
1463///
1464/// # Parameters
1465///
1466/// - `root`: Input consumed by [`crate::declarative::normalize_compiled_json_references`].
1467///
1468/// # Returns
1469///
1470/// Returns the computed value produced by [`crate::declarative::normalize_compiled_json_references`].
1471///
1472/// # Examples
1473///
1474/// ```rust,ignore
1475/// let _result = pharos_runtime_compiler::declarative::normalize_compiled_json_references(std::path::Path::new("."));
1476/// ```
1477///
1478/// # Related
1479///
1480/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1481pub fn normalize_compiled_json_references(root: &Path) -> i32 {
1482 if root.as_os_str().is_empty() || !root.is_dir() {
1483 return 64;
1484 }
1485 normalize_compiled_json_references_inner(root, root, 0)
1486}
1487
1488/// Converts one generated-admin path into its final resource segment.
1489///
1490/// # Parameters
1491///
1492/// - `manage_path`: Input consumed by [`crate::declarative::declarative_source_path_segment_from_manage_path`].
1493///
1494/// # Returns
1495///
1496/// Returns [`Some`] when [`crate::declarative::declarative_source_path_segment_from_manage_path`] can produce a value and [`None`] otherwise.
1497///
1498/// # Panics
1499///
1500/// May panic if an internal invariant assumed by [`crate::declarative::declarative_source_path_segment_from_manage_path`] is violated.
1501///
1502/// # Performance
1503///
1504/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_path_segment_from_manage_path`].
1505///
1506/// # Examples
1507///
1508/// ```rust,ignore
1509/// let _result = pharos_runtime_compiler::declarative::declarative_source_path_segment_from_manage_path(todo!());
1510/// ```
1511///
1512/// # Related
1513///
1514/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1515pub fn declarative_source_path_segment_from_manage_path(manage_path: &str) -> Option<String> {
1516 let segment = manage_path
1517 .trim_matches('/')
1518 .rsplit('/')
1519 .find(|value| !value.is_empty() && !value.starts_with(':') && !value.starts_with('{'))?;
1520 Some(segment.to_owned())
1521}
1522
1523/// Derives a stable generated-admin collection segment from entity metadata.
1524///
1525/// # Parameters
1526///
1527/// - `entity_id`: Input consumed by [`crate::declarative::declarative_source_default_path_segment`].
1528/// - `runtime_collection`: Input consumed by [`crate::declarative::declarative_source_default_path_segment`].
1529///
1530/// # Returns
1531///
1532/// Returns [`Some`] when [`crate::declarative::declarative_source_default_path_segment`] can produce a value and [`None`] otherwise.
1533///
1534/// # Performance
1535///
1536/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_default_path_segment`].
1537///
1538/// # Examples
1539///
1540/// ```rust,ignore
1541/// let _result = pharos_runtime_compiler::declarative::declarative_source_default_path_segment("entity", todo!());
1542/// ```
1543///
1544/// # Related
1545///
1546/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1547pub fn declarative_source_default_path_segment(
1548 entity_id: &str,
1549 runtime_collection: Option<&str>,
1550) -> Option<String> {
1551 if let Some(collection) = runtime_collection.filter(|value| !value.is_empty()) {
1552 if !collection.contains('_') {
1553 return Some(collection.to_owned());
1554 }
1555 if let Some(suffix) = collection
1556 .rsplit('_')
1557 .next()
1558 .filter(|value| !value.is_empty())
1559 {
1560 return Some(suffix.to_owned());
1561 }
1562 }
1563 if entity_id.is_empty() {
1564 return None;
1565 }
1566 if let Some(suffix) = entity_id
1567 .rsplit('_')
1568 .next()
1569 .filter(|value| !value.is_empty())
1570 && suffix != entity_id
1571 {
1572 return Some(suffix.to_owned());
1573 }
1574 Some(if let Some(stem) = entity_id.strip_suffix('y') {
1575 format!("{stem}ies")
1576 } else if entity_id.ends_with('s') {
1577 format!("{entity_id}es")
1578 } else {
1579 format!("{entity_id}s")
1580 })
1581}
1582
1583/// Builds one operation path using the route contract's colon-parameter syntax.
1584///
1585/// # Parameters
1586///
1587/// - `manage_path`: Input consumed by [`crate::declarative::declarative_source_operation_path`].
1588/// - `api_prefix`: Input consumed by [`crate::declarative::declarative_source_operation_path`].
1589/// - `identity_param`: Input consumed by [`crate::declarative::declarative_source_operation_path`].
1590/// - `suffix`: Input consumed by [`crate::declarative::declarative_source_operation_path`].
1591///
1592/// # Returns
1593///
1594/// Returns [`Some`] when [`crate::declarative::declarative_source_operation_path`] can produce a value and [`None`] otherwise.
1595///
1596/// # Examples
1597///
1598/// ```rust,ignore
1599/// let _result = pharos_runtime_compiler::declarative::declarative_source_operation_path(todo!(), todo!(), todo!(), todo!());
1600/// ```
1601///
1602/// # Related
1603///
1604/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1605pub fn declarative_source_operation_path(
1606 manage_path: &str,
1607 api_prefix: bool,
1608 identity_param: Option<&str>,
1609 suffix: Option<&str>,
1610) -> Option<String> {
1611 if manage_path.is_empty() {
1612 return None;
1613 }
1614 let mut output = String::with_capacity(manage_path.len() + 32);
1615 if api_prefix {
1616 output.push_str("/api");
1617 }
1618 output.push_str(manage_path);
1619 if let Some(identity) = identity_param.filter(|value| !value.is_empty()) {
1620 output.push(':');
1621 output.push_str(identity);
1622 output.push('/');
1623 }
1624 if let Some(suffix) = suffix {
1625 output.push_str(suffix);
1626 }
1627 Some(output)
1628}
1629
1630/// Performs declarative source route path has segment.
1631///
1632/// # Parameters
1633///
1634/// - `route_path`: Input consumed by [`crate::declarative::declarative_source_route_path_has_segment`].
1635/// - `path_segment`: Input consumed by [`crate::declarative::declarative_source_route_path_has_segment`].
1636///
1637/// # Returns
1638///
1639/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_route_path_has_segment`] holds and `false` otherwise.
1640///
1641/// # Performance
1642///
1643/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_route_path_has_segment`].
1644///
1645/// # Examples
1646///
1647/// ```rust,ignore
1648/// let _result = pharos_runtime_compiler::declarative::declarative_source_route_path_has_segment(todo!(), todo!());
1649/// ```
1650///
1651/// # Related
1652///
1653/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1654pub fn declarative_source_route_path_has_segment(route_path: &str, path_segment: &str) -> bool {
1655 !path_segment.is_empty() && route_path.split('/').any(|segment| segment == path_segment)
1656}
1657
1658/// Performs declarative source identity param from surface.
1659///
1660/// # Parameters
1661///
1662/// - `path_segment`: Input consumed by [`crate::declarative::declarative_source_identity_param_from_surface`].
1663/// - `entity_id`: Input consumed by [`crate::declarative::declarative_source_identity_param_from_surface`].
1664///
1665/// # Returns
1666///
1667/// Returns [`Some`] when [`crate::declarative::declarative_source_identity_param_from_surface`] can produce a value and [`None`] otherwise.
1668///
1669/// # Examples
1670///
1671/// ```rust,ignore
1672/// let _result = pharos_runtime_compiler::declarative::declarative_source_identity_param_from_surface(todo!(), "entity");
1673/// ```
1674///
1675/// # Related
1676///
1677/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1678pub fn declarative_source_identity_param_from_surface(
1679 path_segment: Option<&str>,
1680 entity_id: &str,
1681) -> Option<String> {
1682 let base = path_segment
1683 .filter(|value| !value.is_empty())
1684 .unwrap_or(entity_id);
1685 if base.is_empty() {
1686 return None;
1687 }
1688 let base = base.strip_suffix('s').unwrap_or(base);
1689 Some(format!("{base}_id"))
1690}
1691
1692/// Performs declarative source runtime collection.
1693///
1694/// # Parameters
1695///
1696/// - `entity_id`: Input consumed by [`crate::declarative::declarative_source_runtime_collection`].
1697/// - `table_name`: Input consumed by [`crate::declarative::declarative_source_runtime_collection`].
1698///
1699/// # Returns
1700///
1701/// Returns [`Some`] when [`crate::declarative::declarative_source_runtime_collection`] can produce a value and [`None`] otherwise.
1702///
1703/// # Examples
1704///
1705/// ```rust,ignore
1706/// let _result = pharos_runtime_compiler::declarative::declarative_source_runtime_collection("entity", todo!());
1707/// ```
1708///
1709/// # Related
1710///
1711/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1712pub fn declarative_source_runtime_collection(
1713 entity_id: &str,
1714 table_name: Option<&str>,
1715) -> Option<String> {
1716 if let Some(table) = table_name.filter(|value| !value.is_empty()) {
1717 if table.contains('_') {
1718 return Some(table.to_owned());
1719 }
1720 if entity_id.is_empty() {
1721 return Some(table.to_owned());
1722 }
1723 }
1724 declarative_source_default_path_segment(entity_id, None)
1725}
1726
1727/// Appends a string to an array unless the same string is already present.
1728///
1729/// # Parameters
1730///
1731/// - `values`: Input consumed by [`crate::declarative::declarative_source_append_unique_string_mut`].
1732/// - `value`: Input consumed by [`crate::declarative::declarative_source_append_unique_string_mut`].
1733///
1734/// # Returns
1735///
1736/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_append_unique_string_mut`] holds and `false` otherwise.
1737///
1738/// # Performance
1739///
1740/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_append_unique_string_mut`].
1741///
1742/// # Examples
1743///
1744/// ```rust,ignore
1745/// let _result = pharos_runtime_compiler::declarative::declarative_source_append_unique_string_mut(todo!(), "value");
1746/// ```
1747///
1748/// # Related
1749///
1750/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1751pub fn declarative_source_append_unique_string_mut(values: &mut Vec<Value>, value: &str) -> bool {
1752 if value.is_empty() {
1753 return false;
1754 }
1755 if values.iter().any(|entry| entry.as_str() == Some(value)) {
1756 return true;
1757 }
1758 values.push(Value::String(value.to_owned()));
1759 true
1760}
1761
1762/// Performs declarative source derived response fields mut.
1763///
1764/// # Parameters
1765///
1766/// - `form_fields`: Input consumed by [`crate::declarative::declarative_source_derived_response_fields_mut`].
1767/// - `relation_inputs`: Input consumed by [`crate::declarative::declarative_source_derived_response_fields_mut`].
1768/// - `identity_field`: Input consumed by [`crate::declarative::declarative_source_derived_response_fields_mut`].
1769/// - `scope_field`: Input consumed by [`crate::declarative::declarative_source_derived_response_fields_mut`].
1770/// - `soft_delete_field`: Input consumed by [`crate::declarative::declarative_source_derived_response_fields_mut`].
1771/// - `auto_sequence_field`: Input consumed by [`crate::declarative::declarative_source_derived_response_fields_mut`].
1772///
1773/// # Returns
1774///
1775/// Returns [`Some`] when [`crate::declarative::declarative_source_derived_response_fields_mut`] can produce a value and [`None`] otherwise.
1776///
1777/// # Performance
1778///
1779/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derived_response_fields_mut`].
1780///
1781/// # Examples
1782///
1783/// ```rust,ignore
1784/// let _result = pharos_runtime_compiler::declarative::declarative_source_derived_response_fields_mut(todo!(), todo!(), todo!(), todo!(), todo!(), todo!());
1785/// ```
1786///
1787/// # Related
1788///
1789/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1790pub fn declarative_source_derived_response_fields_mut(
1791 form_fields: Option<&Value>,
1792 relation_inputs: Option<&Value>,
1793 identity_field: Option<&str>,
1794 scope_field: Option<&str>,
1795 soft_delete_field: Option<&str>,
1796 auto_sequence_field: Option<&str>,
1797) -> Option<Value> {
1798 let mut fields = Vec::new();
1799 declarative_source_append_unique_string_mut(&mut fields, identity_field.unwrap_or_default());
1800 declarative_source_append_unique_string_mut(&mut fields, scope_field.unwrap_or_default());
1801 if let Some(items) = form_fields.and_then(Value::as_array) {
1802 for item in items {
1803 declarative_source_append_unique_string_mut(
1804 &mut fields,
1805 item.get("field_id")
1806 .and_then(Value::as_str)
1807 .unwrap_or_default(),
1808 );
1809 }
1810 }
1811 declarative_source_append_unique_string_mut(
1812 &mut fields,
1813 auto_sequence_field.unwrap_or_default(),
1814 );
1815 declarative_source_append_unique_string_mut(&mut fields, soft_delete_field.unwrap_or_default());
1816 if let Some(items) = relation_inputs.and_then(Value::as_array) {
1817 for item in items {
1818 declarative_source_append_unique_string_mut(
1819 &mut fields,
1820 item.get("field")
1821 .and_then(Value::as_str)
1822 .unwrap_or_default(),
1823 );
1824 }
1825 }
1826 Some(Value::Array(fields))
1827}
1828
1829/// Finds one form-field declaration by its field id.
1830///
1831/// # Parameters
1832///
1833/// - `fields`: Input consumed by [`crate::declarative::declarative_source_find_field_decl`].
1834/// - `field_id`: Input consumed by [`crate::declarative::declarative_source_find_field_decl`].
1835///
1836/// # Returns
1837///
1838/// Returns [`Some`] when [`crate::declarative::declarative_source_find_field_decl`] can produce a value and [`None`] otherwise.
1839///
1840/// # Examples
1841///
1842/// ```rust,ignore
1843/// let _result = pharos_runtime_compiler::declarative::declarative_source_find_field_decl(todo!(), "field");
1844/// ```
1845///
1846/// # Related
1847///
1848/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1849pub fn declarative_source_find_field_decl<'a>(
1850 fields: Option<&'a Value>,
1851 field_id: &str,
1852) -> Option<&'a Value> {
1853 declarative_source_find_object_by_string_field(fields?, "field_id", field_id)
1854}
1855
1856/// Performs declarative source option values mut.
1857///
1858/// # Parameters
1859///
1860/// - `options`: Input consumed by [`crate::declarative::declarative_source_option_values_mut`].
1861///
1862/// # Returns
1863///
1864/// Returns [`Some`] when [`crate::declarative::declarative_source_option_values_mut`] can produce a value and [`None`] otherwise.
1865///
1866/// # Performance
1867///
1868/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_option_values_mut`].
1869///
1870/// # Examples
1871///
1872/// ```rust,ignore
1873/// let _result = pharos_runtime_compiler::declarative::declarative_source_option_values_mut(todo!());
1874/// ```
1875///
1876/// # Related
1877///
1878/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1879pub fn declarative_source_option_values_mut(options: &Value) -> Option<Value> {
1880 let options = options.as_array()?;
1881 Some(Value::Array(
1882 options
1883 .iter()
1884 .filter_map(|item| item.get("value").and_then(Value::as_str))
1885 .filter(|value| !value.is_empty())
1886 .map(|value| Value::String(value.to_owned()))
1887 .collect(),
1888 ))
1889}
1890
1891/// Performs declarative source derived filters mut.
1892///
1893/// # Parameters
1894///
1895/// - `filter_set`: Input consumed by [`crate::declarative::declarative_source_derived_filters_mut`].
1896/// - `form_fields`: Input consumed by [`crate::declarative::declarative_source_derived_filters_mut`].
1897/// - `relation_inputs`: Input consumed by [`crate::declarative::declarative_source_derived_filters_mut`].
1898/// - `scope_field`: Input consumed by [`crate::declarative::declarative_source_derived_filters_mut`].
1899/// - `soft_delete_field`: Input consumed by [`crate::declarative::declarative_source_derived_filters_mut`].
1900///
1901/// # Returns
1902///
1903/// Returns [`Some`] when [`crate::declarative::declarative_source_derived_filters_mut`] can produce a value and [`None`] otherwise.
1904///
1905/// # Performance
1906///
1907/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derived_filters_mut`].
1908///
1909/// # Examples
1910///
1911/// ```rust,ignore
1912/// let _result = pharos_runtime_compiler::declarative::declarative_source_derived_filters_mut(todo!(), todo!(), todo!(), todo!(), todo!());
1913/// ```
1914///
1915/// # Related
1916///
1917/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
1918pub fn declarative_source_derived_filters_mut(
1919 filter_set: &Value,
1920 form_fields: Option<&Value>,
1921 relation_inputs: Option<&Value>,
1922 scope_field: Option<&str>,
1923 soft_delete_field: Option<&str>,
1924) -> Option<Value> {
1925 let mut filters = Vec::new();
1926 for field_name in filter_set.as_array()? {
1927 let field_name = field_name.as_str()?;
1928 if field_name.is_empty() || scope_field == Some(field_name) {
1929 continue;
1930 }
1931 let relation = relation_inputs
1932 .and_then(|value| value.as_array())
1933 .and_then(|items| {
1934 items
1935 .iter()
1936 .find(|item| item.get("field").and_then(Value::as_str) == Some(field_name))
1937 });
1938 let widget = declarative_source_find_field_decl(form_fields, field_name)
1939 .and_then(|value| value.get("widget"))
1940 .and_then(Value::as_str);
1941 let query = if relation.is_some() && field_name.ends_with("_ids") {
1942 field_name[..field_name.len() - 1].to_owned()
1943 } else {
1944 field_name.to_owned()
1945 };
1946 let (match_kind, extra) = if relation.is_some() {
1947 ("array_contains_integer", Value::Null)
1948 } else if soft_delete_field == Some(field_name) {
1949 ("boolean", serde_json::json!(true))
1950 } else if widget == Some("select")
1951 && declarative_source_find_field_decl(form_fields, field_name)
1952 .and_then(|value| value.get("options"))
1953 .is_some()
1954 {
1955 (
1956 "exact",
1957 declarative_source_find_field_decl(form_fields, field_name)
1958 .and_then(|value| value.get("options"))
1959 .cloned()
1960 .unwrap_or(Value::Array(Vec::new())),
1961 )
1962 } else if widget == Some("checkbox") {
1963 ("boolean", Value::Null)
1964 } else {
1965 ("contains", Value::Null)
1966 };
1967 let mut filter = serde_json::Map::new();
1968 filter.insert("query".to_owned(), Value::String(query));
1969 filter.insert("field".to_owned(), Value::String(field_name.to_owned()));
1970 filter.insert("match".to_owned(), Value::String(match_kind.to_owned()));
1971 if !extra.is_null() {
1972 if match_kind == "boolean" {
1973 filter.insert("default".to_owned(), extra);
1974 } else {
1975 filter.insert("options".to_owned(), extra);
1976 }
1977 }
1978 filter.insert(
1979 "label".to_owned(),
1980 Value::String(declarative_source_label(field_name, true)),
1981 );
1982 filters.push(Value::Object(filter));
1983 }
1984 Some(Value::Array(filters))
1985}
1986
1987/// Performs declarative source derived sorts mut.
1988///
1989/// # Parameters
1990///
1991/// - `sort_set`: Input consumed by [`crate::declarative::declarative_source_derived_sorts_mut`].
1992///
1993/// # Returns
1994///
1995/// Returns [`Some`] when [`crate::declarative::declarative_source_derived_sorts_mut`] can produce a value and [`None`] otherwise.
1996///
1997/// # Performance
1998///
1999/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derived_sorts_mut`].
2000///
2001/// # Examples
2002///
2003/// ```rust,ignore
2004/// let _result = pharos_runtime_compiler::declarative::declarative_source_derived_sorts_mut(todo!());
2005/// ```
2006///
2007/// # Related
2008///
2009/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2010pub fn declarative_source_derived_sorts_mut(sort_set: &Value) -> Option<Value> {
2011 let mut sorts = Vec::new();
2012 for sort_key in sort_set.as_array()? {
2013 let sort_key = sort_key.as_str()?;
2014 if sort_key.is_empty() {
2015 continue;
2016 }
2017 if sort_key == "day_time_asc" {
2018 sorts.push(serde_json::json!({
2019 "key": sort_key, "field": "day_of_week", "direction": "asc",
2020 "type": "weekday", "secondary_field": "start_time", "secondary_type": "string",
2021 "label": "Day / start time"
2022 }));
2023 continue;
2024 }
2025 let (field, direction) = if let Some(field) = sort_key.strip_suffix("_desc") {
2026 (field, "desc")
2027 } else if let Some(field) = sort_key.strip_suffix("_asc") {
2028 (field, "asc")
2029 } else {
2030 (sort_key, "asc")
2031 };
2032 if field.is_empty() {
2033 continue;
2034 }
2035 let kind = if field.contains("time") {
2036 "string"
2037 } else if field.contains("order") || field.contains("duration") {
2038 "number"
2039 } else {
2040 "string"
2041 };
2042 sorts.push(serde_json::json!({
2043 "key": sort_key, "field": field, "direction": direction,
2044 "type": kind, "label": declarative_source_label(field, true)
2045 }));
2046 }
2047 Some(Value::Array(sorts))
2048}
2049
2050/// Performs declarative source derived surface uniqueness rules mut.
2051///
2052/// # Parameters
2053///
2054/// - `uniqueness_rules`: Input consumed by [`crate::declarative::declarative_source_derived_surface_uniqueness_rules_mut`].
2055/// - `entity_fields`: Input consumed by [`crate::declarative::declarative_source_derived_surface_uniqueness_rules_mut`].
2056/// - `entity_id`: Input consumed by [`crate::declarative::declarative_source_derived_surface_uniqueness_rules_mut`].
2057///
2058/// # Returns
2059///
2060/// Returns [`Some`] when [`crate::declarative::declarative_source_derived_surface_uniqueness_rules_mut`] can produce a value and [`None`] otherwise.
2061///
2062/// # Performance
2063///
2064/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derived_surface_uniqueness_rules_mut`].
2065///
2066/// # Examples
2067///
2068/// ```rust,ignore
2069/// let _result = pharos_runtime_compiler::declarative::declarative_source_derived_surface_uniqueness_rules_mut(todo!(), todo!(), "entity");
2070/// ```
2071///
2072/// # Related
2073///
2074/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2075pub fn declarative_source_derived_surface_uniqueness_rules_mut(
2076 uniqueness_rules: &Value,
2077 entity_fields: &Value,
2078 entity_id: &str,
2079) -> Option<Value> {
2080 if entity_id.is_empty() {
2081 return None;
2082 }
2083 let mut rules = Vec::new();
2084 for rule_text in uniqueness_rules.as_array()? {
2085 let rule_text = rule_text.as_str()?.trim();
2086 if rule_text.is_empty() {
2087 continue;
2088 }
2089 let fields: Vec<Value> = rule_text
2090 .split('+')
2091 .map(str::trim)
2092 .filter(|field| !field.is_empty())
2093 .map(|field| Value::String(field.to_owned()))
2094 .collect();
2095 if fields.is_empty() {
2096 continue;
2097 }
2098 let casefold_fields: Vec<Value> = fields
2099 .iter()
2100 .filter_map(|field| {
2101 let name = field.as_str()?;
2102 (declarative_source_find_field_decl(Some(entity_fields), name)
2103 .and_then(|value| value.get("type"))
2104 .and_then(Value::as_str)
2105 == Some("string"))
2106 .then(|| Value::String(name.to_owned()))
2107 })
2108 .collect();
2109 let mut rule = serde_json::Map::new();
2110 rule.insert("fields".to_owned(), Value::Array(fields));
2111 if !casefold_fields.is_empty() {
2112 rule.insert("casefold_fields".to_owned(), Value::Array(casefold_fields));
2113 }
2114 rule.insert(
2115 "rule_id".to_owned(),
2116 Value::String(format!("{entity_id}_unique")),
2117 );
2118 rule.insert(
2119 "message".to_owned(),
2120 Value::String(format!("{entity_id} must be unique within this scope.")),
2121 );
2122 rules.push(Value::Object(rule));
2123 }
2124 Some(Value::Array(rules))
2125}
2126
2127/// Performs declarative source append surface.
2128///
2129/// # Parameters
2130///
2131/// - `surfaces`: Input consumed by [`crate::declarative::declarative_source_append_surface`].
2132/// - `surface_root`: Input consumed by [`crate::declarative::declarative_source_append_surface`].
2133///
2134/// # Returns
2135///
2136/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_append_surface`] holds and `false` otherwise.
2137///
2138/// # Examples
2139///
2140/// ```rust,ignore
2141/// let _result = pharos_runtime_compiler::declarative::declarative_source_append_surface(todo!(), todo!());
2142/// ```
2143///
2144/// # Related
2145///
2146/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2147pub fn declarative_source_append_surface(surfaces: &mut Vec<Value>, surface_root: Value) -> bool {
2148 if surfaces.len() >= MAX_DECLARATIVE_FILES {
2149 return false;
2150 }
2151 surfaces.push(surface_root);
2152 true
2153}
2154
2155/// Performs declarative source append generated route mut.
2156///
2157/// # Parameters
2158///
2159/// - `routes`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2160/// - `route_id`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2161/// - `path`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2162/// - `kind`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2163/// - `page_or_action_id`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2164/// - `auth_gate`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2165/// - `policy_gate`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2166/// - `layout_id`: Input consumed by [`crate::declarative::declarative_source_append_generated_route_mut`].
2167///
2168/// # Returns
2169///
2170/// Returns `true` when the condition checked by [`crate::declarative::declarative_source_append_generated_route_mut`] holds and `false` otherwise.
2171///
2172/// # Examples
2173///
2174/// ```rust,ignore
2175/// let _result = pharos_runtime_compiler::declarative::declarative_source_append_generated_route_mut(todo!(), todo!(), std::path::Path::new("fixture.json"), todo!(), todo!(), todo!(), todo!(), todo!());
2176/// ```
2177///
2178/// # Related
2179///
2180/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2181pub fn declarative_source_append_generated_route_mut(
2182 routes: &mut Vec<Value>,
2183 route_id: &str,
2184 path: &str,
2185 kind: &str,
2186 page_or_action_id: &str,
2187 auth_gate: Option<&str>,
2188 policy_gate: Option<&str>,
2189 layout_id: Option<&str>,
2190) -> bool {
2191 if route_id.is_empty()
2192 || path.is_empty()
2193 || page_or_action_id.is_empty()
2194 || !matches!(kind, "page" | "action")
2195 {
2196 return false;
2197 }
2198 let mut route = serde_json::Map::new();
2199 route.insert("route_id".to_owned(), Value::String(route_id.to_owned()));
2200 route.insert("path".to_owned(), Value::String(path.to_owned()));
2201 route.insert("kind".to_owned(), Value::String(kind.to_owned()));
2202 route.insert(
2203 (if kind == "page" {
2204 "page_id"
2205 } else {
2206 "action_id"
2207 })
2208 .to_owned(),
2209 Value::String(page_or_action_id.to_owned()),
2210 );
2211 route.insert(
2212 "layout_id".to_owned(),
2213 Value::String(layout_id.unwrap_or("generated_admin_shell").to_owned()),
2214 );
2215 route.insert(
2216 "auth_gate".to_owned(),
2217 Value::String(auth_gate.unwrap_or("session_required").to_owned()),
2218 );
2219 route.insert(
2220 "policy_gate".to_owned(),
2221 Value::String(policy_gate.unwrap_or_default().to_owned()),
2222 );
2223 route.insert("islands".to_owned(), Value::Array(Vec::new()));
2224 routes.push(Value::Object(route));
2225 true
2226}
2227
2228/// Performs declarative source generated API request hooks.
2229fn declarative_source_generated_api_request_hooks() -> Value {
2230 Value::Array(vec![Value::String("json_body_required".to_owned())])
2231}
2232
2233/// Performs declarative source find generated query for entity.
2234///
2235/// # Parameters
2236///
2237/// - `queries`: Input consumed by [`crate::declarative::declarative_source_find_generated_query_for_entity`].
2238/// - `entity_id`: Input consumed by [`crate::declarative::declarative_source_find_generated_query_for_entity`].
2239///
2240/// # Returns
2241///
2242/// Returns [`Some`] when [`crate::declarative::declarative_source_find_generated_query_for_entity`] can produce a value and [`None`] otherwise.
2243///
2244/// # Performance
2245///
2246/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_find_generated_query_for_entity`].
2247///
2248/// # Examples
2249///
2250/// ```rust,ignore
2251/// let _result = pharos_runtime_compiler::declarative::declarative_source_find_generated_query_for_entity(todo!(), "entity");
2252/// ```
2253///
2254/// # Related
2255///
2256/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2257pub fn declarative_source_find_generated_query_for_entity<'a>(
2258 queries: &'a Value,
2259 entity_id: &str,
2260) -> Option<&'a Value> {
2261 if entity_id.is_empty() {
2262 return None;
2263 }
2264 let items = queries.as_array()?;
2265 let mut match_value = None;
2266 for item in items {
2267 if item.get("entity_id").and_then(Value::as_str) != Some(entity_id) {
2268 continue;
2269 }
2270 if match_value.is_some() {
2271 return None;
2272 }
2273 match_value = Some(item);
2274 }
2275 match_value
2276}
2277
2278/// Performs declarative source find generated form for entity.
2279///
2280/// # Parameters
2281///
2282/// - `forms`: Input consumed by [`crate::declarative::declarative_source_find_generated_form_for_entity`].
2283/// - `entity_id`: Input consumed by [`crate::declarative::declarative_source_find_generated_form_for_entity`].
2284///
2285/// # Returns
2286///
2287/// Returns [`Some`] when [`crate::declarative::declarative_source_find_generated_form_for_entity`] can produce a value and [`None`] otherwise.
2288///
2289/// # Performance
2290///
2291/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_find_generated_form_for_entity`].
2292///
2293/// # Examples
2294///
2295/// ```rust,ignore
2296/// let _result = pharos_runtime_compiler::declarative::declarative_source_find_generated_form_for_entity(todo!(), "entity");
2297/// ```
2298///
2299/// # Related
2300///
2301/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2302pub fn declarative_source_find_generated_form_for_entity<'a>(
2303 forms: &'a Value,
2304 entity_id: &str,
2305) -> Option<&'a Value> {
2306 if entity_id.is_empty() {
2307 return None;
2308 }
2309 let items = forms.as_array()?;
2310 let mut match_value = None;
2311 for item in items {
2312 if item.get("entity_id").and_then(Value::as_str) != Some(entity_id) {
2313 continue;
2314 }
2315 if match_value.is_some() {
2316 return None;
2317 }
2318 match_value = Some(item);
2319 }
2320 match_value
2321}
2322
2323/// Performs declarative source find manage route.
2324///
2325/// # Parameters
2326///
2327/// - `routes`: Input consumed by [`crate::declarative::declarative_source_find_manage_route`].
2328/// - `path_segment`: Input consumed by [`crate::declarative::declarative_source_find_manage_route`].
2329///
2330/// # Returns
2331///
2332/// Returns [`Some`] when [`crate::declarative::declarative_source_find_manage_route`] can produce a value and [`None`] otherwise.
2333///
2334/// # Performance
2335///
2336/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_find_manage_route`].
2337///
2338/// # Examples
2339///
2340/// ```rust,ignore
2341/// let _result = pharos_runtime_compiler::declarative::declarative_source_find_manage_route(todo!(), todo!());
2342/// ```
2343///
2344/// # Related
2345///
2346/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2347pub fn declarative_source_find_manage_route<'a>(
2348 routes: &'a Value,
2349 path_segment: &str,
2350) -> Option<&'a Value> {
2351 if path_segment.is_empty() {
2352 return None;
2353 }
2354 let items = routes.as_array()?;
2355 let mut match_value = None;
2356 for item in items {
2357 let is_match = item.get("kind").and_then(Value::as_str) == Some("page")
2358 && item.get("page_id").and_then(Value::as_str) == Some("generated_admin_manage")
2359 && item
2360 .get("path")
2361 .and_then(Value::as_str)
2362 .is_some_and(|path| declarative_source_route_path_has_segment(path, path_segment));
2363 if !is_match {
2364 continue;
2365 }
2366 if match_value.is_some() {
2367 return None;
2368 }
2369 match_value = Some(item);
2370 }
2371 match_value
2372}
2373
2374/// Resolves array.
2375fn resolve_array(value: Option<&Value>, artifact_dir: &Path) -> Option<Value> {
2376 match value? {
2377 Value::Array(_) => value.cloned(),
2378 Value::String(reference) => declarative_source_load_ref_target(artifact_dir, reference),
2379 _ => None,
2380 }
2381}
2382
2383/// Copies member.
2384fn copy_member(target: &mut serde_json::Map<String, Value>, source: &Value, key: &str) {
2385 if let Some(value) = source.get(key) {
2386 target.insert(key.to_owned(), value.clone());
2387 }
2388}
2389
2390/// Builds one generated surface from declarative entity, query, form, and route values.
2391///
2392/// # Parameters
2393///
2394/// - `artifact_dir`: Input consumed by [`crate::declarative::declarative_source_build_surface_mut`].
2395/// - `entity_root`: Input consumed by [`crate::declarative::declarative_source_build_surface_mut`].
2396/// - `query_root`: Input consumed by [`crate::declarative::declarative_source_build_surface_mut`].
2397/// - `form_root`: Input consumed by [`crate::declarative::declarative_source_build_surface_mut`].
2398/// - `manage_route`: Input consumed by [`crate::declarative::declarative_source_build_surface_mut`].
2399/// - `surface_kind`: Input consumed by [`crate::declarative::declarative_source_build_surface_mut`].
2400///
2401/// # Returns
2402///
2403/// Returns [`Some`] when [`crate::declarative::declarative_source_build_surface_mut`] can produce a value and [`None`] otherwise.
2404///
2405/// # Performance
2406///
2407/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_build_surface_mut`].
2408///
2409/// # Examples
2410///
2411/// ```rust,ignore
2412/// let _result = pharos_runtime_compiler::declarative::declarative_source_build_surface_mut(std::path::Path::new("build/dev_docs"), todo!(), todo!(), todo!(), todo!(), todo!());
2413/// ```
2414///
2415/// # Related
2416///
2417/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2418pub fn declarative_source_build_surface_mut(
2419 artifact_dir: &Path,
2420 entity_root: &Value,
2421 query_root: &Value,
2422 form_root: &Value,
2423 manage_route: &Value,
2424 surface_kind: &str,
2425) -> Option<Value> {
2426 if !matches!(surface_kind, "generated_admin" | "generated_api") {
2427 return None;
2428 }
2429 let generated = entity_root.get("generated_surfaces")?;
2430 let requested = if surface_kind == "generated_admin" {
2431 "admin"
2432 } else {
2433 "api"
2434 };
2435 if !declarative_source_array_has_string(generated, requested) {
2436 return None;
2437 }
2438 let entity_id = entity_root.get("entity_id")?.as_str()?;
2439 let query_id = query_root.get("query_id")?.as_str()?;
2440 let form_id = form_root.get("form_id")?.as_str()?;
2441 let identity_field = entity_root.get("identity_field")?.as_str()?;
2442 let scope_field = entity_root.get("scope_field")?.as_str()?;
2443 let runtime_collection = entity_root
2444 .get("runtime_collection")
2445 .and_then(Value::as_str)
2446 .map(ToOwned::to_owned)
2447 .or_else(|| {
2448 declarative_source_runtime_collection(
2449 entity_id,
2450 entity_root.get("table").and_then(Value::as_str),
2451 )
2452 })?;
2453 let manage_path = manage_route.get("path")?.as_str()?;
2454 let path_segment = entity_root
2455 .get("path_segment")
2456 .and_then(Value::as_str)
2457 .filter(|value| !value.is_empty())
2458 .map(ToOwned::to_owned)
2459 .or_else(|| declarative_source_path_segment_from_manage_path(manage_path))?;
2460 let identity_param =
2461 declarative_source_identity_param_from_surface(Some(&path_segment), entity_id)?;
2462 let entity_fields = resolve_array(entity_root.get("fields"), artifact_dir)?;
2463 let form_fields = resolve_array(form_root.get("fields"), artifact_dir)?;
2464 let relation_inputs = form_root.get("relation_inputs");
2465 let field_types = {
2466 let mut types = serde_json::Map::new();
2467 for field in entity_fields.as_array()? {
2468 if let (Some(id), Some(kind)) = (
2469 field.get("field_id").and_then(Value::as_str),
2470 field.get("type").and_then(Value::as_str),
2471 ) {
2472 types.insert(id.to_owned(), Value::String(kind.to_owned()));
2473 }
2474 }
2475 if let Some(inputs) = relation_inputs.and_then(Value::as_array) {
2476 for input in inputs {
2477 if let Some(field) = input.get("field").and_then(Value::as_str) {
2478 types.insert(field.to_owned(), Value::String("array_integer".to_owned()));
2479 }
2480 }
2481 }
2482 Value::Object(types)
2483 };
2484 let capability = manage_route
2485 .get("policy_gate")
2486 .and_then(Value::as_str)
2487 .unwrap_or("");
2488 let soft_restore_supported = entity_root
2489 .get("soft_delete_field")
2490 .and_then(Value::as_str)
2491 .is_some_and(|value| !value.is_empty())
2492 && entity_root.get("delete_strategy").and_then(Value::as_str) != Some("hard");
2493 let mut operations = if surface_kind == "generated_admin" {
2494 serde_json::json!({
2495 "manage": declarative_source_build_operation_mut("GET", manage_path, capability)?,
2496 "create": declarative_source_build_operation_mut("POST", &declarative_source_operation_path(manage_path, false, None, Some("new/"))?, capability)?,
2497 "edit": declarative_source_build_operation_mut("GET", &declarative_source_operation_path(manage_path, false, Some(&identity_param), Some("edit/"))?, capability)?,
2498 "update": declarative_source_build_operation_mut("POST", &declarative_source_operation_path(manage_path, false, Some(&identity_param), Some("edit/"))?, capability)?,
2499 "delete": declarative_source_build_operation_mut("POST", &declarative_source_operation_path(manage_path, false, Some(&identity_param), Some("delete/"))?, capability)?
2500 })
2501 } else {
2502 serde_json::json!({
2503 "list": declarative_source_build_operation_mut("GET", &api_prefixed_route_path(manage_path)?, capability)?,
2504 "schema": declarative_source_build_operation_mut("GET", &declarative_source_operation_path(manage_path, true, None, Some("schema/"))?, capability)?,
2505 "create": declarative_source_build_operation_mut("POST", &api_prefixed_route_path(manage_path)?, capability)?,
2506 "update": declarative_source_build_operation_mut("POST", &declarative_source_operation_path(manage_path, true, Some(&identity_param), None)?, capability)?,
2507 "delete": declarative_source_build_operation_mut("POST", &declarative_source_operation_path(manage_path, true, Some(&identity_param), Some("delete/"))?, capability)?
2508 })
2509 };
2510 if soft_restore_supported && let Some(object) = operations.as_object_mut() {
2511 let restore_path = declarative_source_operation_path(
2512 manage_path,
2513 surface_kind == "generated_api",
2514 Some(&identity_param),
2515 Some("restore/"),
2516 )?;
2517 object.insert(
2518 "restore".to_owned(),
2519 declarative_source_build_operation_mut("POST", &restore_path, capability)?,
2520 );
2521 }
2522 let mut surface = serde_json::Map::new();
2523 surface.insert(
2524 "surface_id".to_owned(),
2525 Value::String(format!("{entity_id}_{surface_kind}")),
2526 );
2527 surface.insert(
2528 "surface_kind".to_owned(),
2529 Value::String(surface_kind.to_owned()),
2530 );
2531 surface.insert("entity_id".to_owned(), Value::String(entity_id.to_owned()));
2532 surface.insert(
2533 "entity_collection".to_owned(),
2534 Value::String(runtime_collection.clone()),
2535 );
2536 surface.insert("query_id".to_owned(), Value::String(query_id.to_owned()));
2537 surface.insert("form_id".to_owned(), Value::String(form_id.to_owned()));
2538 surface.insert(
2539 "identity_field".to_owned(),
2540 Value::String(identity_field.to_owned()),
2541 );
2542 surface.insert("identity_param".to_owned(), Value::String(identity_param));
2543 surface.insert(
2544 "scope_field".to_owned(),
2545 Value::String(scope_field.to_owned()),
2546 );
2547 surface.insert("field_types".to_owned(), field_types);
2548 surface.insert("operations".to_owned(), operations);
2549 if let Some(value) = entity_root.get("context_binding") {
2550 surface.insert("context_binding".to_owned(), value.clone());
2551 } else if let Some(value) = declarative_source_default_context_binding_mut(Some(scope_field)) {
2552 surface.insert("context_binding".to_owned(), value);
2553 }
2554 if let Some(value) = query_root.get("response_fields") {
2555 surface.insert("response_fields".to_owned(), value.clone());
2556 } else if let Some(value) = declarative_source_derived_response_fields_mut(
2557 Some(&form_fields),
2558 relation_inputs,
2559 Some(identity_field),
2560 Some(scope_field),
2561 entity_root.get("soft_delete_field").and_then(Value::as_str),
2562 entity_root
2563 .get("auto_sequence_field")
2564 .and_then(Value::as_str),
2565 ) {
2566 surface.insert("response_fields".to_owned(), value);
2567 }
2568 if let Some(value) = query_root.get("filters") {
2569 surface.insert("filters".to_owned(), value.clone());
2570 } else if let Some(value) = query_root.get("filter_set").and_then(|value| {
2571 declarative_source_derived_filters_mut(
2572 value,
2573 Some(&form_fields),
2574 relation_inputs,
2575 Some(scope_field),
2576 entity_root.get("soft_delete_field").and_then(Value::as_str),
2577 )
2578 }) {
2579 surface.insert("filters".to_owned(), value);
2580 }
2581 if let Some(value) = query_root.get("sorts") {
2582 surface.insert("sorts".to_owned(), value.clone());
2583 } else if let Some(value) = query_root
2584 .get("sort_set")
2585 .and_then(declarative_source_derived_sorts_mut)
2586 {
2587 surface.insert("sorts".to_owned(), value);
2588 }
2589 if let Some(value) = form_root.get("defaults") {
2590 surface.insert("defaults".to_owned(), value.clone());
2591 } else if let Some(value) = declarative_source_derived_defaults_mut(
2592 Some(&form_fields),
2593 relation_inputs,
2594 entity_root.get("soft_delete_field").and_then(Value::as_str),
2595 ) {
2596 surface.insert("defaults".to_owned(), value);
2597 }
2598 copy_member(&mut surface, form_root, "field_constraints");
2599 copy_member(&mut surface, form_root, "relation_inputs");
2600 copy_member(&mut surface, entity_root, "auto_sequence_field");
2601 copy_member(&mut surface, entity_root, "soft_delete_field");
2602 copy_member(&mut surface, entity_root, "delete_strategy");
2603 if let Some(value) = entity_root.get("surface_uniqueness_rules") {
2604 surface.insert("surface_uniqueness_rules".to_owned(), value.clone());
2605 } else if let Some(value) = entity_root.get("uniqueness_rules").and_then(|value| {
2606 declarative_source_derived_surface_uniqueness_rules_mut(value, &entity_fields, entity_id)
2607 }) {
2608 surface.insert("surface_uniqueness_rules".to_owned(), value);
2609 }
2610 if let Some(value) = entity_root.get("audit_events") {
2611 surface.insert("audit_events".to_owned(), value.clone());
2612 } else if let Some(value) = declarative_source_derived_audit_events_mut(
2613 entity_id,
2614 entity_root.get("delete_strategy").and_then(Value::as_str) == Some("hard"),
2615 ) {
2616 surface.insert("audit_events".to_owned(), value);
2617 }
2618 if let Some(value) = surface.get("surface_uniqueness_rules").cloned() {
2619 surface.insert("uniqueness_rules".to_owned(), value);
2620 }
2621 if surface_kind == "generated_admin" {
2622 let admin_source = manage_route
2623 .get("admin_surface")
2624 .and_then(|value| value.as_object());
2625 let response_fields = surface
2626 .get("response_fields")
2627 .unwrap_or(&Value::Array(Vec::new()))
2628 .clone();
2629 let mut admin = serde_json::Map::new();
2630 admin.insert(
2631 "manage_page_template".to_owned(),
2632 Value::String("templates/generated_admin/manage.page.json".to_owned()),
2633 );
2634 admin.insert(
2635 "edit_page_template".to_owned(),
2636 Value::String("templates/generated_admin/edit.page.json".to_owned()),
2637 );
2638 admin.insert(
2639 "list_title".to_owned(),
2640 Value::String(declarative_source_label(entity_id, true)),
2641 );
2642 admin.insert(
2643 "list_intro".to_owned(),
2644 Value::String(declarative_source_label(&runtime_collection, true)),
2645 );
2646 admin.insert(
2647 "filter_title".to_owned(),
2648 Value::String(declarative_source_label(&runtime_collection, true)),
2649 );
2650 admin.insert(
2651 "records_title".to_owned(),
2652 Value::String(declarative_source_label(&runtime_collection, true)),
2653 );
2654 admin.insert(
2655 "create_title".to_owned(),
2656 Value::String(declarative_source_label(entity_id, true)),
2657 );
2658 admin.insert(
2659 "create_button_label".to_owned(),
2660 Value::String("Create".to_owned()),
2661 );
2662 admin.insert(
2663 "save_button_label".to_owned(),
2664 Value::String("Save".to_owned()),
2665 );
2666 admin.insert(
2667 "empty_state_text".to_owned(),
2668 Value::String("No records found.".to_owned()),
2669 );
2670 admin.insert(
2671 "item_title_field".to_owned(),
2672 Value::String(default_item_title_field(
2673 &entity_fields,
2674 Some(identity_field),
2675 Some(scope_field),
2676 )?),
2677 );
2678 admin.insert(
2679 "list_columns".to_owned(),
2680 declarative_source_default_list_columns_mut(
2681 &response_fields,
2682 Some(identity_field),
2683 Some(scope_field),
2684 )?,
2685 );
2686 if let Some(source) = admin_source {
2687 for key in [
2688 "list_title",
2689 "list_intro",
2690 "back_path_template",
2691 "filter_title",
2692 "records_title",
2693 "create_title",
2694 "create_button_label",
2695 "save_button_label",
2696 "empty_state_text",
2697 "item_title_field",
2698 "delete_button_label",
2699 "list_columns",
2700 "composition",
2701 ] {
2702 if let Some(value) = source.get(key) {
2703 admin.insert(key.to_owned(), value.clone());
2704 }
2705 }
2706 }
2707 surface.insert("admin".to_owned(), Value::Object(admin));
2708 }
2709 Some(Value::Object(surface))
2710}
2711
2712/// Derives and writes the generated surface fixture from framework contract files.
2713///
2714/// # Parameters
2715///
2716/// - `artifact_dir`: Input consumed by [`crate::declarative::declarative_source_derive_generated_surface_fixture`].
2717///
2718/// # Returns
2719///
2720/// Returns [`Some`] when [`crate::declarative::declarative_source_derive_generated_surface_fixture`] can produce a value and [`None`] otherwise.
2721///
2722/// # Performance
2723///
2724/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derive_generated_surface_fixture`].
2725///
2726/// # Examples
2727///
2728/// ```rust,ignore
2729/// let _result = pharos_runtime_compiler::declarative::declarative_source_derive_generated_surface_fixture(std::path::Path::new("build/dev_docs"));
2730/// ```
2731///
2732/// # Related
2733///
2734/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2735pub fn declarative_source_derive_generated_surface_fixture(artifact_dir: &Path) -> Option<usize> {
2736 let entities = read_framework_contract(artifact_dir, "entities.json")?
2737 .get("entities")?
2738 .clone();
2739 let queries = read_framework_contract(artifact_dir, "queries.json")?
2740 .get("queries")?
2741 .clone();
2742 let forms = read_framework_contract(artifact_dir, "forms.json")?
2743 .get("forms")?
2744 .clone();
2745 let routes = read_framework_contract(artifact_dir, "routes.json")?
2746 .get("routes")?
2747 .clone();
2748 let mut surfaces = Vec::new();
2749 for entity in entities.as_array()? {
2750 let entity_id = entity.get("entity_id").and_then(Value::as_str)?;
2751 let generated = entity.get("generated_surfaces");
2752 let generated_admin_requested =
2753 declarative_source_array_has_string(generated.unwrap_or(&Value::Null), "admin");
2754 let generated_api_requested =
2755 declarative_source_array_has_string(generated.unwrap_or(&Value::Null), "api");
2756 if !generated_admin_requested && !generated_api_requested {
2757 continue;
2758 }
2759 let collection = entity
2760 .get("runtime_collection")
2761 .and_then(Value::as_str)
2762 .or_else(|| entity.get("table").and_then(Value::as_str));
2763 let segment = entity
2764 .get("path_segment")
2765 .and_then(Value::as_str)
2766 .filter(|value| !value.is_empty())
2767 .map(ToOwned::to_owned)
2768 .or_else(|| declarative_source_default_path_segment(entity_id, collection));
2769 let query = queries
2770 .as_array()?
2771 .iter()
2772 .find(|value| value.get("entity_id").and_then(Value::as_str) == Some(entity_id));
2773 let form = forms
2774 .as_array()?
2775 .iter()
2776 .find(|value| value.get("entity_id").and_then(Value::as_str) == Some(entity_id));
2777 let routes_array = routes.as_array()?;
2778 let route = declarative_source_surface_anchor_route(
2779 routes_array,
2780 segment.as_deref(),
2781 entity_id,
2782 generated_admin_requested,
2783 );
2784 let (Some(query), Some(form), Some(route)) = (query, form, route) else {
2785 continue;
2786 };
2787 for kind in ["generated_api", "generated_admin"] {
2788 let requested = if kind == "generated_admin" {
2789 "admin"
2790 } else {
2791 "api"
2792 };
2793 if declarative_source_array_has_string(generated.unwrap_or(&Value::Null), requested)
2794 && let Some(surface) = declarative_source_build_surface_mut(
2795 artifact_dir,
2796 entity,
2797 query,
2798 form,
2799 &route,
2800 kind,
2801 )
2802 {
2803 surfaces.push(surface);
2804 }
2805 }
2806 }
2807 let output =
2808 serde_json::json!({"fixture_id": "generated_surface_fixture_v1", "surfaces": surfaces});
2809 let path = artifact_dir.join("data/contracts/generated-surfaces.json");
2810 if fs::create_dir_all(path.parent()?).is_err()
2811 || pharos_runtime_core::fs::write_file_within(
2812 artifact_dir,
2813 &path,
2814 serde_json::to_string_pretty(&output).ok()?.as_bytes(),
2815 0o644,
2816 )
2817 .is_err()
2818 {
2819 return None;
2820 }
2821 Some(output.get("surfaces")?.as_array()?.len())
2822}
2823
2824/// Performs declarative source surface path to route path.
2825///
2826/// # Parameters
2827///
2828/// - `surface_path`: Input consumed by [`crate::declarative::declarative_source_surface_path_to_route_path`].
2829///
2830/// # Returns
2831///
2832/// Returns [`Some`] when [`crate::declarative::declarative_source_surface_path_to_route_path`] can produce a value and [`None`] otherwise.
2833///
2834/// # Performance
2835///
2836/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_surface_path_to_route_path`].
2837///
2838/// # Examples
2839///
2840/// ```rust,ignore
2841/// let _result = pharos_runtime_compiler::declarative::declarative_source_surface_path_to_route_path(todo!());
2842/// ```
2843///
2844/// # Related
2845///
2846/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2847pub fn declarative_source_surface_path_to_route_path(surface_path: &str) -> Option<String> {
2848 if surface_path.is_empty() {
2849 return Some(String::new());
2850 }
2851 let mut output = String::with_capacity(surface_path.len());
2852 let mut chars = surface_path.chars().peekable();
2853 while let Some(character) = chars.next() {
2854 if character == '{' {
2855 output.push(':');
2856 for next in chars.by_ref() {
2857 if next == '}' {
2858 break;
2859 }
2860 output.push(next);
2861 }
2862 } else {
2863 output.push(character);
2864 }
2865 }
2866 Some(output)
2867}
2868
2869/// Performs declarative source manage path from create route.
2870fn declarative_source_manage_path_from_create_route(path: &str) -> Option<String> {
2871 path.strip_suffix("new/").map(ToOwned::to_owned)
2872}
2873
2874/// Performs declarative source surface anchor route.
2875fn declarative_source_surface_anchor_route(
2876 routes: &[Value],
2877 segment: Option<&str>,
2878 entity_id: &str,
2879 require_generated_admin: bool,
2880) -> Option<Value> {
2881 let generated_admin_manage = routes.iter().find(|value| {
2882 value.get("kind").and_then(Value::as_str) == Some("page")
2883 && value.get("page_id").and_then(Value::as_str) == Some("generated_admin_manage")
2884 && segment.is_some_and(|segment| {
2885 value
2886 .get("path")
2887 .and_then(Value::as_str)
2888 .is_some_and(|path| declarative_source_route_path_has_segment(path, segment))
2889 })
2890 });
2891 if require_generated_admin || generated_admin_manage.is_some() {
2892 return generated_admin_manage.cloned();
2893 }
2894 let create_action_id = format!("create_{entity_id}");
2895 routes.iter().find_map(|value| {
2896 if value.get("kind").and_then(Value::as_str) != Some("action")
2897 || value.get("action_id").and_then(Value::as_str) != Some(create_action_id.as_str())
2898 {
2899 return None;
2900 }
2901 let mut route = value.clone();
2902 let manage_path = route
2903 .get("path")
2904 .and_then(Value::as_str)
2905 .and_then(declarative_source_manage_path_from_create_route)?;
2906 route["path"] = Value::String(manage_path);
2907 Some(route)
2908 })
2909}
2910
2911/// Derives missing generated-admin child routes and writes the route artifact.
2912///
2913/// # Parameters
2914///
2915/// - `artifact_dir`: Input consumed by [`crate::declarative::declarative_source_derive_generated_admin_routes`].
2916///
2917/// # Returns
2918///
2919/// Returns [`Some`] when [`crate::declarative::declarative_source_derive_generated_admin_routes`] can produce a value and [`None`] otherwise.
2920///
2921/// # Performance
2922///
2923/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derive_generated_admin_routes`].
2924///
2925/// # Examples
2926///
2927/// ```rust,ignore
2928/// let _result = pharos_runtime_compiler::declarative::declarative_source_derive_generated_admin_routes(std::path::Path::new("build/dev_docs"));
2929/// ```
2930///
2931/// # Related
2932///
2933/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
2934pub fn declarative_source_derive_generated_admin_routes(artifact_dir: &Path) -> Option<usize> {
2935 let root = read_framework_contract(artifact_dir, "routes.json")?;
2936 let surfaces = read_framework_contract(artifact_dir, "generated-surfaces.json")?;
2937 let mut routes = root.get("routes")?.as_array()?.to_vec();
2938 let mut added = 0usize;
2939 for surface in surfaces.get("surfaces")?.as_array()? {
2940 if surface.get("surface_kind").and_then(Value::as_str) != Some("generated_admin") {
2941 continue;
2942 }
2943 let operations = surface.get("operations")?;
2944 let manage_path = operations.get("manage")?.get("path")?.as_str()?;
2945 let manage_route_path = declarative_source_surface_path_to_route_path(manage_path)?;
2946 let manage = routes.iter().find(|route| {
2947 route.get("kind").and_then(Value::as_str) == Some("page")
2948 && route.get("page_id").and_then(Value::as_str) == Some("generated_admin_manage")
2949 && route.get("path").and_then(Value::as_str) == Some(manage_route_path.as_str())
2950 });
2951 let Some(manage) = manage else {
2952 continue;
2953 };
2954 let prefix = manage
2955 .get("route_id")
2956 .and_then(Value::as_str)
2957 .unwrap_or("generated")
2958 .strip_suffix("_manage")
2959 .unwrap_or("generated")
2960 .to_owned();
2961 let auth_gate = manage
2962 .get("auth_gate")
2963 .and_then(Value::as_str)
2964 .map(ToOwned::to_owned);
2965 let policy_gate = manage
2966 .get("policy_gate")
2967 .and_then(Value::as_str)
2968 .map(ToOwned::to_owned);
2969 let entity_id = surface
2970 .get("entity_id")
2971 .and_then(Value::as_str)
2972 .unwrap_or("entity")
2973 .to_owned();
2974 let mut route_specs = vec![
2975 (
2976 "create".to_owned(),
2977 "create".to_owned(),
2978 "action".to_owned(),
2979 format!("create_{entity_id}"),
2980 ),
2981 (
2982 "edit".to_owned(),
2983 "edit".to_owned(),
2984 "page".to_owned(),
2985 format!("{entity_id}_edit"),
2986 ),
2987 (
2988 "update".to_owned(),
2989 "update".to_owned(),
2990 "action".to_owned(),
2991 format!("update_{entity_id}"),
2992 ),
2993 (
2994 "delete".to_owned(),
2995 "delete".to_owned(),
2996 "action".to_owned(),
2997 format!("delete_{entity_id}"),
2998 ),
2999 ];
3000 if operations.get("restore").is_some() {
3001 route_specs.push((
3002 "restore".to_owned(),
3003 "restore".to_owned(),
3004 "action".to_owned(),
3005 format!("restore_{entity_id}"),
3006 ));
3007 }
3008 for (suffix, operation, kind, id) in route_specs {
3009 let route_id = format!("{prefix}_{suffix}");
3010 if routes
3011 .iter()
3012 .any(|route| route.get("route_id").and_then(Value::as_str) == Some(&route_id))
3013 {
3014 continue;
3015 }
3016 let Some(path) = operations
3017 .get(operation)
3018 .and_then(|value| value.get("path"))
3019 .and_then(Value::as_str)
3020 .and_then(declarative_source_surface_path_to_route_path)
3021 else {
3022 continue;
3023 };
3024 if declarative_source_append_generated_route_mut(
3025 &mut routes,
3026 &route_id,
3027 &path,
3028 &kind,
3029 &id,
3030 auth_gate.as_deref(),
3031 policy_gate.as_deref(),
3032 Some("generated_admin_shell"),
3033 ) {
3034 added += 1;
3035 }
3036 }
3037 }
3038 let mut result = root;
3039 result["routes"] = Value::Array(routes);
3040 pharos_runtime_core::fs::write_file_within(
3041 artifact_dir,
3042 &artifact_dir.join("data/contracts/routes.json"),
3043 serde_json::to_string_pretty(&result).ok()?.as_bytes(),
3044 0o644,
3045 )
3046 .ok()?;
3047 Some(added)
3048}
3049
3050/// Derives missing generated-api mutation routes and writes the route artifact.
3051///
3052/// # Parameters
3053///
3054/// - `artifact_dir`: Input consumed by [`crate::declarative::declarative_source_derive_generated_api_routes`].
3055///
3056/// # Returns
3057///
3058/// Returns [`Some`] when [`crate::declarative::declarative_source_derive_generated_api_routes`] can produce a value and [`None`] otherwise.
3059///
3060/// # Performance
3061///
3062/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::declarative_source_derive_generated_api_routes`].
3063///
3064/// # Examples
3065///
3066/// ```rust,ignore
3067/// let _result = pharos_runtime_compiler::declarative::declarative_source_derive_generated_api_routes(std::path::Path::new("build/dev_docs"));
3068/// ```
3069///
3070/// # Related
3071///
3072/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3073pub fn declarative_source_derive_generated_api_routes(artifact_dir: &Path) -> Option<usize> {
3074 let root = read_framework_contract(artifact_dir, "routes.json")?;
3075 let surfaces = read_framework_contract(artifact_dir, "generated-surfaces.json")?;
3076 let mut routes = root.get("routes")?.as_array()?.to_vec();
3077 let mut added = 0usize;
3078 for surface in surfaces.get("surfaces")?.as_array()? {
3079 if surface.get("surface_kind").and_then(Value::as_str) != Some("generated_api") {
3080 continue;
3081 }
3082 let operations = surface.get("operations")?;
3083 let entity_id = surface
3084 .get("entity_id")
3085 .and_then(Value::as_str)
3086 .unwrap_or("entity")
3087 .to_owned();
3088 let policy_gate = operations
3089 .get("create")
3090 .and_then(|value| value.get("capability"))
3091 .and_then(Value::as_str)
3092 .map(ToOwned::to_owned)
3093 .or_else(|| {
3094 operations
3095 .as_object()?
3096 .values()
3097 .find_map(|value| value.get("capability").and_then(Value::as_str))
3098 .map(ToOwned::to_owned)
3099 });
3100 let auth_gate = Some("session_required");
3101 let mut route_specs = vec![
3102 (
3103 "api_create".to_owned(),
3104 "create".to_owned(),
3105 format!("create_{entity_id}"),
3106 ),
3107 (
3108 "api_update".to_owned(),
3109 "update".to_owned(),
3110 format!("update_{entity_id}"),
3111 ),
3112 (
3113 "api_delete".to_owned(),
3114 "delete".to_owned(),
3115 format!("delete_{entity_id}"),
3116 ),
3117 ];
3118 if operations.get("restore").is_some() {
3119 route_specs.push((
3120 "api_restore".to_owned(),
3121 "restore".to_owned(),
3122 format!("restore_{entity_id}"),
3123 ));
3124 }
3125 for (suffix, operation, action_id) in route_specs {
3126 let route_id = format!("{entity_id}_{suffix}");
3127 if routes
3128 .iter()
3129 .any(|route| route.get("route_id").and_then(Value::as_str) == Some(&route_id))
3130 {
3131 continue;
3132 }
3133 let Some(path) = operations
3134 .get(operation)
3135 .and_then(|value| value.get("path"))
3136 .and_then(Value::as_str)
3137 .and_then(declarative_source_surface_path_to_route_path)
3138 else {
3139 continue;
3140 };
3141 if declarative_source_append_generated_route_mut(
3142 &mut routes,
3143 &route_id,
3144 &path,
3145 "action",
3146 &action_id,
3147 auth_gate,
3148 policy_gate.as_deref(),
3149 Some("generated_admin_shell"),
3150 ) {
3151 if let Some(route) = routes.last_mut().and_then(Value::as_object_mut) {
3152 route.insert(
3153 "request_hooks".to_owned(),
3154 declarative_source_generated_api_request_hooks(),
3155 );
3156 }
3157 added += 1;
3158 }
3159 }
3160 }
3161 let mut result = root;
3162 result["routes"] = Value::Array(routes);
3163 pharos_runtime_core::fs::write_file_within(
3164 artifact_dir,
3165 &artifact_dir.join("data/contracts/routes.json"),
3166 serde_json::to_string_pretty(&result).ok()?.as_bytes(),
3167 0o644,
3168 )
3169 .ok()?;
3170 Some(added)
3171}
3172
3173/// Resolves an app-owned artifact template without falling back to shell commands.
3174///
3175/// # Parameters
3176///
3177/// - `app_root`: Input consumed by [`crate::declarative::artifact_shared_template_path`].
3178/// - `relative_path`: Input consumed by [`crate::declarative::artifact_shared_template_path`].
3179///
3180/// # Returns
3181///
3182/// Returns [`Some`] when [`crate::declarative::artifact_shared_template_path`] can produce a value and [`None`] otherwise.
3183///
3184/// # Examples
3185///
3186/// ```rust,ignore
3187/// let _result = pharos_runtime_compiler::declarative::artifact_shared_template_path(std::path::Path::new("apps/dev_docs"), "page");
3188/// ```
3189///
3190/// # Related
3191///
3192/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3193pub fn artifact_shared_template_path(app_root: &Path, relative_path: &str) -> Option<PathBuf> {
3194 let relative = safe_relative_path(relative_path)?;
3195 let candidate = app_root.join("assets").join(relative);
3196 candidate.is_file().then_some(candidate)
3197}
3198
3199const YAML_INCLUDE_MAX_DEPTH: usize = 32;
3200const YAML_BUFFER_LIMIT: usize = 16 * 1024 * 1024;
3201
3202/// Appends bounded YAML source text to an output buffer.
3203///
3204/// # Parameters
3205///
3206/// - `buffer`: Input consumed by [`crate::declarative::yaml_buffer_append`].
3207/// - `text`: Input consumed by [`crate::declarative::yaml_buffer_append`].
3208///
3209/// # Returns
3210///
3211/// Returns `true` when the condition checked by [`crate::declarative::yaml_buffer_append`] holds and `false` otherwise.
3212///
3213/// # Examples
3214///
3215/// ```rust,ignore
3216/// let _result = pharos_runtime_compiler::declarative::yaml_buffer_append(&mut [], "{}");
3217/// ```
3218///
3219/// # Related
3220///
3221/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3222pub fn yaml_buffer_append(buffer: &mut String, text: &str) -> bool {
3223 if buffer.len().saturating_add(text.len()) > YAML_BUFFER_LIMIT {
3224 return false;
3225 }
3226 buffer.push_str(text);
3227 true
3228}
3229
3230/// Returns the parent directory of a YAML source file.
3231///
3232/// # Parameters
3233///
3234/// - `file_path`: Input consumed by [`crate::declarative::yaml_dup_parent_dir`].
3235///
3236/// # Returns
3237///
3238/// Returns [`Some`] when [`crate::declarative::yaml_dup_parent_dir`] can produce a value and [`None`] otherwise.
3239///
3240/// # Examples
3241///
3242/// ```rust,ignore
3243/// let _result = pharos_runtime_compiler::declarative::yaml_dup_parent_dir(todo!());
3244/// ```
3245///
3246/// # Related
3247///
3248/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3249pub fn yaml_dup_parent_dir(file_path: &str) -> Option<String> {
3250 Path::new(file_path)
3251 .parent()
3252 .map(|path| path.to_string_lossy().into_owned())
3253}
3254
3255/// Resolves a safe include path relative to a YAML source file.
3256///
3257/// # Parameters
3258///
3259/// - `base_file_path`: Input consumed by [`crate::declarative::yaml_join_relative_path`].
3260/// - `relative_path`: Input consumed by [`crate::declarative::yaml_join_relative_path`].
3261///
3262/// # Returns
3263///
3264/// Returns [`Some`] when [`crate::declarative::yaml_join_relative_path`] can produce a value and [`None`] otherwise.
3265///
3266/// # Examples
3267///
3268/// ```rust,ignore
3269/// let _result = pharos_runtime_compiler::declarative::yaml_join_relative_path(todo!(), "page");
3270/// ```
3271///
3272/// # Related
3273///
3274/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3275pub fn yaml_join_relative_path(base_file_path: &str, relative_path: &str) -> Option<String> {
3276 if relative_path.is_empty()
3277 || Path::new(relative_path).is_absolute()
3278 || relative_path.contains("..")
3279 {
3280 return None;
3281 }
3282 Some(
3283 Path::new(base_file_path)
3284 .parent()?
3285 .join(relative_path)
3286 .to_string_lossy()
3287 .into_owned(),
3288 )
3289}
3290
3291/// Identifies the supported YAML include token form.
3292///
3293/// # Parameters
3294///
3295/// - `token`: Input consumed by [`crate::declarative::yaml_token_is_include_path`].
3296///
3297/// # Returns
3298///
3299/// Returns `true` when the condition checked by [`crate::declarative::yaml_token_is_include_path`] holds and `false` otherwise.
3300///
3301/// # Examples
3302///
3303/// ```rust,ignore
3304/// let _result = pharos_runtime_compiler::declarative::yaml_token_is_include_path(todo!());
3305/// ```
3306///
3307/// # Related
3308///
3309/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3310pub fn yaml_token_is_include_path(token: &str) -> bool {
3311 token.trim().starts_with("!include ") || token.trim().starts_with("include:")
3312}
3313
3314/// Parses `!include relative[#section]` into its two components.
3315///
3316/// # Parameters
3317///
3318/// - `token`: Input consumed by [`crate::declarative::yaml_parse_include_token`].
3319/// - `Option<String>`: Input consumed by [`crate::declarative::yaml_parse_include_token`].
3320///
3321/// # Returns
3322///
3323/// Returns [`Some`] when [`crate::declarative::yaml_parse_include_token`] can produce a value and [`None`] otherwise.
3324///
3325/// # Examples
3326///
3327/// ```rust,ignore
3328/// let _result = pharos_runtime_compiler::declarative::yaml_parse_include_token(todo!(), todo!());
3329/// ```
3330///
3331/// # Related
3332///
3333/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3334pub fn yaml_parse_include_token(token: &str) -> Option<(String, Option<String>)> {
3335 if !yaml_token_is_include_path(token) {
3336 return None;
3337 }
3338 let raw = token
3339 .trim()
3340 .strip_prefix("!include ")
3341 .or_else(|| token.trim().strip_prefix("include:"))?
3342 .trim();
3343 let (path, section) = raw.split_once('#').map_or((raw, None), |(path, section)| {
3344 (path, Some(section.to_owned()))
3345 });
3346 (!path.is_empty() && !path.contains("..")).then(|| (path.to_owned(), section))
3347}
3348
3349/// Detects an include directive on one YAML line.
3350///
3351/// # Parameters
3352///
3353/// - `line`: Input consumed by [`crate::declarative::yaml_line_is_fields_include`].
3354/// - `String`: Input consumed by [`crate::declarative::yaml_line_is_fields_include`].
3355///
3356/// # Returns
3357///
3358/// Returns [`Some`] when [`crate::declarative::yaml_line_is_fields_include`] can produce a value and [`None`] otherwise.
3359///
3360/// # Examples
3361///
3362/// ```rust,ignore
3363/// let _result = pharos_runtime_compiler::declarative::yaml_line_is_fields_include(todo!(), todo!());
3364/// ```
3365///
3366/// # Related
3367///
3368/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3369pub fn yaml_line_is_fields_include(line: &str) -> Option<(usize, String)> {
3370 let indent = line
3371 .len()
3372 .saturating_sub(line.trim_start_matches(' ').len());
3373 let token = line.trim();
3374 yaml_parse_include_token(token).map(|(path, section)| {
3375 (
3376 indent,
3377 format!(
3378 "{}{}",
3379 path,
3380 section.map(|value| format!("#{value}")).unwrap_or_default()
3381 ),
3382 )
3383 })
3384}
3385
3386/// Looks up a dotted section in a compiled YAML JSON value.
3387///
3388/// # Parameters
3389///
3390/// - `root`: Input consumed by [`crate::declarative::yaml_lookup_include_section_value`].
3391/// - `section_path`: Input consumed by [`crate::declarative::yaml_lookup_include_section_value`].
3392///
3393/// # Returns
3394///
3395/// Returns [`Some`] when [`crate::declarative::yaml_lookup_include_section_value`] can produce a value and [`None`] otherwise.
3396///
3397/// # Examples
3398///
3399/// ```rust,ignore
3400/// let _result = pharos_runtime_compiler::declarative::yaml_lookup_include_section_value(std::path::Path::new("."), todo!());
3401/// ```
3402///
3403/// # Related
3404///
3405/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3406pub fn yaml_lookup_include_section_value<'a>(
3407 root: &'a Value,
3408 section_path: &str,
3409) -> Option<&'a Value> {
3410 declarative_source_resolve_dotted_path(root, section_path)
3411}
3412
3413/// Adds indentation to every line of an included YAML block.
3414///
3415/// # Parameters
3416///
3417/// - `buffer`: Input consumed by [`crate::declarative::yaml_append_indented_block`].
3418/// - `indent_prefix`: Input consumed by [`crate::declarative::yaml_append_indented_block`].
3419/// - `include_text`: Input consumed by [`crate::declarative::yaml_append_indented_block`].
3420///
3421/// # Returns
3422///
3423/// Returns `true` when the condition checked by [`crate::declarative::yaml_append_indented_block`] holds and `false` otherwise.
3424///
3425/// # Performance
3426///
3427/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::yaml_append_indented_block`].
3428///
3429/// # Examples
3430///
3431/// ```rust,ignore
3432/// let _result = pharos_runtime_compiler::declarative::yaml_append_indented_block(&mut [], todo!(), todo!());
3433/// ```
3434///
3435/// # Related
3436///
3437/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3438pub fn yaml_append_indented_block(
3439 buffer: &mut String,
3440 indent_prefix: &str,
3441 include_text: &str,
3442) -> bool {
3443 for line in include_text.lines() {
3444 if !yaml_buffer_append(buffer, indent_prefix)
3445 || !yaml_buffer_append(buffer, line)
3446 || !yaml_buffer_append(buffer, "\n")
3447 {
3448 return false;
3449 }
3450 }
3451 true
3452}
3453
3454/// Performs YAML preprocess includes inner.
3455fn yaml_preprocess_includes_inner(path: &Path, depth: usize) -> Option<String> {
3456 if depth > YAML_INCLUDE_MAX_DEPTH {
3457 return None;
3458 }
3459 let text = fs::read_to_string(path).ok()?;
3460 if text.len() > YAML_BUFFER_LIMIT {
3461 return None;
3462 }
3463 let mut output = String::new();
3464 for line in text.lines() {
3465 if let Some((indent, token)) = yaml_line_is_fields_include(line) {
3466 let (relative, section) = yaml_parse_include_token(&format!("!include {token}"))?;
3467 let child_path = yaml_join_relative_path(&path.to_string_lossy(), &relative)?;
3468 let child_text = yaml_preprocess_includes_inner(Path::new(&child_path), depth + 1)?;
3469 let child_text = if let Some(section) = section {
3470 let value = yaml_read_as_json(&child_path)?;
3471 serde_json::to_string_pretty(yaml_lookup_include_section_value(&value, §ion)?)
3472 .ok()?
3473 } else {
3474 child_text
3475 };
3476 if !yaml_append_indented_block(&mut output, &" ".repeat(indent), &child_text) {
3477 return None;
3478 }
3479 } else if !yaml_buffer_append(&mut output, line) || !yaml_buffer_append(&mut output, "\n") {
3480 return None;
3481 }
3482 }
3483 Some(output)
3484}
3485
3486/// Preprocesses bounded recursive YAML includes using app-relative paths.
3487///
3488/// # Parameters
3489///
3490/// - `file_path`: Input consumed by [`crate::declarative::yaml_preprocess_includes_from_file`].
3491/// - `depth`: Input consumed by [`crate::declarative::yaml_preprocess_includes_from_file`].
3492///
3493/// # Returns
3494///
3495/// Returns [`Some`] when [`crate::declarative::yaml_preprocess_includes_from_file`] can produce a value and [`None`] otherwise.
3496///
3497/// # Examples
3498///
3499/// ```rust,ignore
3500/// let _result = pharos_runtime_compiler::declarative::yaml_preprocess_includes_from_file(todo!(), todo!());
3501/// ```
3502///
3503/// # Related
3504///
3505/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3506pub fn yaml_preprocess_includes_from_file(file_path: &str, depth: usize) -> Option<String> {
3507 yaml_preprocess_includes_inner(Path::new(file_path), depth)
3508}
3509
3510/// Identifies an integer YAML scalar.
3511///
3512/// # Parameters
3513///
3514/// - `value`: Input consumed by [`crate::declarative::yaml_scalar_is_integer`].
3515///
3516/// # Returns
3517///
3518/// Returns `true` when the condition checked by [`crate::declarative::yaml_scalar_is_integer`] holds and `false` otherwise.
3519///
3520/// # Examples
3521///
3522/// ```rust,ignore
3523/// let _result = pharos_runtime_compiler::declarative::yaml_scalar_is_integer("value");
3524/// ```
3525///
3526/// # Related
3527///
3528/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3529pub fn yaml_scalar_is_integer(value: &str) -> bool {
3530 value.trim().parse::<i64>().is_ok()
3531}
3532
3533/// Identifies a finite floating-point YAML scalar.
3534///
3535/// # Parameters
3536///
3537/// - `value`: Input consumed by [`crate::declarative::yaml_scalar_is_float`].
3538///
3539/// # Returns
3540///
3541/// Returns `true` when the condition checked by [`crate::declarative::yaml_scalar_is_float`] holds and `false` otherwise.
3542///
3543/// # Examples
3544///
3545/// ```rust,ignore
3546/// let _result = pharos_runtime_compiler::declarative::yaml_scalar_is_float("value");
3547/// ```
3548///
3549/// # Related
3550///
3551/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3552pub fn yaml_scalar_is_float(value: &str) -> bool {
3553 value.trim().parse::<f64>().is_ok() && value.trim().contains('.')
3554}
3555
3556/// Identifies a YAML boolean scalar.
3557///
3558/// # Parameters
3559///
3560/// - `value`: Input consumed by [`crate::declarative::yaml_scalar_is_bool`].
3561///
3562/// # Returns
3563///
3564/// Returns `true` when the condition checked by [`crate::declarative::yaml_scalar_is_bool`] holds and `false` otherwise.
3565///
3566/// # Examples
3567///
3568/// ```rust,ignore
3569/// let _result = pharos_runtime_compiler::declarative::yaml_scalar_is_bool("value");
3570/// ```
3571///
3572/// # Related
3573///
3574/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3575pub fn yaml_scalar_is_bool(value: &str) -> bool {
3576 matches!(
3577 value.trim().to_ascii_lowercase().as_str(),
3578 "true" | "false" | "yes" | "no" | "on" | "off"
3579 )
3580}
3581
3582/// Identifies a YAML null scalar.
3583///
3584/// # Parameters
3585///
3586/// - `value`: Input consumed by [`crate::declarative::yaml_scalar_is_null`].
3587///
3588/// # Returns
3589///
3590/// Returns `true` when the condition checked by [`crate::declarative::yaml_scalar_is_null`] holds and `false` otherwise.
3591///
3592/// # Examples
3593///
3594/// ```rust,ignore
3595/// let _result = pharos_runtime_compiler::declarative::yaml_scalar_is_null("value");
3596/// ```
3597///
3598/// # Related
3599///
3600/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3601pub fn yaml_scalar_is_null(value: &str) -> bool {
3602 matches!(value.trim().to_ascii_lowercase().as_str(), "null" | "~")
3603}
3604
3605/// Identifies the YAML boolean tag.
3606///
3607/// # Parameters
3608///
3609/// - `tag`: Input consumed by [`crate::declarative::yaml_tag_is_bool`].
3610///
3611/// # Returns
3612///
3613/// Returns `true` when the condition checked by [`crate::declarative::yaml_tag_is_bool`] holds and `false` otherwise.
3614///
3615/// # Examples
3616///
3617/// ```rust,ignore
3618/// let _result = pharos_runtime_compiler::declarative::yaml_tag_is_bool(todo!());
3619/// ```
3620///
3621/// # Related
3622///
3623/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3624pub fn yaml_tag_is_bool(tag: &str) -> bool {
3625 matches!(tag, "!!bool" | "tag:yaml.org,2002:bool")
3626}
3627
3628/// Identifies the YAML floating-point tag.
3629///
3630/// # Parameters
3631///
3632/// - `tag`: Input consumed by [`crate::declarative::yaml_tag_is_float`].
3633///
3634/// # Returns
3635///
3636/// Returns `true` when the condition checked by [`crate::declarative::yaml_tag_is_float`] holds and `false` otherwise.
3637///
3638/// # Examples
3639///
3640/// ```rust,ignore
3641/// let _result = pharos_runtime_compiler::declarative::yaml_tag_is_float(todo!());
3642/// ```
3643///
3644/// # Related
3645///
3646/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3647pub fn yaml_tag_is_float(tag: &str) -> bool {
3648 matches!(tag, "!!float" | "tag:yaml.org,2002:float")
3649}
3650
3651/// Identifies the YAML integer tag.
3652///
3653/// # Parameters
3654///
3655/// - `tag`: Input consumed by [`crate::declarative::yaml_tag_is_int`].
3656///
3657/// # Returns
3658///
3659/// Returns `true` when the condition checked by [`crate::declarative::yaml_tag_is_int`] holds and `false` otherwise.
3660///
3661/// # Examples
3662///
3663/// ```rust,ignore
3664/// let _result = pharos_runtime_compiler::declarative::yaml_tag_is_int(todo!());
3665/// ```
3666///
3667/// # Related
3668///
3669/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3670pub fn yaml_tag_is_int(tag: &str) -> bool {
3671 matches!(tag, "!!int" | "tag:yaml.org,2002:int")
3672}
3673
3674/// Identifies the YAML null tag.
3675///
3676/// # Parameters
3677///
3678/// - `tag`: Input consumed by [`crate::declarative::yaml_tag_is_null`].
3679///
3680/// # Returns
3681///
3682/// Returns `true` when the condition checked by [`crate::declarative::yaml_tag_is_null`] holds and `false` otherwise.
3683///
3684/// # Examples
3685///
3686/// ```rust,ignore
3687/// let _result = pharos_runtime_compiler::declarative::yaml_tag_is_null(todo!());
3688/// ```
3689///
3690/// # Related
3691///
3692/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3693pub fn yaml_tag_is_null(tag: &str) -> bool {
3694 matches!(tag, "!!null" | "tag:yaml.org,2002:null")
3695}
3696
3697/// Identifies the YAML string tag.
3698///
3699/// # Parameters
3700///
3701/// - `tag`: Input consumed by [`crate::declarative::yaml_tag_is_str`].
3702///
3703/// # Returns
3704///
3705/// Returns `true` when the condition checked by [`crate::declarative::yaml_tag_is_str`] holds and `false` otherwise.
3706///
3707/// # Examples
3708///
3709/// ```rust,ignore
3710/// let _result = pharos_runtime_compiler::declarative::yaml_tag_is_str(todo!());
3711/// ```
3712///
3713/// # Related
3714///
3715/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3716pub fn yaml_tag_is_str(tag: &str) -> bool {
3717 matches!(tag, "!!str" | "tag:yaml.org,2002:str")
3718}
3719
3720#[derive(Debug, Clone, Default, Eq, PartialEq)]
3721/// Public struct `YamlCompileContext` used by this module.
3722///
3723/// # Fields
3724///
3725/// - `stack`: Stored field on `YamlCompileContext`.
3726/// - `root`: Stored field on `YamlCompileContext`.
3727///
3728/// # Examples
3729///
3730/// ```rust,ignore
3731/// let _ = std::mem::size_of::<pharos_runtime_compiler::declarative::YamlCompileContext>();
3732/// ```
3733///
3734/// # Related
3735///
3736/// - [`crate::declarative`] for constructors, helpers, and neighboring runtime types.
3737pub struct YamlCompileContext {
3738 pub stack: Vec<Value>,
3739 pub root: Option<Value>,
3740}
3741
3742/// Initializes a YAML compile context.
3743///
3744/// # Parameters
3745///
3746/// - `context`: Input consumed by [`crate::declarative::yaml_compile_context_init`].
3747///
3748/// # Returns
3749///
3750/// Returns after [`crate::declarative::yaml_compile_context_init`] applies its in-place effect.
3751///
3752/// # Examples
3753///
3754/// ```rust,ignore
3755/// let _result = pharos_runtime_compiler::declarative::yaml_compile_context_init(todo!());
3756/// ```
3757///
3758/// # Related
3759///
3760/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3761pub fn yaml_compile_context_init(context: &mut YamlCompileContext) {
3762 context.stack.clear();
3763 context.root = None;
3764}
3765
3766/// Releases a YAML compile context.
3767///
3768/// # Parameters
3769///
3770/// - `context`: Input consumed by [`crate::declarative::yaml_compile_context_free`].
3771///
3772/// # Returns
3773///
3774/// Returns after [`crate::declarative::yaml_compile_context_free`] applies its in-place effect.
3775///
3776/// # Examples
3777///
3778/// ```rust,ignore
3779/// let _result = pharos_runtime_compiler::declarative::yaml_compile_context_free(todo!());
3780/// ```
3781///
3782/// # Related
3783///
3784/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3785pub fn yaml_compile_context_free(context: &mut YamlCompileContext) {
3786 context.stack.clear();
3787 context.root = None;
3788}
3789
3790/// Pushes a mapping or sequence container.
3791///
3792/// # Parameters
3793///
3794/// - `context`: Input consumed by [`crate::declarative::yaml_compile_push`].
3795/// - `value`: Input consumed by [`crate::declarative::yaml_compile_push`].
3796///
3797/// # Returns
3798///
3799/// Returns `true` when the condition checked by [`crate::declarative::yaml_compile_push`] holds and `false` otherwise.
3800///
3801/// # Examples
3802///
3803/// ```rust,ignore
3804/// let _result = pharos_runtime_compiler::declarative::yaml_compile_push(todo!(), "value");
3805/// ```
3806///
3807/// # Related
3808///
3809/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3810pub fn yaml_compile_push(context: &mut YamlCompileContext, value: Value) -> bool {
3811 context.stack.push(value);
3812 true
3813}
3814
3815/// Completes and returns the current container.
3816///
3817/// # Parameters
3818///
3819/// - `context`: Input consumed by [`crate::declarative::yaml_compile_pop`].
3820///
3821/// # Returns
3822///
3823/// Returns [`Some`] when [`crate::declarative::yaml_compile_pop`] can produce a value and [`None`] otherwise.
3824///
3825/// # Examples
3826///
3827/// ```rust,ignore
3828/// let _result = pharos_runtime_compiler::declarative::yaml_compile_pop(todo!());
3829/// ```
3830///
3831/// # Related
3832///
3833/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3834pub fn yaml_compile_pop(context: &mut YamlCompileContext) -> Option<Value> {
3835 context.stack.pop()
3836}
3837
3838/// Borrows the current compile container.
3839///
3840/// # Parameters
3841///
3842/// - `context`: Input consumed by [`crate::declarative::yaml_compile_peek`].
3843///
3844/// # Returns
3845///
3846/// Returns [`Some`] when [`crate::declarative::yaml_compile_peek`] can produce a value and [`None`] otherwise.
3847///
3848/// # Examples
3849///
3850/// ```rust,ignore
3851/// let _result = pharos_runtime_compiler::declarative::yaml_compile_peek(todo!());
3852/// ```
3853///
3854/// # Related
3855///
3856/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3857pub fn yaml_compile_peek(context: &YamlCompileContext) -> Option<&Value> {
3858 context.stack.last()
3859}
3860
3861/// Adds a completed value to the active compile container or root.
3862///
3863/// # Parameters
3864///
3865/// - `context`: Input consumed by [`crate::declarative::yaml_compile_add_value`].
3866/// - `value`: Input consumed by [`crate::declarative::yaml_compile_add_value`].
3867///
3868/// # Returns
3869///
3870/// Returns `true` when the condition checked by [`crate::declarative::yaml_compile_add_value`] holds and `false` otherwise.
3871///
3872/// # Examples
3873///
3874/// ```rust,ignore
3875/// let _result = pharos_runtime_compiler::declarative::yaml_compile_add_value(todo!(), "value");
3876/// ```
3877///
3878/// # Related
3879///
3880/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3881pub fn yaml_compile_add_value(context: &mut YamlCompileContext, value: Value) -> bool {
3882 if let Some(parent) = context.stack.last_mut() {
3883 match parent {
3884 Value::Array(items) => items.push(value),
3885 Value::Object(map) => {
3886 let key = map.len().to_string();
3887 map.insert(key, value);
3888 }
3889 _ => return false,
3890 }
3891 } else {
3892 context.root = Some(value);
3893 }
3894 true
3895}
3896
3897/// Starts a YAML mapping container.
3898///
3899/// # Parameters
3900///
3901/// - `context`: Input consumed by [`crate::declarative::yaml_handle_mapping_start`].
3902///
3903/// # Returns
3904///
3905/// Returns `true` when the condition checked by [`crate::declarative::yaml_handle_mapping_start`] holds and `false` otherwise.
3906///
3907/// # Examples
3908///
3909/// ```rust,ignore
3910/// let _result = pharos_runtime_compiler::declarative::yaml_handle_mapping_start(todo!());
3911/// ```
3912///
3913/// # Related
3914///
3915/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3916pub fn yaml_handle_mapping_start(context: &mut YamlCompileContext) -> bool {
3917 yaml_compile_push(context, Value::Object(Map::new()))
3918}
3919
3920/// Ends a YAML mapping container.
3921///
3922/// # Parameters
3923///
3924/// - `context`: Input consumed by [`crate::declarative::yaml_handle_mapping_end`].
3925///
3926/// # Returns
3927///
3928/// Returns `true` when the condition checked by [`crate::declarative::yaml_handle_mapping_end`] holds and `false` otherwise.
3929///
3930/// # Examples
3931///
3932/// ```rust,ignore
3933/// let _result = pharos_runtime_compiler::declarative::yaml_handle_mapping_end(todo!());
3934/// ```
3935///
3936/// # Related
3937///
3938/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3939pub fn yaml_handle_mapping_end(context: &mut YamlCompileContext) -> bool {
3940 let value = yaml_compile_pop(context);
3941 value.is_some_and(|value| yaml_compile_add_value(context, value))
3942}
3943
3944/// Starts a YAML sequence container.
3945///
3946/// # Parameters
3947///
3948/// - `context`: Input consumed by [`crate::declarative::yaml_handle_sequence_start`].
3949///
3950/// # Returns
3951///
3952/// Returns `true` when the condition checked by [`crate::declarative::yaml_handle_sequence_start`] holds and `false` otherwise.
3953///
3954/// # Examples
3955///
3956/// ```rust,ignore
3957/// let _result = pharos_runtime_compiler::declarative::yaml_handle_sequence_start(todo!());
3958/// ```
3959///
3960/// # Related
3961///
3962/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3963pub fn yaml_handle_sequence_start(context: &mut YamlCompileContext) -> bool {
3964 yaml_compile_push(context, Value::Array(Vec::new()))
3965}
3966
3967/// Ends a YAML sequence container.
3968///
3969/// # Parameters
3970///
3971/// - `context`: Input consumed by [`crate::declarative::yaml_handle_sequence_end`].
3972///
3973/// # Returns
3974///
3975/// Returns `true` when the condition checked by [`crate::declarative::yaml_handle_sequence_end`] holds and `false` otherwise.
3976///
3977/// # Examples
3978///
3979/// ```rust,ignore
3980/// let _result = pharos_runtime_compiler::declarative::yaml_handle_sequence_end(todo!());
3981/// ```
3982///
3983/// # Related
3984///
3985/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
3986pub fn yaml_handle_sequence_end(context: &mut YamlCompileContext) -> bool {
3987 let value = yaml_compile_pop(context);
3988 value.is_some_and(|value| yaml_compile_add_value(context, value))
3989}
3990
3991/// Adds a scalar YAML value using the same scalar rules as saphyr.
3992///
3993/// # Parameters
3994///
3995/// - `context`: Input consumed by [`crate::declarative::yaml_handle_scalar`].
3996/// - `scalar`: Input consumed by [`crate::declarative::yaml_handle_scalar`].
3997///
3998/// # Returns
3999///
4000/// Returns `true` when the condition checked by [`crate::declarative::yaml_handle_scalar`] holds and `false` otherwise.
4001///
4002/// # Examples
4003///
4004/// ```rust,ignore
4005/// let _result = pharos_runtime_compiler::declarative::yaml_handle_scalar(todo!(), todo!());
4006/// ```
4007///
4008/// # Related
4009///
4010/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
4011pub fn yaml_handle_scalar(context: &mut YamlCompileContext, scalar: &str) -> bool {
4012 let value = if yaml_scalar_is_null(scalar) {
4013 Value::Null
4014 } else if yaml_scalar_is_bool(scalar) {
4015 Value::Bool(matches!(
4016 scalar.trim().to_ascii_lowercase().as_str(),
4017 "true" | "yes" | "on"
4018 ))
4019 } else if yaml_scalar_is_integer(scalar) {
4020 Value::Number(scalar.trim().parse::<i64>().ok().unwrap_or_default().into())
4021 } else if yaml_scalar_is_float(scalar) {
4022 serde_json::Number::from_f64(scalar.trim().parse::<f64>().ok().unwrap_or_default())
4023 .map(Value::Number)
4024 .unwrap_or_else(|| Value::String(scalar.to_owned()))
4025 } else {
4026 Value::String(scalar.to_owned())
4027 };
4028 yaml_compile_add_value(context, value)
4029}
4030
4031/// Compiles YAML text to one JSON value.
4032///
4033/// # Parameters
4034///
4035/// - `text`: Input consumed by [`crate::declarative::yaml_compile_to_json`].
4036///
4037/// # Returns
4038///
4039/// Returns [`Some`] when [`crate::declarative::yaml_compile_to_json`] can produce a value and [`None`] otherwise.
4040///
4041/// # Examples
4042///
4043/// ```rust,ignore
4044/// let _result = pharos_runtime_compiler::declarative::yaml_compile_to_json("{}");
4045/// ```
4046///
4047/// # Related
4048///
4049/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
4050pub fn yaml_compile_to_json(text: &str) -> Option<Value> {
4051 pharos_runtime_core::yaml::parse_yaml_text(text)
4052}
4053
4054/// Reads and compiles one YAML file to JSON.
4055///
4056/// # Returns
4057///
4058/// Returns `true` when the condition checked by [`crate::declarative::yaml_compile_enabled`] holds and `false` otherwise.
4059///
4060/// # Examples
4061///
4062/// ```rust,ignore
4063/// let _result = pharos_runtime_compiler::declarative::yaml_compile_enabled();
4064/// ```
4065///
4066/// # Related
4067///
4068/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
4069pub fn yaml_compile_enabled() -> bool {
4070 true
4071}
4072
4073/// Performs YAML read as JSON.
4074///
4075/// # Parameters
4076///
4077/// - `file_path`: Input consumed by [`crate::declarative::yaml_read_as_json`].
4078///
4079/// # Returns
4080///
4081/// Returns [`Some`] when [`crate::declarative::yaml_read_as_json`] can produce a value and [`None`] otherwise.
4082///
4083/// # Performance
4084///
4085/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::declarative::yaml_read_as_json`].
4086///
4087/// # Examples
4088///
4089/// ```rust,ignore
4090/// let _result = pharos_runtime_compiler::declarative::yaml_read_as_json(todo!());
4091/// ```
4092///
4093/// # Related
4094///
4095/// - [`crate::declarative`] for adjacent helpers that share the same runtime surface.
4096pub fn yaml_read_as_json(file_path: &str) -> Option<Value> {
4097 let text = yaml_preprocess_includes_from_file(file_path, 0)
4098 .or_else(|| fs::read_to_string(file_path).ok())?;
4099 yaml_compile_to_json(&text)
4100}
4101
4102#[cfg(test)]
4103mod tests {
4104 use super::{
4105 declarative_source_derive_generated_admin_routes,
4106 declarative_source_derive_generated_api_routes,
4107 declarative_source_derive_generated_surface_fixture,
4108 };
4109 use std::time::{SystemTime, UNIX_EPOCH};
4110
4111 /// Performs test root.
4112 fn test_root(label: &str) -> std::path::PathBuf {
4113 let nonce = SystemTime::now()
4114 .duration_since(UNIX_EPOCH)
4115 .expect("clock")
4116 .as_nanos();
4117 std::env::temp_dir().join(format!(
4118 "pharos-declarative-{label}-{}-{nonce}",
4119 std::process::id()
4120 ))
4121 }
4122
4123 /// Writes write.
4124 fn write(root: &std::path::Path, relative: &str, content: &str) {
4125 let path = root.join(relative);
4126 std::fs::create_dir_all(path.parent().expect("parent")).expect("directories");
4127 std::fs::write(path, content).expect("fixture");
4128 }
4129
4130 #[test]
4131 /// Performs derives generated admin delete route when missing.
4132 fn derives_generated_admin_delete_route_when_missing() {
4133 let root = test_root("generated-admin-routes");
4134 write(
4135 &root,
4136 "data/contracts/routes.json",
4137 r#"{"contract_version":"1.0","routes":[{"route_id":"service_manage","path":"/businesses/:business_slug/services/","kind":"page","page_id":"generated_admin_manage","layout_id":"generated_admin_shell","auth_gate":"session_required","policy_gate":"manage_services","islands":[]}]} "#,
4138 );
4139 write(
4140 &root,
4141 "data/contracts/generated-surfaces.json",
4142 r#"{"fixture_id":"generated_surface_fixture_v1","surfaces":[{"surface_id":"service_generated_admin","surface_kind":"generated_admin","entity_id":"service","operations":{"manage":{"method":"GET","path":"/businesses/{business_slug}/services/","capability":"manage_services"},"create":{"method":"POST","path":"/businesses/{business_slug}/services/new/","capability":"manage_services"},"edit":{"method":"GET","path":"/businesses/{business_slug}/services/{service_id}/edit/","capability":"manage_services"},"update":{"method":"POST","path":"/businesses/{business_slug}/services/{service_id}/edit/","capability":"manage_services"},"delete":{"method":"POST","path":"/businesses/{business_slug}/services/{service_id}/delete/","capability":"manage_services"},"restore":{"method":"POST","path":"/businesses/{business_slug}/services/{service_id}/restore/","capability":"manage_services"}}}]}"#,
4143 );
4144 assert_eq!(
4145 declarative_source_derive_generated_admin_routes(&root),
4146 Some(5)
4147 );
4148 let routes =
4149 std::fs::read_to_string(root.join("data/contracts/routes.json")).expect("routes");
4150 assert!(routes.contains("\"route_id\": \"service_delete\""));
4151 assert!(routes.contains("\"action_id\": \"delete_service\""));
4152 assert!(routes.contains("\"route_id\": \"service_restore\""));
4153 assert!(routes.contains("\"action_id\": \"restore_service\""));
4154 std::fs::remove_dir_all(root).expect("cleanup");
4155 }
4156
4157 #[test]
4158 /// Performs derives generated API mutation routes when missing.
4159 fn derives_generated_api_mutation_routes_when_missing() {
4160 let root = test_root("generated-api-routes");
4161 write(
4162 &root,
4163 "data/contracts/routes.json",
4164 r#"{"contract_version":"1.0","routes":[]}"#,
4165 );
4166 write(
4167 &root,
4168 "data/contracts/generated-surfaces.json",
4169 r#"{"fixture_id":"generated_surface_fixture_v1","surfaces":[{"surface_id":"service_generated_api","surface_kind":"generated_api","entity_id":"service","operations":{"list":{"method":"GET","path":"/api/businesses/{business_slug}/services/","capability":"manage_services"},"schema":{"method":"GET","path":"/api/businesses/{business_slug}/services/schema/","capability":"manage_services"},"create":{"method":"POST","path":"/api/businesses/{business_slug}/services/","capability":"manage_services"},"update":{"method":"POST","path":"/api/businesses/{business_slug}/services/{service_id}/","capability":"manage_services"},"delete":{"method":"POST","path":"/api/businesses/{business_slug}/services/{service_id}/delete/","capability":"manage_services"},"restore":{"method":"POST","path":"/api/businesses/{business_slug}/services/{service_id}/restore/","capability":"manage_services"}}}]}"#,
4170 );
4171 assert_eq!(
4172 declarative_source_derive_generated_api_routes(&root),
4173 Some(4)
4174 );
4175 let routes =
4176 std::fs::read_to_string(root.join("data/contracts/routes.json")).expect("routes");
4177 assert!(routes.contains("\"route_id\": \"service_api_create\""));
4178 assert!(routes.contains("\"route_id\": \"service_api_update\""));
4179 assert!(routes.contains("\"route_id\": \"service_api_delete\""));
4180 assert!(routes.contains("\"route_id\": \"service_api_restore\""));
4181 assert!(
4182 routes.contains("\"path\": \"/api/businesses/:business_slug/services/:service_id/\"")
4183 );
4184 assert!(routes.contains("\"action_id\": \"update_service\""));
4185 assert!(routes.contains("\"action_id\": \"restore_service\""));
4186 assert!(routes.contains("\"request_hooks\": [\n \"json_body_required\"\n ]"));
4187 std::fs::remove_dir_all(root).expect("cleanup");
4188 }
4189
4190 #[test]
4191 /// Performs derives generated API surface without generated admin anchor.
4192 fn derives_generated_api_surface_without_generated_admin_anchor() {
4193 let root = test_root("generated-api-surface-anchor");
4194 write(
4195 &root,
4196 "data/contracts/entities.json",
4197 r#"{"contract_version":"1.0","entities":[{"entity_id":"technician","table":"technician","identity_field":"id","scope_field":"business_id","generated_surfaces":["form","api","manifest"],"fields":[{"field_id":"id","type":"integer"},{"field_id":"business_id","type":"integer"},{"field_id":"name","type":"string"},{"field_id":"email","type":"string"},{"field_id":"is_active","type":"boolean"}],"soft_delete_field":"is_active"}]}"#,
4198 );
4199 write(
4200 &root,
4201 "data/contracts/queries.json",
4202 r#"{"contract_version":"1.0","queries":[{"query_id":"technicians_by_business","entity_id":"technician","filter_set":["business_id","is_active"],"sort_set":["name_asc"]}]}"#,
4203 );
4204 write(
4205 &root,
4206 "data/contracts/forms.json",
4207 r#"{"contract_version":"1.0","forms":[{"form_id":"technician_form","entity_id":"technician","fields":[{"field_id":"name","type":"string"},{"field_id":"email","type":"string"}],"submits_action":"create_technician"}]}"#,
4208 );
4209 write(
4210 &root,
4211 "data/contracts/routes.json",
4212 r#"{"contract_version":"1.0","routes":[{"route_id":"technician_create","path":"/businesses/:business_slug/technicians/new/","kind":"action","action_id":"create_technician","auth_gate":"session_required","policy_gate":"manage_technicians","islands":[]}]}"#,
4213 );
4214 assert_eq!(
4215 declarative_source_derive_generated_surface_fixture(&root),
4216 Some(1)
4217 );
4218 let surfaces = std::fs::read_to_string(root.join("data/contracts/generated-surfaces.json"))
4219 .expect("surfaces");
4220 assert!(surfaces.contains("\"surface_id\": \"technician_generated_api\""));
4221 assert!(surfaces.contains("\"path\": \"/api/businesses/{business_slug}/technicians/\""));
4222 assert!(surfaces.contains("\"restore\""));
4223 assert!(
4224 surfaces
4225 .contains("/api/businesses/{business_slug}/technicians/{technician_id}/restore/")
4226 );
4227 std::fs::remove_dir_all(root).expect("cleanup");
4228 }
4229}