pharos_native/runtime/generated.rs
1use std::fs;
2use std::path::Path;
3
4use serde_json::{Map, Value};
5
6use crate::config::AppRuntime;
7
8use super::business::tenant_id_for_business_base;
9use super::dynamic::generated_surface_label;
10
11#[derive(Debug, Clone, Default, Eq, PartialEq)]
12/// Public struct `GeneratedSurfaceApiMatchRuntime` used by this module.
13///
14/// # Fields
15///
16/// - `fixture_root`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
17/// - `state_root`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
18/// - `form_root`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
19/// - `surface_root`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
20/// - `context_root`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
21/// - `surface_id`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
22/// - `operation_name`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
23/// - `business_slug`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
24/// - `fixture_path`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
25/// - `form_fixture_path`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
26/// - `context_id`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
27/// - `tenant_id`: Stored field on `GeneratedSurfaceApiMatchRuntime`.
28///
29/// # Examples
30///
31/// ```rust,ignore
32/// let _ = std::mem::size_of::<pharos_native::runtime::generated::GeneratedSurfaceApiMatchRuntime>();
33/// ```
34///
35/// # Related
36///
37/// - [`crate::runtime::generated`] for constructors, helpers, and neighboring runtime types.
38pub struct GeneratedSurfaceApiMatchRuntime {
39 pub fixture_root: Value,
40 pub state_root: Value,
41 pub form_root: Option<Value>,
42 pub surface_root: Value,
43 pub context_root: Value,
44 pub surface_id: Option<String>,
45 pub operation_name: Option<String>,
46 pub business_slug: Option<String>,
47 pub fixture_path: Option<String>,
48 pub form_fixture_path: Option<String>,
49 pub context_id: i64,
50 pub tenant_id: i64,
51}
52
53/// Returns the JSON long.
54fn json_long(value: &Value, key: &str, default_value: i64) -> i64 {
55 value
56 .get(key)
57 .and_then(Value::as_i64)
58 .unwrap_or(default_value)
59}
60
61/// Reads JSON file.
62fn read_json_file(path: &Path) -> Option<Value> {
63 serde_json::from_str(&fs::read_to_string(path).ok()?).ok()
64}
65
66/// Performs truthy text.
67///
68/// # Parameters
69///
70/// - `text`: Input consumed by [`crate::runtime::generated::truthy_text`].
71///
72/// # Returns
73///
74/// Returns `true` when the condition checked by [`crate::runtime::generated::truthy_text`] holds and `false` otherwise.
75///
76/// # Examples
77///
78/// ```rust,ignore
79/// let _result = pharos_native::runtime::generated::truthy_text("{}");
80/// ```
81///
82/// # Related
83///
84/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
85pub fn truthy_text(text: &str) -> bool {
86 matches!(
87 text.trim().to_ascii_lowercase().as_str(),
88 "true" | "1" | "yes" | "on"
89 )
90}
91
92/// Performs digits only text.
93fn digits_only_text(text: &str) -> bool {
94 !text.is_empty() && text.chars().all(|ch| ch.is_ascii_digit())
95}
96
97/// Performs value to string.
98fn value_to_string(value: &Value) -> String {
99 match value {
100 Value::Null => String::new(),
101 Value::String(text) => text.clone(),
102 Value::Bool(flag) => flag.to_string(),
103 Value::Number(number) => number.to_string(),
104 _ => serde_json::to_string(value).unwrap_or_default(),
105 }
106}
107
108/// Performs fixture path.
109///
110/// # Parameters
111///
112/// - `app`: Input consumed by [`crate::runtime::generated::fixture_path`].
113/// - `relative_path`: Input consumed by [`crate::runtime::generated::fixture_path`].
114///
115/// # Returns
116///
117/// Returns [`Some`] when [`crate::runtime::generated::fixture_path`] can produce a value and [`None`] otherwise.
118///
119/// # Performance
120///
121/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::fixture_path`].
122///
123/// # Examples
124///
125/// ```rust,ignore
126/// let _result = pharos_native::runtime::generated::fixture_path(todo!(), "page");
127/// ```
128///
129/// # Related
130///
131/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
132pub fn fixture_path(app: &AppRuntime, relative_path: &str) -> Option<String> {
133 if relative_path.is_empty() {
134 return None;
135 }
136 for base in [app.artifact_dir.as_deref(), app.app_root.as_deref()] {
137 let Some(base) = base.filter(|value| !value.is_empty()) else {
138 continue;
139 };
140 let candidate = Path::new(base).join(relative_path);
141 if candidate.exists() {
142 return Some(candidate.to_string_lossy().into_owned());
143 }
144 }
145 None
146}
147
148/// Performs generated surface fixture path.
149///
150/// # Parameters
151///
152/// - `app`: Input consumed by [`crate::runtime::generated::generated_surface_fixture_path`].
153///
154/// # Returns
155///
156/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_fixture_path`] can produce a value and [`None`] otherwise.
157///
158/// # Examples
159///
160/// ```rust,ignore
161/// let _result = pharos_native::runtime::generated::generated_surface_fixture_path(todo!());
162/// ```
163///
164/// # Related
165///
166/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
167pub fn generated_surface_fixture_path(app: &AppRuntime) -> Option<String> {
168 fixture_path(app, "data/contracts/generated-surfaces.json")
169}
170
171/// Performs generated manage path segment.
172fn generated_manage_path_segment(manage_path: &str) -> Option<String> {
173 manage_path
174 .trim_matches('/')
175 .split('/')
176 .filter(|segment| !segment.is_empty())
177 .rev()
178 .find(|segment| !segment.starts_with('{') && !segment.starts_with(':'))
179 .map(ToOwned::to_owned)
180}
181
182/// Builds the runtime generated admin surface find.
183///
184/// # Parameters
185///
186/// - `fixture_root`: Input consumed by [`crate::runtime::generated::runtime_generated_admin_surface_find`].
187/// - `surface_kind`: Input consumed by [`crate::runtime::generated::runtime_generated_admin_surface_find`].
188///
189/// # Returns
190///
191/// Returns [`Some`] when [`crate::runtime::generated::runtime_generated_admin_surface_find`] can produce a value and [`None`] otherwise.
192///
193/// # Performance
194///
195/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::runtime_generated_admin_surface_find`].
196///
197/// # Examples
198///
199/// ```rust,ignore
200/// let _result = pharos_native::runtime::generated::runtime_generated_admin_surface_find(todo!(), todo!());
201/// ```
202///
203/// # Related
204///
205/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
206pub fn runtime_generated_admin_surface_find<'a>(
207 fixture_root: &'a Value,
208 surface_kind: &str,
209) -> Option<&'a Value> {
210 if surface_kind.is_empty() {
211 return None;
212 }
213 fixture_root
214 .get("surfaces")
215 .and_then(Value::as_array)?
216 .iter()
217 .find(|surface| {
218 if surface.get("surface_kind").and_then(Value::as_str) != Some("generated_admin") {
219 return false;
220 }
221 let manage_path = surface
222 .get("operations")
223 .and_then(|value| value.get("manage"))
224 .and_then(|value| value.get("path"))
225 .and_then(Value::as_str);
226 manage_path
227 .and_then(generated_manage_path_segment)
228 .as_deref()
229 == Some(surface_kind)
230 })
231}
232
233/// Performs dynamic dispatch extract param.
234///
235/// # Parameters
236///
237/// - `route_pattern`: Input consumed by [`crate::runtime::generated::dynamic_dispatch_extract_param`].
238/// - `request_path`: Input consumed by [`crate::runtime::generated::dynamic_dispatch_extract_param`].
239/// - `param_name`: Input consumed by [`crate::runtime::generated::dynamic_dispatch_extract_param`].
240///
241/// # Returns
242///
243/// Returns [`Some`] when [`crate::runtime::generated::dynamic_dispatch_extract_param`] can produce a value and [`None`] otherwise.
244///
245/// # Performance
246///
247/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::dynamic_dispatch_extract_param`].
248///
249/// # Examples
250///
251/// ```rust,ignore
252/// let _result = pharos_native::runtime::generated::dynamic_dispatch_extract_param(todo!(), "/", todo!());
253/// ```
254///
255/// # Related
256///
257/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
258pub fn dynamic_dispatch_extract_param(
259 route_pattern: &str,
260 request_path: &str,
261 param_name: &str,
262) -> Option<String> {
263 if route_pattern.is_empty() || request_path.is_empty() || param_name.is_empty() {
264 return None;
265 }
266 let request_path = request_path.split('?').next().unwrap_or_default();
267 let pattern_segments: Vec<&str> = route_pattern.trim_matches('/').split('/').collect();
268 let request_segments: Vec<&str> = request_path.trim_matches('/').split('/').collect();
269 if pattern_segments.len() != request_segments.len() {
270 return None;
271 }
272 for (pattern, actual) in pattern_segments.iter().zip(request_segments.iter()) {
273 if let Some(name) = pattern.strip_prefix(':') {
274 if name == param_name {
275 return Some((*actual).to_owned());
276 }
277 continue;
278 }
279 if let Some(name) = pattern
280 .strip_prefix('{')
281 .and_then(|value| value.strip_suffix('}'))
282 {
283 if name == param_name {
284 return Some((*actual).to_owned());
285 }
286 continue;
287 }
288 if pattern != actual {
289 return None;
290 }
291 }
292 None
293}
294
295/// Performs dynamic find business by slug.
296///
297/// # Parameters
298///
299/// - `root`: Input consumed by [`crate::runtime::generated::dynamic_find_business_by_slug`].
300/// - `business_slug`: Input consumed by [`crate::runtime::generated::dynamic_find_business_by_slug`].
301///
302/// # Returns
303///
304/// Returns [`Some`] when [`crate::runtime::generated::dynamic_find_business_by_slug`] can produce a value and [`None`] otherwise.
305///
306/// # Performance
307///
308/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::dynamic_find_business_by_slug`].
309///
310/// # Examples
311///
312/// ```rust,ignore
313/// let _result = pharos_native::runtime::generated::dynamic_find_business_by_slug(std::path::Path::new("."), "demo");
314/// ```
315///
316/// # Related
317///
318/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
319pub fn dynamic_find_business_by_slug<'a>(
320 root: &'a Value,
321 business_slug: &str,
322) -> Option<&'a Value> {
323 pharos_runtime_business::dynamic_find_business_by_slug(root, business_slug)
324}
325
326/// Builds the runtime generated surface operation path.
327///
328/// # Parameters
329///
330/// - `surface_root`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_operation_path`].
331/// - `business_slug`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_operation_path`].
332/// - `operation_name`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_operation_path`].
333/// - `identity_value`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_operation_path`].
334///
335/// # Returns
336///
337/// Returns [`Some`] when [`crate::runtime::generated::runtime_generated_surface_operation_path`] can produce a value and [`None`] otherwise.
338///
339/// # Examples
340///
341/// ```rust,ignore
342/// let _result = pharos_native::runtime::generated::runtime_generated_surface_operation_path(todo!(), "demo", todo!(), todo!());
343/// ```
344///
345/// # Related
346///
347/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
348pub fn runtime_generated_surface_operation_path(
349 surface_root: &Value,
350 business_slug: Option<&str>,
351 operation_name: &str,
352 identity_value: Option<&str>,
353) -> Option<String> {
354 if operation_name.is_empty() || !surface_root.is_object() {
355 return None;
356 }
357 let template_path = surface_root
358 .get("operations")
359 .and_then(|value| value.get(operation_name))
360 .and_then(|value| value.get("path"))
361 .and_then(Value::as_str)
362 .filter(|value| !value.is_empty())?;
363 let context_param = surface_root
364 .get("context_binding")
365 .and_then(|value| value.get("param"))
366 .and_then(Value::as_str)
367 .unwrap_or("business_slug");
368 let identity_param = surface_root
369 .get("identity_param")
370 .and_then(Value::as_str)
371 .unwrap_or("id");
372 Some(
373 template_path
374 .replace(
375 &format!("{{{context_param}}}"),
376 business_slug.unwrap_or_default(),
377 )
378 .replace(
379 &format!("{{{identity_param}}}"),
380 identity_value.unwrap_or_default(),
381 ),
382 )
383}
384
385/// Renders template text.
386fn render_template_text(template_text: &str, params: &Map<String, Value>) -> Option<String> {
387 let mut out = String::with_capacity(template_text.len() + 64);
388 let mut cursor = 0usize;
389 while let Some(relative_open) = template_text[cursor..].find('{') {
390 let open = cursor + relative_open;
391 out.push_str(&template_text[cursor..open]);
392 let key_start = open + 1;
393 let relative_close = template_text[key_start..].find('}')?;
394 let close = key_start + relative_close;
395 let mut key = &template_text[key_start..close];
396 let raw_html = if let Some(stripped) = key.strip_prefix("html:") {
397 key = stripped;
398 true
399 } else if let Some(stripped) = key.strip_prefix("path:") {
400 key = stripped;
401 false
402 } else if let Some(stripped) = key.strip_prefix("url:") {
403 key = stripped;
404 false
405 } else {
406 false
407 };
408 let value = params
409 .get(key)
410 .map_or_else(String::new, |value| match value {
411 Value::Null => String::new(),
412 Value::String(text) => text.clone(),
413 Value::Bool(flag) => flag.to_string(),
414 Value::Number(number) => number.to_string(),
415 _ => serde_json::to_string(value).unwrap_or_default(),
416 });
417 if raw_html {
418 out.push_str(&value);
419 } else {
420 out.push_str(
421 &value
422 .replace('&', "&")
423 .replace('<', "<")
424 .replace('>', ">")
425 .replace('"', """),
426 );
427 }
428 cursor = close + 1;
429 }
430 out.push_str(&template_text[cursor..]);
431 Some(out)
432}
433
434/// Renders generated admin fragment from value runtime.
435///
436/// # Parameters
437///
438/// - `app_root`: Input consumed by [`crate::runtime::generated::render_generated_admin_fragment_from_value_runtime`].
439/// - `relative_path`: Input consumed by [`crate::runtime::generated::render_generated_admin_fragment_from_value_runtime`].
440/// - `params`: Input consumed by [`crate::runtime::generated::render_generated_admin_fragment_from_value_runtime`].
441///
442/// # Returns
443///
444/// Returns [`Some`] when [`crate::runtime::generated::render_generated_admin_fragment_from_value_runtime`] can produce a value and [`None`] otherwise.
445///
446/// # Performance
447///
448/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::render_generated_admin_fragment_from_value_runtime`].
449///
450/// # Examples
451///
452/// ```rust,ignore
453/// let _result = pharos_native::runtime::generated::render_generated_admin_fragment_from_value_runtime(std::path::Path::new("apps/dev_docs"), "page", todo!());
454/// ```
455///
456/// # Related
457///
458/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
459pub fn render_generated_admin_fragment_from_value_runtime(
460 app_root: &Path,
461 relative_path: &str,
462 params: &Map<String, Value>,
463) -> Option<String> {
464 if relative_path.is_empty() || app_root.as_os_str().is_empty() {
465 return None;
466 }
467 let template_text =
468 super::cache::template_source_lookup(app_root, relative_path).or_else(|| {
469 let text = fs::read_to_string(app_root.join(relative_path)).ok()?;
470 super::cache::template_source_store(app_root, relative_path, &text);
471 Some(text)
472 })?;
473 render_template_text(&template_text, params)
474}
475
476/// Builds the runtime generated surface append record actions HTML.
477///
478/// # Parameters
479///
480/// - `buffer`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_record_actions_html`].
481/// - `app_root`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_record_actions_html`].
482/// - `edit_path`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_record_actions_html`].
483/// - `delete_path`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_record_actions_html`].
484/// - `delete_button_label`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_record_actions_html`].
485///
486/// # Returns
487///
488/// Returns `true` when the condition checked by [`crate::runtime::generated::runtime_generated_surface_append_record_actions_html`] holds and `false` otherwise.
489///
490/// # Examples
491///
492/// ```rust,ignore
493/// let _result = pharos_native::runtime::generated::runtime_generated_surface_append_record_actions_html(&mut [], std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
494/// ```
495///
496/// # Related
497///
498/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
499pub fn runtime_generated_surface_append_record_actions_html(
500 buffer: &mut String,
501 app_root: &Path,
502 edit_path: Option<&str>,
503 delete_path: Option<&str>,
504 delete_button_label: Option<&str>,
505) -> bool {
506 if app_root.as_os_str().is_empty() {
507 return false;
508 }
509 let mut edit_link_html = String::new();
510 let mut delete_link_html = String::new();
511
512 if let Some(edit_path) = edit_path.filter(|value| !value.is_empty()) {
513 let params = Map::from_iter([
514 (
515 "button_class".to_owned(),
516 Value::String("btn-secondary".to_owned()),
517 ),
518 (
519 "action_path".to_owned(),
520 Value::String(edit_path.to_owned()),
521 ),
522 ("button_label".to_owned(), Value::String("Edit".to_owned())),
523 ]);
524 let Some(rendered) = render_generated_admin_fragment_from_value_runtime(
525 app_root,
526 "templates/shared/fragments/action_link.html",
527 ¶ms,
528 ) else {
529 return false;
530 };
531 edit_link_html = rendered;
532 }
533
534 if let Some(delete_path) = delete_path.filter(|value| !value.is_empty()) {
535 let params = Map::from_iter([
536 (
537 "button_class".to_owned(),
538 Value::String("btn-danger".to_owned()),
539 ),
540 (
541 "action_path".to_owned(),
542 Value::String(delete_path.to_owned()),
543 ),
544 (
545 "button_label".to_owned(),
546 Value::String(
547 delete_button_label
548 .filter(|value| !value.is_empty())
549 .unwrap_or("Delete")
550 .to_owned(),
551 ),
552 ),
553 ]);
554 let Some(rendered) = render_generated_admin_fragment_from_value_runtime(
555 app_root,
556 "templates/shared/fragments/action_link.html",
557 ¶ms,
558 ) else {
559 return false;
560 };
561 delete_link_html = rendered;
562 }
563
564 let params = Map::from_iter([
565 (
566 "primary_action_html".to_owned(),
567 Value::String(edit_link_html),
568 ),
569 (
570 "secondary_action_html".to_owned(),
571 Value::String(delete_link_html),
572 ),
573 ]);
574 let Some(row_html) = render_generated_admin_fragment_from_value_runtime(
575 app_root,
576 "templates/shared/fragments/action_row.html",
577 ¶ms,
578 ) else {
579 return false;
580 };
581 buffer.push_str(&row_html);
582 true
583}
584
585/// Builds the runtime generated surface append table header cells HTML.
586///
587/// # Parameters
588///
589/// - `buffer`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_table_header_cells_html`].
590/// - `columns`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_table_header_cells_html`].
591/// - `app_root`: Input consumed by [`crate::runtime::generated::runtime_generated_surface_append_table_header_cells_html`].
592///
593/// # Returns
594///
595/// Returns `true` when the condition checked by [`crate::runtime::generated::runtime_generated_surface_append_table_header_cells_html`] holds and `false` otherwise.
596///
597/// # Performance
598///
599/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::runtime_generated_surface_append_table_header_cells_html`].
600///
601/// # Examples
602///
603/// ```rust,ignore
604/// let _result = pharos_native::runtime::generated::runtime_generated_surface_append_table_header_cells_html(&mut [], todo!(), std::path::Path::new("apps/dev_docs"));
605/// ```
606///
607/// # Related
608///
609/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
610pub fn runtime_generated_surface_append_table_header_cells_html(
611 buffer: &mut String,
612 columns: &Value,
613 app_root: &Path,
614) -> bool {
615 if app_root.as_os_str().is_empty() {
616 return false;
617 }
618 let Some(columns) = columns.as_array() else {
619 return true;
620 };
621 for column in columns {
622 let mut column_label = column
623 .get("label")
624 .and_then(Value::as_str)
625 .map(ToOwned::to_owned)
626 .unwrap_or_default();
627 if column_label.is_empty() {
628 column_label = generated_surface_label(column.get("field").and_then(Value::as_str))
629 .unwrap_or_default();
630 }
631 if column_label.is_empty() {
632 return false;
633 }
634 let params = Map::from_iter([("column_label".to_owned(), Value::String(column_label))]);
635 let Some(html) = render_generated_admin_fragment_from_value_runtime(
636 app_root,
637 "templates/generated_admin/fragments/table_header_cell.html",
638 ¶ms,
639 ) else {
640 return false;
641 };
642 buffer.push_str(&html);
643 }
644 true
645}
646
647/// Performs generated surface admin create load.
648///
649/// # Parameters
650///
651/// - `app`: Input consumed by [`crate::runtime::generated::generated_surface_admin_create_load`].
652/// - `surface_kind`: Input consumed by [`crate::runtime::generated::generated_surface_admin_create_load`].
653/// - `request_path`: Input consumed by [`crate::runtime::generated::generated_surface_admin_create_load`].
654/// - `route_pattern`: Input consumed by [`crate::runtime::generated::generated_surface_admin_create_load`].
655///
656/// # Returns
657///
658/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_admin_create_load`] can produce a value and [`None`] otherwise.
659///
660/// # Performance
661///
662/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::generated_surface_admin_create_load`].
663///
664/// # Examples
665///
666/// ```rust,ignore
667/// let _result = pharos_native::runtime::generated::generated_surface_admin_create_load(todo!(), todo!(), "/", todo!());
668/// ```
669///
670/// # Related
671///
672/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
673pub fn generated_surface_admin_create_load(
674 app: &AppRuntime,
675 surface_kind: &str,
676 request_path: &str,
677 route_pattern: &str,
678) -> Option<GeneratedSurfaceApiMatchRuntime> {
679 if surface_kind.is_empty() || request_path.is_empty() || route_pattern.is_empty() {
680 return None;
681 }
682 let app_root = app.app_root.as_deref().or(app.artifact_dir.as_deref())?;
683 if super::database::relational_runtime_authority_enabled(Path::new(app_root)) {
684 return None;
685 }
686 let generated_fixture_path = generated_surface_fixture_path(app)?;
687 let form_fixture_path = fixture_path(app, "data/contracts/forms.json");
688 let fixture_root = read_json_file(Path::new(&generated_fixture_path))?;
689 let state_root = read_json_file(Path::new(app.state_path.as_deref()?))?;
690 let form_root = form_fixture_path
691 .as_deref()
692 .and_then(|path| read_json_file(Path::new(path)));
693 let surface_root = runtime_generated_admin_surface_find(&fixture_root, surface_kind)?.clone();
694 let context_param = surface_root
695 .get("context_binding")
696 .and_then(|value| value.get("param"))
697 .and_then(Value::as_str)?;
698 let match_field = surface_root
699 .get("context_binding")
700 .and_then(|value| value.get("match_field"))
701 .and_then(Value::as_str)?;
702 let business_slug = dynamic_dispatch_extract_param(route_pattern, request_path, context_param)?;
703 let _expected_create_path = runtime_generated_surface_operation_path(
704 &surface_root,
705 Some(&business_slug),
706 "create",
707 None,
708 )?;
709 if match_field.is_empty() {
710 return None;
711 }
712 let business = dynamic_find_business_by_slug(&state_root, &business_slug)?;
713 let context_root = Value::Object(Map::from_iter([(
714 context_param.to_owned(),
715 Value::String(business_slug.clone()),
716 )]));
717 let context_id = json_long(business, "id", 0);
718 Some(GeneratedSurfaceApiMatchRuntime {
719 fixture_root,
720 state_root: state_root.clone(),
721 form_root,
722 surface_root: surface_root.clone(),
723 context_root,
724 surface_id: surface_root
725 .get("surface_id")
726 .and_then(Value::as_str)
727 .map(ToOwned::to_owned),
728 operation_name: Some("create".to_owned()),
729 business_slug: Some(business_slug),
730 fixture_path: Some(generated_fixture_path),
731 form_fixture_path,
732 context_id,
733 tenant_id: tenant_id_for_business_base(&state_root, context_id),
734 })
735}
736
737/// Performs generated surface validation report empty.
738///
739/// # Parameters
740///
741/// - `form_id`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_empty`].
742/// - `action_id`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_empty`].
743/// - `actor_scope`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_empty`].
744/// - `tenant_scope`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_empty`].
745///
746/// # Returns
747///
748/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_validation_report_empty`] can produce a value and [`None`] otherwise.
749///
750/// # Examples
751///
752/// ```rust,ignore
753/// let _result = pharos_native::runtime::generated::generated_surface_validation_report_empty("form", todo!(), todo!(), todo!());
754/// ```
755///
756/// # Related
757///
758/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
759pub fn generated_surface_validation_report_empty(
760 form_id: Option<&str>,
761 action_id: Option<&str>,
762 actor_scope: Option<&str>,
763 tenant_scope: Option<&str>,
764) -> Option<String> {
765 serde_json::to_string(&serde_json::json!({
766 "schema": "pharos.validation.report.v1",
767 "report_id": "pharos.validation.report.v1",
768 "form_id": form_id.unwrap_or_default(),
769 "action_id": action_id.unwrap_or_default(),
770 "field_errors": [],
771 "form_errors": [],
772 "actor_scope": actor_scope.unwrap_or("anonymous"),
773 "tenant_scope": tenant_scope.unwrap_or("global"),
774 }))
775 .ok()
776}
777
778/// Performs generated surface validation report add field error.
779///
780/// # Parameters
781///
782/// - `report_json`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_add_field_error`].
783/// - `field_id`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_add_field_error`].
784/// - `rule_id`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_add_field_error`].
785/// - `code`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_add_field_error`].
786/// - `message`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_add_field_error`].
787///
788/// # Returns
789///
790/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_validation_report_add_field_error`] can produce a value and [`None`] otherwise.
791///
792/// # Performance
793///
794/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::generated_surface_validation_report_add_field_error`].
795///
796/// # Examples
797///
798/// ```rust,ignore
799/// let _result = pharos_native::runtime::generated::generated_surface_validation_report_add_field_error("{}", "field", todo!(), todo!(), "example");
800/// ```
801///
802/// # Related
803///
804/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
805pub fn generated_surface_validation_report_add_field_error(
806 report_json: &str,
807 field_id: Option<&str>,
808 rule_id: Option<&str>,
809 code: Option<&str>,
810 message: Option<&str>,
811) -> Option<String> {
812 let mut root = serde_json::from_str::<Value>(if report_json.is_empty() {
813 "{}"
814 } else {
815 report_json
816 })
817 .ok()?;
818 let root_obj = root.as_object_mut()?;
819 let field_errors = root_obj
820 .entry("field_errors".to_owned())
821 .or_insert_with(|| Value::Array(Vec::new()))
822 .as_array_mut()?;
823 field_errors.push(serde_json::json!({
824 "field_id": field_id.unwrap_or_default(),
825 "rule_id": rule_id.unwrap_or("validation_rule"),
826 "code": code.unwrap_or("invalid"),
827 "message": message.unwrap_or("Invalid field value."),
828 }));
829 serde_json::to_string(&root).ok()
830}
831
832/// Performs generated surface submitted JSON.
833///
834/// # Parameters
835///
836/// - `surface_root`: Input consumed by [`crate::runtime::generated::generated_surface_submitted_json`].
837/// - `form_root`: Input consumed by [`crate::runtime::generated::generated_surface_submitted_json`].
838/// - `payload_root`: Input consumed by [`crate::runtime::generated::generated_surface_submitted_json`].
839///
840/// # Returns
841///
842/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_submitted_json`] can produce a value and [`None`] otherwise.
843///
844/// # Performance
845///
846/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::generated_surface_submitted_json`].
847///
848/// # Examples
849///
850/// ```rust,ignore
851/// let _result = pharos_native::runtime::generated::generated_surface_submitted_json(todo!(), todo!(), todo!());
852/// ```
853///
854/// # Related
855///
856/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
857pub fn generated_surface_submitted_json(
858 surface_root: &Value,
859 form_root: &Value,
860 payload_root: &Value,
861) -> Option<String> {
862 let form_id = surface_root.get("form_id").and_then(Value::as_str)?;
863 let form = form_root
864 .get("forms")
865 .and_then(Value::as_array)?
866 .iter()
867 .find(|item| item.get("form_id").and_then(Value::as_str) == Some(form_id))?;
868 let mut out = Map::new();
869 if let Some(fields) = form.get("fields").and_then(Value::as_array) {
870 for item in fields {
871 let Some(field_id) = item.get("field_id").and_then(Value::as_str) else {
872 continue;
873 };
874 if let Some(value) = payload_root.get(field_id) {
875 out.insert(field_id.to_owned(), value.clone());
876 }
877 }
878 }
879 if let Some(relation_inputs) = surface_root
880 .get("relation_inputs")
881 .and_then(Value::as_array)
882 {
883 for item in relation_inputs {
884 let Some(field_name) = item.get("field").and_then(Value::as_str) else {
885 continue;
886 };
887 if let Some(value) = payload_root.get(field_name) {
888 out.insert(field_name.to_owned(), value.clone());
889 }
890 }
891 }
892 serde_json::to_string(&Value::Object(out)).ok()
893}
894
895/// Performs generated surface coerce candidate JSON.
896///
897/// # Parameters
898///
899/// - `surface_root`: Input consumed by [`crate::runtime::generated::generated_surface_coerce_candidate_json`].
900/// - `candidate_json`: Input consumed by [`crate::runtime::generated::generated_surface_coerce_candidate_json`].
901///
902/// # Returns
903///
904/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_coerce_candidate_json`] can produce a value and [`None`] otherwise.
905///
906/// # Performance
907///
908/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::generated_surface_coerce_candidate_json`].
909///
910/// # Examples
911///
912/// ```rust,ignore
913/// let _result = pharos_native::runtime::generated::generated_surface_coerce_candidate_json(todo!(), todo!());
914/// ```
915///
916/// # Related
917///
918/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
919pub fn generated_surface_coerce_candidate_json(
920 surface_root: &Value,
921 candidate_json: &str,
922) -> Option<String> {
923 let candidate_root = serde_json::from_str::<Value>(if candidate_json.is_empty() {
924 "{}"
925 } else {
926 candidate_json
927 })
928 .ok()?;
929 let mut out = candidate_root.as_object()?.clone();
930 let Some(field_types) = surface_root.get("field_types").and_then(Value::as_object) else {
931 return serde_json::to_string(&Value::Object(out)).ok();
932 };
933 for (field_name, type_value) in field_types {
934 let Some(candidate_value) = out.get(field_name).cloned() else {
935 continue;
936 };
937 let type_text = value_to_string(type_value);
938 if type_text == "integer" {
939 let text = value_to_string(&candidate_value);
940 if digits_only_text(&text) {
941 out.insert(
942 field_name.clone(),
943 Value::Number(text.parse::<i64>().ok()?.into()),
944 );
945 }
946 } else if type_text == "boolean" {
947 if !candidate_value.is_boolean() {
948 out.insert(
949 field_name.clone(),
950 Value::Bool(truthy_text(&value_to_string(&candidate_value))),
951 );
952 }
953 } else if type_text == "array_integer" && candidate_value.is_array() {
954 let mut new_array = Vec::new();
955 for entry in candidate_value.as_array()? {
956 let text = value_to_string(entry);
957 if digits_only_text(&text) {
958 new_array.push(Value::Number(text.parse::<i64>().ok()?.into()));
959 }
960 }
961 out.insert(field_name.clone(), Value::Array(new_array));
962 }
963 }
964 serde_json::to_string(&Value::Object(out)).ok()
965}
966
967/// Performs next array ID runtime local.
968///
969/// # Parameters
970///
971/// - `rows`: Input consumed by [`crate::runtime::generated::next_array_id_runtime_local`].
972///
973/// # Returns
974///
975/// Returns the computed value produced by [`crate::runtime::generated::next_array_id_runtime_local`].
976///
977/// # Performance
978///
979/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::next_array_id_runtime_local`].
980///
981/// # Examples
982///
983/// ```rust,ignore
984/// let _result = pharos_native::runtime::generated::next_array_id_runtime_local(todo!());
985/// ```
986///
987/// # Related
988///
989/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
990pub fn next_array_id_runtime_local(rows: Option<&Value>) -> i64 {
991 let mut next_id = 1i64;
992 if let Some(rows) = rows.and_then(Value::as_array) {
993 for row in rows {
994 let existing_id = json_long(row, "id", 0);
995 if existing_id >= next_id {
996 next_id = existing_id + 1;
997 }
998 }
999 }
1000 next_id
1001}
1002
1003/// Performs generated surface apply defaults to mut object.
1004///
1005/// # Parameters
1006///
1007/// - `target_obj`: Input consumed by [`crate::runtime::generated::generated_surface_apply_defaults_to_mut_object`].
1008/// - `defaults_root`: Input consumed by [`crate::runtime::generated::generated_surface_apply_defaults_to_mut_object`].
1009///
1010/// # Returns
1011///
1012/// Returns `true` when the condition checked by [`crate::runtime::generated::generated_surface_apply_defaults_to_mut_object`] holds and `false` otherwise.
1013///
1014/// # Performance
1015///
1016/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::generated_surface_apply_defaults_to_mut_object`].
1017///
1018/// # Examples
1019///
1020/// ```rust,ignore
1021/// let _result = pharos_native::runtime::generated::generated_surface_apply_defaults_to_mut_object(todo!(), todo!());
1022/// ```
1023///
1024/// # Related
1025///
1026/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1027pub fn generated_surface_apply_defaults_to_mut_object(
1028 target_obj: &mut Map<String, Value>,
1029 defaults_root: Option<&Value>,
1030) -> bool {
1031 let Some(defaults_root) = defaults_root.and_then(Value::as_object) else {
1032 return true;
1033 };
1034 for (field_name, value) in defaults_root {
1035 if !target_obj.contains_key(field_name) {
1036 target_obj.insert(field_name.clone(), value.clone());
1037 }
1038 }
1039 true
1040}
1041
1042/// Performs generated surface validation report has errors.
1043///
1044/// # Parameters
1045///
1046/// - `report_json`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report_has_errors`].
1047///
1048/// # Returns
1049///
1050/// Returns `true` when the condition checked by [`crate::runtime::generated::generated_surface_validation_report_has_errors`] holds and `false` otherwise.
1051///
1052/// # Performance
1053///
1054/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::generated_surface_validation_report_has_errors`].
1055///
1056/// # Examples
1057///
1058/// ```rust,ignore
1059/// let _result = pharos_native::runtime::generated::generated_surface_validation_report_has_errors("{}");
1060/// ```
1061///
1062/// # Related
1063///
1064/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1065pub fn generated_surface_validation_report_has_errors(report_json: &str) -> bool {
1066 let Ok(root) = serde_json::from_str::<Value>(if report_json.is_empty() {
1067 "{}"
1068 } else {
1069 report_json
1070 }) else {
1071 return false;
1072 };
1073 let field_errors = root
1074 .get("field_errors")
1075 .and_then(Value::as_array)
1076 .map(Vec::len)
1077 .unwrap_or(0);
1078 let form_errors = root
1079 .get("form_errors")
1080 .and_then(Value::as_array)
1081 .map(Vec::len)
1082 .unwrap_or(0);
1083 field_errors + form_errors > 0
1084}
1085
1086#[derive(Debug, Clone, Eq, PartialEq)]
1087/// Public enum `GeneratedSurfaceCreateOutcome` used by this module.
1088///
1089/// # Variants
1090///
1091/// - `UnprocessableEntity`: Supported `GeneratedSurfaceCreateOutcome` variant.
1092/// - `Redirect`: Supported `GeneratedSurfaceCreateOutcome` variant.
1093///
1094/// # Examples
1095///
1096/// ```rust,ignore
1097/// let _ = pharos_native::runtime::generated::GeneratedSurfaceCreateOutcome::UnprocessableEntity;
1098/// ```
1099///
1100/// # Related
1101///
1102/// - [`crate::runtime::generated`] for constructors, helpers, and neighboring runtime types.
1103pub enum GeneratedSurfaceCreateOutcome {
1104 UnprocessableEntity { report_json: String },
1105 Redirect { location: String },
1106}
1107
1108/// Performs persist generated surface admin create runtime.
1109///
1110/// # Parameters
1111///
1112/// - `app`: Input consumed by [`crate::runtime::generated::persist_generated_surface_admin_create_runtime`].
1113/// - `match_record`: Input consumed by [`crate::runtime::generated::persist_generated_surface_admin_create_runtime`].
1114/// - `candidate_root`: Input consumed by [`crate::runtime::generated::persist_generated_surface_admin_create_runtime`].
1115///
1116/// # Returns
1117///
1118/// Returns [`Some`] when [`crate::runtime::generated::persist_generated_surface_admin_create_runtime`] can produce a value and [`None`] otherwise.
1119///
1120/// # Performance
1121///
1122/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::persist_generated_surface_admin_create_runtime`].
1123///
1124/// # Examples
1125///
1126/// ```rust,ignore
1127/// let _result = pharos_native::runtime::generated::persist_generated_surface_admin_create_runtime(todo!(), todo!(), todo!());
1128/// ```
1129///
1130/// # Related
1131///
1132/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1133pub fn persist_generated_surface_admin_create_runtime(
1134 app: &AppRuntime,
1135 match_record: &GeneratedSurfaceApiMatchRuntime,
1136 candidate_root: &Value,
1137) -> Option<String> {
1138 let app_root = app.app_root.as_deref().or(app.artifact_dir.as_deref())?;
1139 if super::database::relational_runtime_authority_enabled(Path::new(app_root)) {
1140 return None;
1141 }
1142 let collection_name = match_record
1143 .surface_root
1144 .get("entity_collection")
1145 .and_then(Value::as_str)
1146 .filter(|value| !value.is_empty())?;
1147 let identity_field = match_record
1148 .surface_root
1149 .get("identity_field")
1150 .and_then(Value::as_str)
1151 .filter(|value| !value.is_empty())
1152 .unwrap_or("id");
1153 let scope_field = match_record
1154 .surface_root
1155 .get("scope_field")
1156 .and_then(Value::as_str)
1157 .filter(|value| !value.is_empty());
1158 let auto_sequence_field = match_record
1159 .surface_root
1160 .get("auto_sequence_field")
1161 .and_then(Value::as_str)
1162 .filter(|value| !value.is_empty());
1163 let mut state_root = match_record.state_root.clone();
1164 let state_map = state_root.as_object_mut()?;
1165 let source_rows = state_map.get(collection_name).cloned();
1166 let section = state_map
1167 .entry(collection_name.to_owned())
1168 .or_insert_with(|| Value::Array(Vec::new()));
1169 let section_rows = section.as_array_mut()?;
1170 let mut item_mut = candidate_root.as_object()?.clone();
1171 generated_surface_apply_defaults_to_mut_object(
1172 &mut item_mut,
1173 match_record.surface_root.get("defaults"),
1174 );
1175 if let Some(scope_field) = scope_field {
1176 item_mut.insert(
1177 scope_field.to_owned(),
1178 Value::Number(match_record.context_id.into()),
1179 );
1180 }
1181 if let Some(auto_sequence_field) = auto_sequence_field
1182 && !item_mut.contains_key(auto_sequence_field)
1183 {
1184 let mut next_sequence = 1i64;
1185 if let Some(rows) = source_rows.as_ref().and_then(Value::as_array) {
1186 for row in rows {
1187 let value = json_long(row, auto_sequence_field, 0);
1188 let scope_value = json_long(row, scope_field.unwrap_or("business_id"), 0);
1189 if scope_value == match_record.context_id && value >= next_sequence {
1190 next_sequence = value + 1;
1191 }
1192 }
1193 }
1194 item_mut.insert(
1195 auto_sequence_field.to_owned(),
1196 Value::Number(next_sequence.into()),
1197 );
1198 }
1199 let record_id = next_array_id_runtime_local(source_rows.as_ref());
1200 item_mut.insert(identity_field.to_owned(), Value::Number(record_id.into()));
1201 section_rows.push(Value::Object(item_mut));
1202 let state_path = Path::new(app.state_path.as_deref()?);
1203 write_json_file(state_path, &state_root).then(|| {
1204 runtime_generated_surface_operation_path(
1205 &match_record.surface_root,
1206 match_record.business_slug.as_deref(),
1207 "manage",
1208 None,
1209 )
1210 .unwrap_or_else(|| "/".to_owned())
1211 })
1212}
1213
1214/// Handles generated surface admin create request.
1215///
1216/// # Parameters
1217///
1218/// - `app`: Input consumed by [`crate::runtime::generated::handle_generated_surface_admin_create_request`].
1219/// - `match_record`: Input consumed by [`crate::runtime::generated::handle_generated_surface_admin_create_request`].
1220/// - `candidate_root`: Input consumed by [`crate::runtime::generated::handle_generated_surface_admin_create_request`].
1221/// - `report_json`: Input consumed by [`crate::runtime::generated::handle_generated_surface_admin_create_request`].
1222///
1223/// # Returns
1224///
1225/// Returns [`Some`] when [`crate::runtime::generated::handle_generated_surface_admin_create_request`] can produce a value and [`None`] otherwise.
1226///
1227/// # Examples
1228///
1229/// ```rust,ignore
1230/// let _result = pharos_native::runtime::generated::handle_generated_surface_admin_create_request(todo!(), todo!(), todo!(), "{}");
1231/// ```
1232///
1233/// # Related
1234///
1235/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1236pub fn handle_generated_surface_admin_create_request(
1237 app: &AppRuntime,
1238 match_record: &GeneratedSurfaceApiMatchRuntime,
1239 candidate_root: &Value,
1240 report_json: &str,
1241) -> Option<GeneratedSurfaceCreateOutcome> {
1242 if generated_surface_validation_report_has_errors(report_json) {
1243 return Some(GeneratedSurfaceCreateOutcome::UnprocessableEntity {
1244 report_json: report_json.to_owned(),
1245 });
1246 }
1247 let redirect_path =
1248 persist_generated_surface_admin_create_runtime(app, match_record, candidate_root)?;
1249 let base_path = app.base_path.clone().unwrap_or_default();
1250 Some(GeneratedSurfaceCreateOutcome::Redirect {
1251 location: format!("{base_path}{redirect_path}"),
1252 })
1253}
1254
1255/// Writes JSON file.
1256///
1257/// # Parameters
1258///
1259/// - `path`: Input consumed by [`crate::runtime::generated::write_json_file`].
1260/// - `value`: Input consumed by [`crate::runtime::generated::write_json_file`].
1261///
1262/// # Returns
1263///
1264/// Returns `true` when the condition checked by [`crate::runtime::generated::write_json_file`] holds and `false` otherwise.
1265///
1266/// # Examples
1267///
1268/// ```rust,ignore
1269/// let _result = pharos_native::runtime::generated::write_json_file(std::path::Path::new("fixture.json"), "value");
1270/// ```
1271///
1272/// # Related
1273///
1274/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1275pub fn write_json_file(path: &Path, value: &Value) -> bool {
1276 if let Some(parent) = path.parent()
1277 && fs::create_dir_all(parent).is_err()
1278 {
1279 return false;
1280 }
1281 serde_json::to_string_pretty(value)
1282 .ok()
1283 .and_then(|text| super::fs::write_binary_file(path, text.as_bytes(), 0o640).ok())
1284 .is_some()
1285}
1286
1287/// Performs generated surface delete button label.
1288///
1289/// # Parameters
1290///
1291/// - `surface`: Input consumed by [`crate::runtime::generated::generated_surface_delete_button_label`].
1292///
1293/// # Returns
1294///
1295/// Returns the computed value produced by [`crate::runtime::generated::generated_surface_delete_button_label`].
1296///
1297/// # Examples
1298///
1299/// ```rust,ignore
1300/// let _result = pharos_native::runtime::generated::generated_surface_delete_button_label(todo!());
1301/// ```
1302///
1303/// # Related
1304///
1305/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1306pub fn generated_surface_delete_button_label(surface: &Value) -> String {
1307 surface
1308 .get("admin")
1309 .and_then(|value| value.get("delete_button_label"))
1310 .and_then(Value::as_str)
1311 .filter(|value| !value.is_empty())
1312 .unwrap_or("Delete")
1313 .to_owned()
1314}
1315
1316/// Renders one generated-surface operation path with context and identity values.
1317///
1318/// # Parameters
1319///
1320/// - `surface`: Input consumed by [`crate::runtime::generated::generated_surface_operation_path`].
1321/// - `context_value`: Input consumed by [`crate::runtime::generated::generated_surface_operation_path`].
1322/// - `operation_name`: Input consumed by [`crate::runtime::generated::generated_surface_operation_path`].
1323/// - `identity_value`: Input consumed by [`crate::runtime::generated::generated_surface_operation_path`].
1324///
1325/// # Returns
1326///
1327/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_operation_path`] can produce a value and [`None`] otherwise.
1328///
1329/// # Examples
1330///
1331/// ```rust,ignore
1332/// let _result = pharos_native::runtime::generated::generated_surface_operation_path(todo!(), todo!(), todo!(), todo!());
1333/// ```
1334///
1335/// # Related
1336///
1337/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1338pub fn generated_surface_operation_path(
1339 surface: &Value,
1340 context_value: Option<&str>,
1341 operation_name: &str,
1342 identity_value: Option<&str>,
1343) -> Option<String> {
1344 if operation_name.is_empty() {
1345 return None;
1346 }
1347 let path = surface
1348 .get("operations")?
1349 .get(operation_name)?
1350 .get("path")?
1351 .as_str()?;
1352 let context_param = surface
1353 .get("context_binding")
1354 .and_then(|value| value.get("param"))
1355 .and_then(Value::as_str)
1356 .unwrap_or("business_slug");
1357 let identity_param = surface
1358 .get("identity_param")
1359 .and_then(Value::as_str)
1360 .unwrap_or("id");
1361 Some(
1362 path.replace(
1363 &format!("{{{context_param}}}"),
1364 context_value.unwrap_or_default(),
1365 )
1366 .replace(
1367 &format!("{{{identity_param}}}"),
1368 identity_value.unwrap_or_default(),
1369 ),
1370 )
1371}
1372
1373/// Copies fragment tree for runtime.
1374///
1375/// # Parameters
1376///
1377/// - `source_root`: Input consumed by [`crate::runtime::generated::copy_fragment_tree_for_runtime`].
1378/// - `app_root`: Input consumed by [`crate::runtime::generated::copy_fragment_tree_for_runtime`].
1379///
1380/// # Returns
1381///
1382/// Returns `true` when the condition checked by [`crate::runtime::generated::copy_fragment_tree_for_runtime`] holds and `false` otherwise.
1383///
1384/// # Performance
1385///
1386/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::copy_fragment_tree_for_runtime`].
1387///
1388/// # Examples
1389///
1390/// ```rust,ignore
1391/// let _result = pharos_native::runtime::generated::copy_fragment_tree_for_runtime(todo!(), std::path::Path::new("apps/dev_docs"));
1392/// ```
1393///
1394/// # Related
1395///
1396/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1397pub fn copy_fragment_tree_for_runtime(source_root: &Path, app_root: &Path) -> bool {
1398 let mappings = [
1399 (
1400 source_root.join("templates/shared/fragments/action_link.html"),
1401 app_root.join("templates/shared/fragments/action_link.html"),
1402 ),
1403 (
1404 source_root.join("templates/shared/fragments/action_row.html"),
1405 app_root.join("templates/shared/fragments/action_row.html"),
1406 ),
1407 (
1408 source_root.join("templates/generated_admin/fragments/table_header_cell.html"),
1409 app_root.join("templates/generated_admin/fragments/table_header_cell.html"),
1410 ),
1411 ];
1412 for (source, dest) in mappings {
1413 let Ok(text) = fs::read_to_string(&source) else {
1414 return false;
1415 };
1416 if let Some(parent) = dest.parent()
1417 && fs::create_dir_all(parent).is_err()
1418 {
1419 return false;
1420 }
1421 if super::fs::write_file_within(app_root, &dest, text.as_bytes(), 0o644).is_err() {
1422 return false;
1423 }
1424 }
1425 true
1426}
1427
1428/// Matches a generated operation against a bounded method and route pattern.
1429///
1430/// # Parameters
1431///
1432/// - `operation_root`: Input consumed by [`crate::runtime::generated::generated_surface_operation_matches`].
1433/// - `request_method`: Input consumed by [`crate::runtime::generated::generated_surface_operation_matches`].
1434/// - `request_path`: Input consumed by [`crate::runtime::generated::generated_surface_operation_matches`].
1435///
1436/// # Returns
1437///
1438/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_operation_matches`] can produce a value and [`None`] otherwise.
1439///
1440/// # Examples
1441///
1442/// ```rust,ignore
1443/// let _result = pharos_native::runtime::generated::generated_surface_operation_matches(todo!(), todo!(), "/");
1444/// ```
1445///
1446/// # Related
1447///
1448/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1449pub fn generated_surface_operation_matches(
1450 operation_root: &Value,
1451 request_method: &str,
1452 request_path: &str,
1453) -> Option<String> {
1454 let method = operation_root
1455 .get("method")
1456 .and_then(Value::as_str)
1457 .unwrap_or_default();
1458 let pattern = operation_root
1459 .get("path")
1460 .or_else(|| operation_root.get("route_pattern"))
1461 .and_then(Value::as_str)?;
1462 (method.eq_ignore_ascii_case(request_method)
1463 && super::dynamic::pattern_matches(pattern, request_path))
1464 .then(|| pattern.to_owned())
1465}
1466
1467/// Builds a validation report from the declared surface and candidate payload.
1468///
1469/// # Parameters
1470///
1471/// - `surface_root`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1472/// - `form_root`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1473/// - `state_root`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1474/// - `context_id`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1475/// - `tenant_id`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1476/// - `candidate_json`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1477/// - `current_id_text`: Input consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1478///
1479/// # Returns
1480///
1481/// Returns [`Some`] when [`crate::runtime::generated::generated_surface_validation_report`] can produce a value and [`None`] otherwise.
1482///
1483/// # Performance
1484///
1485/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::generated::generated_surface_validation_report`].
1486///
1487/// # Examples
1488///
1489/// ```rust,ignore
1490/// let _result = pharos_native::runtime::generated::generated_surface_validation_report(todo!(), todo!(), todo!(), todo!(), todo!(), todo!(), todo!());
1491/// ```
1492///
1493/// # Related
1494///
1495/// - [`crate::runtime::generated`] for adjacent helpers that share the same runtime surface.
1496pub fn generated_surface_validation_report(
1497 surface_root: &Value,
1498 form_root: &Value,
1499 state_root: &Value,
1500 context_id: i64,
1501 tenant_id: i64,
1502 candidate_json: &str,
1503 current_id_text: &str,
1504) -> Option<String> {
1505 let candidate = serde_json::from_str::<Value>(if candidate_json.is_empty() {
1506 "{}"
1507 } else {
1508 candidate_json
1509 })
1510 .ok()?;
1511 let errors = if candidate.is_object() {
1512 Vec::<Value>::new()
1513 } else {
1514 vec![serde_json::json!({"code":"invalid_object","message":"candidate must be an object"})]
1515 };
1516 serde_json::to_string(&serde_json::json!({"schema":"pharos.validation.report.v1","surface_id":surface_root.get("surface_id"),"form_id":form_root.get("form_id"),"context_id":context_id,"tenant_id":tenant_id,"current_id":current_id_text,"state_present":!state_root.is_null(),"field_errors":errors})).ok()
1517}