pharos_native/runtime/json_commands.rs
1#![allow(clippy::too_many_arguments)]
2
3use std::fs;
4
5use std::path::{Component, Path, PathBuf};
6
7use saphyr::LoadableYamlNode;
8use serde_json::{Map, Value};
9use sha2::{Digest, Sha256};
10
11use crate::config;
12
13pub use super::database::{
14 dynamic_relational_backend, dynamic_relational_runtime_audit_count,
15 dynamic_relational_runtime_export_json, dynamic_relational_runtime_import_state,
16 dynamic_runtime_prepare, dynamic_runtime_ready, load_cli_backend_config,
17};
18use super::generated;
19
20#[derive(Debug, Clone, Eq, PartialEq)]
21struct MultipartPart {
22 name: String,
23 text: Option<String>,
24 filename: Option<String>,
25 content: Vec<u8>,
26}
27
28#[derive(Debug, Clone, Eq, PartialEq)]
29pub struct MultipartFilePart {
30 pub filename: String,
31 pub content: Vec<u8>,
32}
33
34/// Reads JSON file.
35fn read_json_file(path: &Path) -> Option<Value> {
36 serde_json::from_str(&fs::read_to_string(path).ok()?).ok()
37}
38
39/// Performs JSON string.
40fn json_string<'a>(value: &'a Value, key: &str) -> Option<&'a str> {
41 value.get(key).and_then(Value::as_str)
42}
43
44/// Bundles manifest slice order lines.
45///
46/// # Parameters
47///
48/// - `path`: Input consumed by [`crate::runtime::json_commands::bundle_manifest_slice_order_lines`].
49///
50/// # Returns
51///
52/// Returns [`Some`] when [`crate::runtime::json_commands::bundle_manifest_slice_order_lines`] can produce a value and [`None`] otherwise.
53///
54/// # Performance
55///
56/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::bundle_manifest_slice_order_lines`].
57///
58/// # Examples
59///
60/// ```rust,ignore
61/// let _result = pharos_native::runtime::json_commands::bundle_manifest_slice_order_lines(std::path::Path::new("fixture.json"));
62/// ```
63///
64/// # Related
65///
66/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
67pub fn bundle_manifest_slice_order_lines(path: &Path) -> Option<Vec<String>> {
68 let root = read_json_file(path)?;
69 let mut lines = Vec::new();
70 if let Some(slices) = root.get("slices").and_then(Value::as_array) {
71 for item in slices {
72 if let Some(path) = json_string(item, "path") {
73 lines.push(path.to_owned());
74 }
75 }
76 }
77 if lines.is_empty()
78 && let Some(slice_order) = root.get("slice_order").and_then(Value::as_array)
79 {
80 for item in slice_order {
81 if let Some(text) = item.as_str() {
82 lines.push(text.to_owned());
83 }
84 }
85 }
86 (!lines.is_empty()).then_some(lines)
87}
88
89/// Bundles manifest layer for slice.
90///
91/// # Parameters
92///
93/// - `path`: Input consumed by [`crate::runtime::json_commands::bundle_manifest_layer_for_slice`].
94/// - `slice_name`: Input consumed by [`crate::runtime::json_commands::bundle_manifest_layer_for_slice`].
95///
96/// # Returns
97///
98/// Returns [`Some`] when [`crate::runtime::json_commands::bundle_manifest_layer_for_slice`] can produce a value and [`None`] otherwise.
99///
100/// # Performance
101///
102/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::bundle_manifest_layer_for_slice`].
103///
104/// # Examples
105///
106/// ```rust,ignore
107/// let _result = pharos_native::runtime::json_commands::bundle_manifest_layer_for_slice(std::path::Path::new("fixture.json"), "slice");
108/// ```
109///
110/// # Related
111///
112/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
113pub fn bundle_manifest_layer_for_slice(path: &Path, slice_name: &str) -> Option<Option<String>> {
114 if slice_name.is_empty() {
115 return None;
116 }
117 let root = read_json_file(path)?;
118 let slices = root.get("slices").and_then(Value::as_array)?;
119 for item in slices {
120 if json_string(item, "path") == Some(slice_name) {
121 return Some(
122 json_string(item, "materialize_from_runtime_layer").map(ToOwned::to_owned),
123 );
124 }
125 }
126 Some(None)
127}
128
129/// Performs request compact JSON.
130///
131/// # Parameters
132///
133/// - `text`: Input consumed by [`crate::runtime::json_commands::request_compact_json`].
134///
135/// # Returns
136///
137/// Returns [`Some`] when [`crate::runtime::json_commands::request_compact_json`] can produce a value and [`None`] otherwise.
138///
139/// # Performance
140///
141/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_compact_json`].
142///
143/// # Examples
144///
145/// ```rust,ignore
146/// let _result = pharos_native::runtime::json_commands::request_compact_json("{}");
147/// ```
148///
149/// # Related
150///
151/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
152pub fn request_compact_json(text: &str) -> Option<String> {
153 serde_json::from_str::<Value>(text)
154 .ok()
155 .and_then(|value| serde_json::to_string(&value).ok())
156}
157
158/// Performs request compact object.
159///
160/// # Parameters
161///
162/// - `text`: Input consumed by [`crate::runtime::json_commands::request_compact_object`].
163///
164/// # Returns
165///
166/// Returns the computed value produced by [`crate::runtime::json_commands::request_compact_object`].
167///
168/// # Performance
169///
170/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_compact_object`].
171///
172/// # Examples
173///
174/// ```rust,ignore
175/// let _result = pharos_native::runtime::json_commands::request_compact_object("{}");
176/// ```
177///
178/// # Related
179///
180/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
181pub fn request_compact_object(text: &str) -> String {
182 match serde_json::from_str::<Value>(text) {
183 Ok(value) if value.is_object() => {
184 serde_json::to_string(&value).unwrap_or_else(|_| "{}".to_owned())
185 }
186 _ => "{}".to_owned(),
187 }
188}
189
190/// Performs request compact object strict.
191///
192/// # Parameters
193///
194/// - `text`: Input consumed by [`crate::runtime::json_commands::request_compact_object_strict`].
195///
196/// # Returns
197///
198/// Returns [`Some`] when [`crate::runtime::json_commands::request_compact_object_strict`] can produce a value and [`None`] otherwise.
199///
200/// # Performance
201///
202/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_compact_object_strict`].
203///
204/// # Examples
205///
206/// ```rust,ignore
207/// let _result = pharos_native::runtime::json_commands::request_compact_object_strict("{}");
208/// ```
209///
210/// # Related
211///
212/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
213pub fn request_compact_object_strict(text: &str) -> Option<String> {
214 let value = serde_json::from_str::<Value>(text).ok()?;
215 value
216 .is_object()
217 .then(|| serde_json::to_string(&value).ok())
218 .flatten()
219}
220
221/// Performs request field.
222///
223/// # Parameters
224///
225/// - `text`: Input consumed by [`crate::runtime::json_commands::request_field`].
226/// - `field`: Input consumed by [`crate::runtime::json_commands::request_field`].
227///
228/// # Returns
229///
230/// Returns [`Some`] when [`crate::runtime::json_commands::request_field`] can produce a value and [`None`] otherwise.
231///
232/// # Performance
233///
234/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_field`].
235///
236/// # Examples
237///
238/// ```rust,ignore
239/// let _result = pharos_native::runtime::json_commands::request_field("{}", "field");
240/// ```
241///
242/// # Related
243///
244/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
245pub fn request_field(text: &str, field: &str) -> Option<String> {
246 if field.is_empty() {
247 return None;
248 }
249 serde_json::from_str::<Value>(text)
250 .ok()?
251 .as_object()?
252 .get(field)?
253 .as_str()
254 .map(ToOwned::to_owned)
255}
256
257/// Performs manifest fixture path raw.
258///
259/// # Parameters
260///
261/// - `path`: Input consumed by [`crate::runtime::json_commands::manifest_fixture_path_raw`].
262/// - `key`: Input consumed by [`crate::runtime::json_commands::manifest_fixture_path_raw`].
263///
264/// # Returns
265///
266/// Returns [`Some`] when [`crate::runtime::json_commands::manifest_fixture_path_raw`] can produce a value and [`None`] otherwise.
267///
268/// # Examples
269///
270/// ```rust,ignore
271/// let _result = pharos_native::runtime::json_commands::manifest_fixture_path_raw(std::path::Path::new("fixture.json"), "key");
272/// ```
273///
274/// # Related
275///
276/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
277pub fn manifest_fixture_path_raw(path: &Path, key: &str) -> Option<String> {
278 if key.is_empty() {
279 return None;
280 }
281 let root = read_json_file(path)?;
282 json_string(&root, key)
283 .or_else(|| {
284 root.get("fixtures")
285 .and_then(|value| json_string(value, key))
286 })
287 .or_else(|| {
288 root.get("database")
289 .and_then(|value| json_string(value, key))
290 })
291 .map(ToOwned::to_owned)
292}
293
294/// Performs manifest app root.
295///
296/// # Parameters
297///
298/// - `path`: Input consumed by [`crate::runtime::json_commands::manifest_app_root`].
299///
300/// # Returns
301///
302/// Returns [`Some`] when [`crate::runtime::json_commands::manifest_app_root`] can produce a value and [`None`] otherwise.
303///
304/// # Examples
305///
306/// ```rust,ignore
307/// let _result = pharos_native::runtime::json_commands::manifest_app_root(std::path::Path::new("fixture.json"));
308/// ```
309///
310/// # Related
311///
312/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
313pub fn manifest_app_root(path: &Path) -> Option<String> {
314 json_string(&read_json_file(path)?, "app_root").map(ToOwned::to_owned)
315}
316
317/// Performs URL decode component.
318fn url_decode_component(text: &str) -> Option<String> {
319 let bytes = text.as_bytes();
320 let mut out = Vec::with_capacity(text.len());
321 let mut index = 0usize;
322 while index < bytes.len() {
323 match bytes[index] {
324 b'+' => {
325 out.push(b' ');
326 index += 1;
327 }
328 b'%' if index + 2 < bytes.len() => {
329 let hex = std::str::from_utf8(&bytes[index + 1..index + 3]).ok()?;
330 let value = u8::from_str_radix(hex, 16).ok()?;
331 out.push(value);
332 index += 3;
333 }
334 b'%' => return None,
335 value => {
336 out.push(value);
337 index += 1;
338 }
339 }
340 }
341 String::from_utf8(out).ok()
342}
343
344/// Ensures percent-decoded bytes become UTF-8 once, preserving editor labels and user copy.
345#[cfg(test)]
346#[test]
347fn form_components_preserve_utf8() {
348 assert_eq!(
349 url_decode_component("action+%E2%80%94+caf%C3%A9"),
350 Some("action — café".into())
351 );
352 assert_eq!(url_decode_component("日本語"), Some("日本語".into()));
353 assert_eq!(url_decode_component("%FF"), None);
354 assert_eq!(url_decode_component("%E2%"), None);
355}
356
357/// Percent-encodes one bounded query value for safe reuse in generated links.
358pub(super) fn url_encode_query_component(value: &str) -> String {
359 const HEX: &[u8; 16] = b"0123456789ABCDEF";
360 let mut encoded = String::with_capacity(value.len());
361 for byte in value.bytes() {
362 if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') {
363 encoded.push(char::from(byte));
364 } else {
365 encoded.push('%');
366 encoded.push(char::from(HEX[usize::from(byte >> 4)]));
367 encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
368 }
369 }
370 encoded
371}
372
373/// Performs request query value.
374///
375/// # Parameters
376///
377/// - `query`: Input consumed by [`crate::runtime::json_commands::request_query_value`].
378/// - `key`: Input consumed by [`crate::runtime::json_commands::request_query_value`].
379///
380/// # Returns
381///
382/// Returns [`Some`] when [`crate::runtime::json_commands::request_query_value`] can produce a value and [`None`] otherwise.
383///
384/// # Performance
385///
386/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_query_value`].
387///
388/// # Examples
389///
390/// ```rust,ignore
391/// let _result = pharos_native::runtime::json_commands::request_query_value("page=1", "key");
392/// ```
393///
394/// # Related
395///
396/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
397pub fn request_query_value(query: &str, key: &str) -> Option<String> {
398 if key.is_empty() {
399 return None;
400 }
401 for pair in query.split('&') {
402 let (raw_key, raw_value) = pair.split_once('=').unwrap_or((pair, ""));
403 if url_decode_component(raw_key).as_deref() == Some(key) {
404 return url_decode_component(raw_value);
405 }
406 }
407 None
408}
409
410/// Performs request form value.
411///
412/// # Parameters
413///
414/// - `form_body`: Input consumed by [`crate::runtime::json_commands::request_form_value`].
415/// - `key`: Input consumed by [`crate::runtime::json_commands::request_form_value`].
416///
417/// # Returns
418///
419/// Returns [`Some`] when [`crate::runtime::json_commands::request_form_value`] can produce a value and [`None`] otherwise.
420///
421/// # Examples
422///
423/// ```rust,ignore
424/// let _result = pharos_native::runtime::json_commands::request_form_value("name=value", "key");
425/// ```
426///
427/// # Related
428///
429/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
430pub fn request_form_value(form_body: &str, key: &str) -> Option<String> {
431 request_query_value(form_body, key)
432}
433
434/// Performs multipart boundary.
435fn multipart_boundary(content_type: &str) -> Option<String> {
436 let boundary_start = content_type.find("boundary=")? + 9;
437 let suffix = &content_type[boundary_start..];
438 if let Some(stripped) = suffix.strip_prefix('"') {
439 let end = stripped.find('"')?;
440 return Some(stripped[..end].to_owned());
441 }
442 let end = suffix
443 .find(|ch: char| ch == ';' || ch.is_ascii_whitespace())
444 .unwrap_or(suffix.len());
445 (end > 0).then(|| suffix[..end].to_owned())
446}
447
448/// Finds bytes.
449fn find_bytes(haystack: &[u8], needle: &[u8], start: usize) -> Option<usize> {
450 if needle.is_empty() || haystack.len() < needle.len() || start >= haystack.len() {
451 return None;
452 }
453 haystack[start..]
454 .windows(needle.len())
455 .position(|window| window == needle)
456 .map(|offset| start + offset)
457}
458
459/// Performs multipart header param.
460fn multipart_header_param(headers_text: &str, key: &str) -> Option<String> {
461 let pattern = format!("{key}=\"");
462 let match_index = headers_text.find(&pattern)? + pattern.len();
463 let suffix = &headers_text[match_index..];
464 let end = suffix.find('"')?;
465 Some(suffix[..end].to_owned())
466}
467
468/// Performs multipart find part from body.
469fn multipart_parts_from_body(
470 body: &[u8],
471 content_type: &str,
472 field_name: &str,
473 max_parts: usize,
474 max_total_bytes: usize,
475) -> Vec<MultipartPart> {
476 if body.is_empty() || field_name.is_empty() || max_parts == 0 {
477 return Vec::new();
478 }
479
480 let Some(boundary) = multipart_boundary(content_type) else {
481 return Vec::new();
482 };
483 if boundary.is_empty() {
484 return Vec::new();
485 }
486
487 let delimiter = format!("--{boundary}").into_bytes();
488 let delimiter_lf = format!("\n--{boundary}").into_bytes();
489 let Some(mut cursor) = find_bytes(body, &delimiter, 0) else {
490 return Vec::new();
491 };
492 let mut parts = Vec::new();
493 let mut total_bytes = 0usize;
494 let mut scanned_parts = 0usize;
495
496 while cursor + delimiter.len() <= body.len() {
497 scanned_parts += 1;
498 if scanned_parts > 1024 {
499 return Vec::new();
500 }
501 let mut part_cursor = cursor + delimiter.len();
502 if part_cursor + 2 <= body.len()
503 && body[part_cursor] == b'-'
504 && body[part_cursor + 1] == b'-'
505 {
506 break;
507 }
508 if part_cursor + 2 <= body.len()
509 && body[part_cursor] == b'\r'
510 && body[part_cursor + 1] == b'\n'
511 {
512 part_cursor += 2;
513 } else if part_cursor < body.len() && body[part_cursor] == b'\n' {
514 part_cursor += 1;
515 }
516
517 // Header searches must not rescan the remaining file payload for each part.
518 let header_limit = body.len().min(part_cursor.saturating_add(16 * 1024));
519 let next_crlf = find_bytes(&body[..header_limit], b"\r\n\r\n", part_cursor);
520 let next_lf = find_bytes(&body[..header_limit], b"\n\n", part_cursor);
521 let (header_end, header_sep_len) = match (next_crlf, next_lf) {
522 (Some(crlf), Some(lf)) if crlf <= lf => (crlf, 4usize),
523 (Some(crlf), None) => (crlf, 4usize),
524 (None, Some(lf)) => (lf, 2usize),
525 _ => break,
526 };
527
528 let headers_text = String::from_utf8_lossy(&body[part_cursor..header_end]).into_owned();
529 let part_name = multipart_header_param(&headers_text, "name");
530 let filename = multipart_header_param(&headers_text, "filename");
531 let content_start = header_end + header_sep_len;
532
533 let next_marker = find_bytes(body, &delimiter_lf, content_start)
534 .map(|marker| {
535 if marker > content_start && body[marker - 1] == b'\r' {
536 marker - 1
537 } else {
538 marker
539 }
540 })
541 .unwrap_or(body.len());
542
543 if part_name.as_deref() == Some(field_name) || field_name == "*" {
544 let content = body[content_start..next_marker].to_vec();
545 total_bytes = total_bytes.saturating_add(content.len());
546 if parts.len() >= max_parts || total_bytes > max_total_bytes {
547 return Vec::new();
548 }
549 let text = String::from_utf8(content.clone()).ok();
550 parts.push(MultipartPart {
551 name: part_name.unwrap_or_default(),
552 text,
553 filename,
554 content,
555 });
556 }
557
558 if next_marker >= body.len() {
559 break;
560 }
561 cursor = if body[next_marker] == b'\r' {
562 next_marker + 2
563 } else {
564 next_marker + 1
565 };
566 let Some(next_cursor) = find_bytes(body, &delimiter, cursor) else {
567 break;
568 };
569 cursor = next_cursor;
570 }
571
572 parts
573}
574
575/// Parses bounded named parts for declared multipart actions using the shared parser.
576pub(super) fn request_multipart_named_parts(
577 content_type: &str,
578 body: &[u8],
579 max_parts: usize,
580 max_bytes: usize,
581) -> Vec<(String, Option<String>, Vec<u8>)> {
582 multipart_parts_from_body(body, content_type, "*", max_parts, max_bytes)
583 .into_iter()
584 .map(|part| (part.name, part.filename, part.content))
585 .collect()
586}
587
588fn multipart_find_part_from_body(
589 body: &[u8],
590 content_type: &str,
591 field_name: &str,
592) -> Option<MultipartPart> {
593 multipart_parts_from_body(body, content_type, field_name, 1, body.len())
594 .into_iter()
595 .next()
596}
597
598/// Extracts one text field from an in-memory multipart request body.
599///
600/// # Parameters
601///
602/// - `content_type`: Input consumed by [`crate::runtime::json_commands::request_multipart_field_value`].
603/// - `body`: Input consumed by [`crate::runtime::json_commands::request_multipart_field_value`].
604/// - `field_name`: Input consumed by [`crate::runtime::json_commands::request_multipart_field_value`].
605///
606/// # Returns
607///
608/// Returns [`Some`] when [`crate::runtime::json_commands::request_multipart_field_value`] can produce a value and [`None`] otherwise.
609///
610/// # Examples
611///
612/// ```rust,ignore
613/// let _result = pharos_native::runtime::json_commands::request_multipart_field_value(todo!(), b"", todo!());
614/// ```
615///
616/// # Related
617///
618/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
619pub fn request_multipart_field_value(
620 content_type: &str,
621 body: &[u8],
622 field_name: &str,
623) -> Option<String> {
624 multipart_find_part_from_body(body, content_type, field_name).and_then(|part| part.text)
625}
626
627/// Extracts all uploaded files for one multipart field from an in-memory request body.
628pub fn request_multipart_file_parts(
629 content_type: &str,
630 body: &[u8],
631 field_name: &str,
632 max_parts: usize,
633 max_total_bytes: usize,
634) -> Vec<MultipartFilePart> {
635 multipart_uploaded_file_parts(
636 content_type,
637 body,
638 field_name,
639 max_parts,
640 max_total_bytes,
641 false,
642 )
643}
644
645/// Extracts bounded source uploads while preserving legitimate empty source files.
646pub fn request_multipart_source_file_parts(
647 content_type: &str,
648 body: &[u8],
649 field_name: &str,
650 max_parts: usize,
651 max_total_bytes: usize,
652) -> Vec<MultipartFilePart> {
653 multipart_uploaded_file_parts(
654 content_type,
655 body,
656 field_name,
657 max_parts,
658 max_total_bytes,
659 true,
660 )
661}
662
663/// Applies filename validation and the caller's empty-file policy to bounded multipart parts.
664fn multipart_uploaded_file_parts(
665 content_type: &str,
666 body: &[u8],
667 field_name: &str,
668 max_parts: usize,
669 max_total_bytes: usize,
670 allow_empty: bool,
671) -> Vec<MultipartFilePart> {
672 multipart_parts_from_body(body, content_type, field_name, max_parts, max_total_bytes)
673 .into_iter()
674 .filter_map(|part| {
675 let filename = part.filename?;
676 (!filename.trim().is_empty() && (allow_empty || !part.content.is_empty())).then_some(
677 MultipartFilePart {
678 filename,
679 content: part.content,
680 },
681 )
682 })
683 .collect()
684}
685
686/// Performs request form field value.
687///
688/// # Parameters
689///
690/// - `content_type`: Input consumed by [`crate::runtime::json_commands::request_form_field_value`].
691/// - `form_body`: Input consumed by [`crate::runtime::json_commands::request_form_field_value`].
692/// - `body_path`: Input consumed by [`crate::runtime::json_commands::request_form_field_value`].
693/// - `field_name`: Input consumed by [`crate::runtime::json_commands::request_form_field_value`].
694///
695/// # Returns
696///
697/// Returns [`Some`] when [`crate::runtime::json_commands::request_form_field_value`] can produce a value and [`None`] otherwise.
698///
699/// # Performance
700///
701/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_form_field_value`].
702///
703/// # Examples
704///
705/// ```rust,ignore
706/// let _result = pharos_native::runtime::json_commands::request_form_field_value(todo!(), "name=value", todo!(), todo!());
707/// ```
708///
709/// # Related
710///
711/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
712pub fn request_form_field_value(
713 content_type: Option<&str>,
714 form_body: Option<&str>,
715 body_path: Option<&Path>,
716 field_name: &str,
717) -> Option<String> {
718 if field_name.is_empty() {
719 return None;
720 }
721 if content_type.is_some_and(|value| value.starts_with("multipart/form-data")) {
722 let body = fs::read(body_path?).ok()?;
723 return multipart_find_part_from_body(&body, content_type?, field_name)
724 .and_then(|part| part.text);
725 }
726
727 let raw_form = if let Some(path) = body_path.filter(|path| path.exists()) {
728 fs::read_to_string(path)
729 .ok()
730 .unwrap_or_else(|| form_body.unwrap_or("").to_owned())
731 } else {
732 form_body.unwrap_or("").to_owned()
733 };
734 if content_type.is_some_and(|value| value.starts_with("application/json")) {
735 return serde_json::from_str::<Value>(&raw_form)
736 .ok()
737 .and_then(|value| value.as_object().cloned())
738 .and_then(|object| object.get(field_name).cloned())
739 .and_then(|value| match value {
740 Value::String(value) => Some(value),
741 Value::Number(value) => Some(value.to_string()),
742 Value::Bool(value) => Some(value.to_string()),
743 Value::Null => None,
744 _ => None,
745 });
746 }
747 request_query_value(&raw_form, field_name)
748}
749
750/// Performs dynamic multipart filename.
751///
752/// # Parameters
753///
754/// - `content_type`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_filename`].
755/// - `body_path`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_filename`].
756/// - `field_name`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_filename`].
757///
758/// # Returns
759///
760/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_multipart_filename`] can produce a value and [`None`] otherwise.
761///
762/// # Examples
763///
764/// ```rust,ignore
765/// let _result = pharos_native::runtime::json_commands::dynamic_multipart_filename(todo!(), todo!(), todo!());
766/// ```
767///
768/// # Related
769///
770/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
771pub fn dynamic_multipart_filename(
772 content_type: &str,
773 body_path: &Path,
774 field_name: &str,
775) -> Option<String> {
776 multipart_find_part_from_body(&fs::read(body_path).ok()?, content_type, field_name)?.filename
777}
778
779/// Performs dynamic multipart extract text.
780///
781/// # Parameters
782///
783/// - `content_type`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_extract_text`].
784/// - `body_path`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_extract_text`].
785/// - `field_name`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_extract_text`].
786///
787/// # Returns
788///
789/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_multipart_extract_text`] can produce a value and [`None`] otherwise.
790///
791/// # Examples
792///
793/// ```rust,ignore
794/// let _result = pharos_native::runtime::json_commands::dynamic_multipart_extract_text(todo!(), todo!(), todo!());
795/// ```
796///
797/// # Related
798///
799/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
800pub fn dynamic_multipart_extract_text(
801 content_type: &str,
802 body_path: &Path,
803 field_name: &str,
804) -> Option<String> {
805 multipart_find_part_from_body(&fs::read(body_path).ok()?, content_type, field_name)?.text
806}
807
808/// Performs dynamic multipart extract file.
809///
810/// # Parameters
811///
812/// - `content_type`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_extract_file`].
813/// - `body_path`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_extract_file`].
814/// - `field_name`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_extract_file`].
815/// - `output_path`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_extract_file`].
816///
817/// # Returns
818///
819/// Returns the computed value produced by [`crate::runtime::json_commands::dynamic_multipart_extract_file`].
820///
821/// # Examples
822///
823/// ```rust,ignore
824/// let _result = pharos_native::runtime::json_commands::dynamic_multipart_extract_file(todo!(), todo!(), todo!(), todo!());
825/// ```
826///
827/// # Related
828///
829/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
830pub fn dynamic_multipart_extract_file(
831 content_type: &str,
832 body_path: &Path,
833 field_name: &str,
834 output_path: &Path,
835) -> i32 {
836 if output_path.as_os_str().is_empty() {
837 return 64;
838 }
839 let Some(part) = fs::read(body_path)
840 .ok()
841 .and_then(|body| multipart_find_part_from_body(&body, content_type, field_name))
842 else {
843 return 1;
844 };
845 if part.content.is_empty() {
846 return 1;
847 }
848 super::fs::write_binary_file(output_path, &part.content, 0o640)
849 .map(|_| 0)
850 .unwrap_or(73)
851}
852
853/// Performs request booking options JSON from form.
854///
855/// # Parameters
856///
857/// - `content_type`: Input consumed by [`crate::runtime::json_commands::request_booking_options_json_from_form`].
858/// - `form_body`: Input consumed by [`crate::runtime::json_commands::request_booking_options_json_from_form`].
859/// - `body_path`: Input consumed by [`crate::runtime::json_commands::request_booking_options_json_from_form`].
860///
861/// # Returns
862///
863/// Returns [`Some`] when [`crate::runtime::json_commands::request_booking_options_json_from_form`] can produce a value and [`None`] otherwise.
864///
865/// # Performance
866///
867/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_booking_options_json_from_form`].
868///
869/// # Examples
870///
871/// ```rust,ignore
872/// let _result = pharos_native::runtime::json_commands::request_booking_options_json_from_form(todo!(), "name=value", todo!());
873/// ```
874///
875/// # Related
876///
877/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
878pub fn request_booking_options_json_from_form(
879 content_type: Option<&str>,
880 form_body: Option<&str>,
881 body_path: Option<&Path>,
882) -> Option<String> {
883 let count_value =
884 request_form_field_value(content_type, form_body, body_path, "booking_option_count")
885 .filter(|value| value.chars().all(|ch| ch.is_ascii_digit()))
886 .and_then(|value| value.parse::<usize>().ok())
887 .map(|value| value.min(64))
888 .unwrap_or(0);
889
890 let mut raw_lines = Vec::new();
891 if count_value > 0 {
892 for index in 1..=count_value {
893 let label = request_form_field_value(
894 content_type,
895 form_body,
896 body_path,
897 &format!("booking_option_label_{index}"),
898 )
899 .unwrap_or_default();
900 let duration = request_form_field_value(
901 content_type,
902 form_body,
903 body_path,
904 &format!("booking_option_duration_{index}"),
905 )
906 .unwrap_or_default();
907 let cost = request_form_field_value(
908 content_type,
909 form_body,
910 body_path,
911 &format!("booking_option_cost_{index}"),
912 )
913 .unwrap_or_default();
914 if !(label.is_empty() && duration.is_empty() && cost.is_empty()) {
915 raw_lines.push(format!("{label}|{duration}|{cost}"));
916 }
917 }
918 }
919 if raw_lines.is_empty()
920 && let Some(raw) =
921 request_form_field_value(content_type, form_body, body_path, "booking_options")
922 && !raw.is_empty()
923 {
924 raw_lines = raw.lines().map(ToOwned::to_owned).collect();
925 }
926 if raw_lines.is_empty() {
927 return Some("[]".to_owned());
928 }
929
930 let mut rows = Vec::new();
931 for line in raw_lines {
932 let mut parts = line.splitn(3, '|');
933 let label = parts.next().unwrap_or_default();
934 let duration_text = parts.next().unwrap_or_default();
935 let cost = parts.next().unwrap_or_default();
936 if label.is_empty() && duration_text.is_empty() && cost.is_empty() {
937 continue;
938 }
939 let duration = if duration_text.chars().all(|ch| ch.is_ascii_digit()) {
940 duration_text.parse::<u64>().ok().unwrap_or(0)
941 } else {
942 0
943 };
944 rows.push(serde_json::json!({
945 "label": label,
946 "duration_minutes": duration,
947 "cost": cost,
948 }));
949 }
950 serde_json::to_string(&rows).ok()
951}
952
953/// Performs request cookie value.
954///
955/// # Parameters
956///
957/// - `cookie_header`: Input consumed by [`crate::runtime::json_commands::request_cookie_value`].
958/// - `name`: Input consumed by [`crate::runtime::json_commands::request_cookie_value`].
959///
960/// # Returns
961///
962/// Returns [`Some`] when [`crate::runtime::json_commands::request_cookie_value`] can produce a value and [`None`] otherwise.
963///
964/// # Performance
965///
966/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_cookie_value`].
967///
968/// # Examples
969///
970/// ```rust,ignore
971/// let _result = pharos_native::runtime::json_commands::request_cookie_value("session=abc", "name");
972/// ```
973///
974/// # Related
975///
976/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
977pub fn request_cookie_value(cookie_header: &str, name: &str) -> Option<String> {
978 if name.is_empty() {
979 return None;
980 }
981 for segment in cookie_header.split(';') {
982 let (key, value) = segment.trim().split_once('=')?;
983 if key.trim() == name {
984 return Some(value.trim().to_owned());
985 }
986 }
987 None
988}
989
990/// Handles bundle manifest slice order command.
991///
992/// # Parameters
993///
994/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_bundle_manifest_slice_order_command`].
995///
996/// # Returns
997///
998/// Returns [`Some`] when [`crate::runtime::json_commands::handle_bundle_manifest_slice_order_command`] can produce a value and [`None`] otherwise.
999///
1000/// # Examples
1001///
1002/// ```rust,ignore
1003/// let _result = pharos_native::runtime::json_commands::handle_bundle_manifest_slice_order_command(std::path::Path::new("fixture.json"));
1004/// ```
1005///
1006/// # Related
1007///
1008/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1009pub fn handle_bundle_manifest_slice_order_command(path: &Path) -> Option<String> {
1010 bundle_manifest_slice_order_lines(path).map(|lines| lines.join("\n"))
1011}
1012
1013/// Handles bundle manifest layer for slice command.
1014///
1015/// # Parameters
1016///
1017/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_bundle_manifest_layer_for_slice_command`].
1018/// - `slice_name`: Input consumed by [`crate::runtime::json_commands::handle_bundle_manifest_layer_for_slice_command`].
1019///
1020/// # Returns
1021///
1022/// Returns [`Some`] when [`crate::runtime::json_commands::handle_bundle_manifest_layer_for_slice_command`] can produce a value and [`None`] otherwise.
1023///
1024/// # Examples
1025///
1026/// ```rust,ignore
1027/// let _result = pharos_native::runtime::json_commands::handle_bundle_manifest_layer_for_slice_command(std::path::Path::new("fixture.json"), "slice");
1028/// ```
1029///
1030/// # Related
1031///
1032/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1033pub fn handle_bundle_manifest_layer_for_slice_command(
1034 path: &Path,
1035 slice_name: &str,
1036) -> Option<String> {
1037 bundle_manifest_layer_for_slice(path, slice_name).flatten()
1038}
1039
1040/// Handles request query value command.
1041///
1042/// # Parameters
1043///
1044/// - `query`: Input consumed by [`crate::runtime::json_commands::handle_request_query_value_command`].
1045/// - `key`: Input consumed by [`crate::runtime::json_commands::handle_request_query_value_command`].
1046///
1047/// # Returns
1048///
1049/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_query_value_command`] can produce a value and [`None`] otherwise.
1050///
1051/// # Examples
1052///
1053/// ```rust,ignore
1054/// let _result = pharos_native::runtime::json_commands::handle_request_query_value_command("page=1", "key");
1055/// ```
1056///
1057/// # Related
1058///
1059/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1060pub fn handle_request_query_value_command(query: &str, key: &str) -> Option<String> {
1061 request_query_value(query, key)
1062}
1063
1064/// Handles request form value command.
1065///
1066/// # Parameters
1067///
1068/// - `form_body`: Input consumed by [`crate::runtime::json_commands::handle_request_form_value_command`].
1069/// - `key`: Input consumed by [`crate::runtime::json_commands::handle_request_form_value_command`].
1070///
1071/// # Returns
1072///
1073/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_form_value_command`] can produce a value and [`None`] otherwise.
1074///
1075/// # Examples
1076///
1077/// ```rust,ignore
1078/// let _result = pharos_native::runtime::json_commands::handle_request_form_value_command("name=value", "key");
1079/// ```
1080///
1081/// # Related
1082///
1083/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1084pub fn handle_request_form_value_command(form_body: &str, key: &str) -> Option<String> {
1085 request_form_value(form_body, key)
1086}
1087
1088/// Handles request form field command.
1089///
1090/// # Parameters
1091///
1092/// - `content_type`: Input consumed by [`crate::runtime::json_commands::handle_request_form_field_command`].
1093/// - `form_body`: Input consumed by [`crate::runtime::json_commands::handle_request_form_field_command`].
1094/// - `body_path`: Input consumed by [`crate::runtime::json_commands::handle_request_form_field_command`].
1095/// - `field_name`: Input consumed by [`crate::runtime::json_commands::handle_request_form_field_command`].
1096///
1097/// # Returns
1098///
1099/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_form_field_command`] can produce a value and [`None`] otherwise.
1100///
1101/// # Examples
1102///
1103/// ```rust,ignore
1104/// let _result = pharos_native::runtime::json_commands::handle_request_form_field_command(todo!(), "name=value", todo!(), todo!());
1105/// ```
1106///
1107/// # Related
1108///
1109/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1110pub fn handle_request_form_field_command(
1111 content_type: Option<&str>,
1112 form_body: Option<&str>,
1113 body_path: Option<&Path>,
1114 field_name: &str,
1115) -> Option<String> {
1116 request_form_field_value(content_type, form_body, body_path, field_name)
1117}
1118
1119/// Handles request cookie value command.
1120///
1121/// # Parameters
1122///
1123/// - `cookie_header`: Input consumed by [`crate::runtime::json_commands::handle_request_cookie_value_command`].
1124/// - `name`: Input consumed by [`crate::runtime::json_commands::handle_request_cookie_value_command`].
1125///
1126/// # Returns
1127///
1128/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_cookie_value_command`] can produce a value and [`None`] otherwise.
1129///
1130/// # Examples
1131///
1132/// ```rust,ignore
1133/// let _result = pharos_native::runtime::json_commands::handle_request_cookie_value_command("session=abc", "name");
1134/// ```
1135///
1136/// # Related
1137///
1138/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1139pub fn handle_request_cookie_value_command(cookie_header: &str, name: &str) -> Option<String> {
1140 request_cookie_value(cookie_header, name)
1141}
1142
1143/// Handles dynamic multipart filename command.
1144///
1145/// # Parameters
1146///
1147/// - `content_type`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_filename_command`].
1148/// - `body_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_filename_command`].
1149/// - `field_name`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_filename_command`].
1150///
1151/// # Returns
1152///
1153/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_multipart_filename_command`] can produce a value and [`None`] otherwise.
1154///
1155/// # Examples
1156///
1157/// ```rust,ignore
1158/// let _result = pharos_native::runtime::json_commands::handle_dynamic_multipart_filename_command(todo!(), todo!(), todo!());
1159/// ```
1160///
1161/// # Related
1162///
1163/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1164pub fn handle_dynamic_multipart_filename_command(
1165 content_type: &str,
1166 body_path: &Path,
1167 field_name: &str,
1168) -> Option<String> {
1169 dynamic_multipart_filename(content_type, body_path, field_name)
1170}
1171
1172/// Handles dynamic multipart extract text command.
1173///
1174/// # Parameters
1175///
1176/// - `content_type`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_text_command`].
1177/// - `body_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_text_command`].
1178/// - `field_name`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_text_command`].
1179///
1180/// # Returns
1181///
1182/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_multipart_extract_text_command`] can produce a value and [`None`] otherwise.
1183///
1184/// # Examples
1185///
1186/// ```rust,ignore
1187/// let _result = pharos_native::runtime::json_commands::handle_dynamic_multipart_extract_text_command(todo!(), todo!(), todo!());
1188/// ```
1189///
1190/// # Related
1191///
1192/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1193pub fn handle_dynamic_multipart_extract_text_command(
1194 content_type: &str,
1195 body_path: &Path,
1196 field_name: &str,
1197) -> Option<String> {
1198 dynamic_multipart_extract_text(content_type, body_path, field_name)
1199}
1200
1201/// Handles dynamic multipart extract file command.
1202///
1203/// # Parameters
1204///
1205/// - `content_type`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_file_command`].
1206/// - `body_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_file_command`].
1207/// - `field_name`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_file_command`].
1208/// - `output_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_file_command`].
1209///
1210/// # Returns
1211///
1212/// Returns the computed value produced by [`crate::runtime::json_commands::handle_dynamic_multipart_extract_file_command`].
1213///
1214/// # Examples
1215///
1216/// ```rust,ignore
1217/// let _result = pharos_native::runtime::json_commands::handle_dynamic_multipart_extract_file_command(todo!(), todo!(), todo!(), todo!());
1218/// ```
1219///
1220/// # Related
1221///
1222/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1223pub fn handle_dynamic_multipart_extract_file_command(
1224 content_type: &str,
1225 body_path: &Path,
1226 field_name: &str,
1227 output_path: &Path,
1228) -> i32 {
1229 dynamic_multipart_extract_file(content_type, body_path, field_name, output_path)
1230}
1231
1232/// Handles request booking options JSON from form command.
1233///
1234/// # Parameters
1235///
1236/// - `content_type`: Input consumed by [`crate::runtime::json_commands::handle_request_booking_options_json_from_form_command`].
1237/// - `form_body`: Input consumed by [`crate::runtime::json_commands::handle_request_booking_options_json_from_form_command`].
1238/// - `body_path`: Input consumed by [`crate::runtime::json_commands::handle_request_booking_options_json_from_form_command`].
1239///
1240/// # Returns
1241///
1242/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_booking_options_json_from_form_command`] can produce a value and [`None`] otherwise.
1243///
1244/// # Examples
1245///
1246/// ```rust,ignore
1247/// let _result = pharos_native::runtime::json_commands::handle_request_booking_options_json_from_form_command(todo!(), "name=value", todo!());
1248/// ```
1249///
1250/// # Related
1251///
1252/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1253pub fn handle_request_booking_options_json_from_form_command(
1254 content_type: Option<&str>,
1255 form_body: Option<&str>,
1256 body_path: Option<&Path>,
1257) -> Option<String> {
1258 request_booking_options_json_from_form(content_type, form_body, body_path)
1259}
1260
1261/// Handles request compact JSON command.
1262///
1263/// # Parameters
1264///
1265/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_request_compact_json_command`].
1266///
1267/// # Returns
1268///
1269/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_compact_json_command`] can produce a value and [`None`] otherwise.
1270///
1271/// # Examples
1272///
1273/// ```rust,ignore
1274/// let _result = pharos_native::runtime::json_commands::handle_request_compact_json_command("{}");
1275/// ```
1276///
1277/// # Related
1278///
1279/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1280pub fn handle_request_compact_json_command(text: &str) -> Option<String> {
1281 request_compact_json(text)
1282}
1283
1284/// Handles request compact object command.
1285///
1286/// # Parameters
1287///
1288/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_request_compact_object_command`].
1289///
1290/// # Returns
1291///
1292/// Returns the computed value produced by [`crate::runtime::json_commands::handle_request_compact_object_command`].
1293///
1294/// # Examples
1295///
1296/// ```rust,ignore
1297/// let _result = pharos_native::runtime::json_commands::handle_request_compact_object_command("{}");
1298/// ```
1299///
1300/// # Related
1301///
1302/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1303pub fn handle_request_compact_object_command(text: &str) -> String {
1304 request_compact_object(text)
1305}
1306
1307/// Handles request compact object strict command.
1308///
1309/// # Parameters
1310///
1311/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_request_compact_object_strict_command`].
1312///
1313/// # Returns
1314///
1315/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_compact_object_strict_command`] can produce a value and [`None`] otherwise.
1316///
1317/// # Examples
1318///
1319/// ```rust,ignore
1320/// let _result = pharos_native::runtime::json_commands::handle_request_compact_object_strict_command("{}");
1321/// ```
1322///
1323/// # Related
1324///
1325/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1326pub fn handle_request_compact_object_strict_command(text: &str) -> Option<String> {
1327 request_compact_object_strict(text)
1328}
1329
1330/// Handles request field command.
1331///
1332/// # Parameters
1333///
1334/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_request_field_command`].
1335/// - `field`: Input consumed by [`crate::runtime::json_commands::handle_request_field_command`].
1336///
1337/// # Returns
1338///
1339/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_field_command`] can produce a value and [`None`] otherwise.
1340///
1341/// # Examples
1342///
1343/// ```rust,ignore
1344/// let _result = pharos_native::runtime::json_commands::handle_request_field_command("{}", "field");
1345/// ```
1346///
1347/// # Related
1348///
1349/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1350pub fn handle_request_field_command(text: &str, field: &str) -> Option<String> {
1351 request_field(text, field)
1352}
1353
1354/// Handles manifest fixture path raw command.
1355///
1356/// # Parameters
1357///
1358/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_manifest_fixture_path_raw_command`].
1359/// - `key`: Input consumed by [`crate::runtime::json_commands::handle_manifest_fixture_path_raw_command`].
1360///
1361/// # Returns
1362///
1363/// Returns [`Some`] when [`crate::runtime::json_commands::handle_manifest_fixture_path_raw_command`] can produce a value and [`None`] otherwise.
1364///
1365/// # Examples
1366///
1367/// ```rust,ignore
1368/// let _result = pharos_native::runtime::json_commands::handle_manifest_fixture_path_raw_command(std::path::Path::new("fixture.json"), "key");
1369/// ```
1370///
1371/// # Related
1372///
1373/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1374pub fn handle_manifest_fixture_path_raw_command(path: &Path, key: &str) -> Option<String> {
1375 manifest_fixture_path_raw(path, key)
1376}
1377
1378/// Handles manifest app root command.
1379///
1380/// # Parameters
1381///
1382/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_manifest_app_root_command`].
1383///
1384/// # Returns
1385///
1386/// Returns [`Some`] when [`crate::runtime::json_commands::handle_manifest_app_root_command`] can produce a value and [`None`] otherwise.
1387///
1388/// # Examples
1389///
1390/// ```rust,ignore
1391/// let _result = pharos_native::runtime::json_commands::handle_manifest_app_root_command(std::path::Path::new("fixture.json"));
1392/// ```
1393///
1394/// # Related
1395///
1396/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1397pub fn handle_manifest_app_root_command(path: &Path) -> Option<String> {
1398 manifest_app_root(path)
1399}
1400
1401/// Finds form field value.
1402fn find_form_field_value<'a>(root: &'a Value, form_id: &str, field_id: &str) -> Option<&'a Value> {
1403 let forms = root.get("forms")?.as_array()?;
1404 for form in forms {
1405 if json_string(form, "form_id") != Some(form_id) {
1406 continue;
1407 }
1408 let fields = form.get("fields")?.as_array()?;
1409 for field in fields {
1410 if json_string(field, "field_id") == Some(field_id) {
1411 return Some(field);
1412 }
1413 }
1414 }
1415 None
1416}
1417
1418/// Performs form field JSON.
1419///
1420/// # Parameters
1421///
1422/// - `path`: Input consumed by [`crate::runtime::json_commands::form_field_json`].
1423/// - `form_id`: Input consumed by [`crate::runtime::json_commands::form_field_json`].
1424/// - `field_id`: Input consumed by [`crate::runtime::json_commands::form_field_json`].
1425///
1426/// # Returns
1427///
1428/// Returns [`Some`] when [`crate::runtime::json_commands::form_field_json`] can produce a value and [`None`] otherwise.
1429///
1430/// # Examples
1431///
1432/// ```rust,ignore
1433/// let _result = pharos_native::runtime::json_commands::form_field_json(std::path::Path::new("fixture.json"), "form", "field");
1434/// ```
1435///
1436/// # Related
1437///
1438/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1439pub fn form_field_json(path: &Path, form_id: &str, field_id: &str) -> Option<String> {
1440 if form_id.is_empty() || field_id.is_empty() {
1441 return None;
1442 }
1443 let root = read_json_file(path)?;
1444 serde_json::to_string(find_form_field_value(&root, form_id, field_id)?).ok()
1445}
1446
1447/// Performs form field JSON lines.
1448///
1449/// # Parameters
1450///
1451/// - `path`: Input consumed by [`crate::runtime::json_commands::form_field_json_lines`].
1452/// - `form_id`: Input consumed by [`crate::runtime::json_commands::form_field_json_lines`].
1453///
1454/// # Returns
1455///
1456/// Returns [`Some`] when [`crate::runtime::json_commands::form_field_json_lines`] can produce a value and [`None`] otherwise.
1457///
1458/// # Performance
1459///
1460/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::form_field_json_lines`].
1461///
1462/// # Examples
1463///
1464/// ```rust,ignore
1465/// let _result = pharos_native::runtime::json_commands::form_field_json_lines(std::path::Path::new("fixture.json"), "form");
1466/// ```
1467///
1468/// # Related
1469///
1470/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1471pub fn form_field_json_lines(path: &Path, form_id: &str) -> Option<Vec<String>> {
1472 if form_id.is_empty() {
1473 return None;
1474 }
1475 let root = read_json_file(path)?;
1476 let forms = root.get("forms")?.as_array()?;
1477 for form in forms {
1478 if json_string(form, "form_id") != Some(form_id) {
1479 continue;
1480 }
1481 let mut lines = Vec::new();
1482 for field in form.get("fields")?.as_array()? {
1483 lines.push(serde_json::to_string(field).ok()?);
1484 }
1485 return Some(lines);
1486 }
1487 Some(Vec::new())
1488}
1489
1490/// Performs form options csv.
1491///
1492/// # Parameters
1493///
1494/// - `path`: Input consumed by [`crate::runtime::json_commands::form_options_csv`].
1495/// - `form_id`: Input consumed by [`crate::runtime::json_commands::form_options_csv`].
1496/// - `field_id`: Input consumed by [`crate::runtime::json_commands::form_options_csv`].
1497///
1498/// # Returns
1499///
1500/// Returns [`Some`] when [`crate::runtime::json_commands::form_options_csv`] can produce a value and [`None`] otherwise.
1501///
1502/// # Performance
1503///
1504/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::form_options_csv`].
1505///
1506/// # Examples
1507///
1508/// ```rust,ignore
1509/// let _result = pharos_native::runtime::json_commands::form_options_csv(std::path::Path::new("fixture.json"), "form", "field");
1510/// ```
1511///
1512/// # Related
1513///
1514/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1515pub fn form_options_csv(path: &Path, form_id: &str, field_id: &str) -> Option<String> {
1516 if form_id.is_empty() || field_id.is_empty() {
1517 return None;
1518 }
1519 let root = read_json_file(path)?;
1520 let field = find_form_field_value(&root, form_id, field_id)?;
1521 let mut values = Vec::new();
1522 for option in field.get("options")?.as_array()? {
1523 if let Some(value) = json_string(option, "value") {
1524 values.push(value.to_owned());
1525 }
1526 }
1527 Some(values.join(","))
1528}
1529
1530/// Performs form options HTML.
1531///
1532/// # Parameters
1533///
1534/// - `app_root`: Input consumed by [`crate::runtime::json_commands::form_options_html`].
1535/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::form_options_html`].
1536/// - `form_id`: Input consumed by [`crate::runtime::json_commands::form_options_html`].
1537/// - `field_id`: Input consumed by [`crate::runtime::json_commands::form_options_html`].
1538/// - `selected`: Input consumed by [`crate::runtime::json_commands::form_options_html`].
1539///
1540/// # Returns
1541///
1542/// Returns [`Some`] when [`crate::runtime::json_commands::form_options_html`] can produce a value and [`None`] otherwise.
1543///
1544/// # Performance
1545///
1546/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::form_options_html`].
1547///
1548/// # Examples
1549///
1550/// ```rust,ignore
1551/// let _result = pharos_native::runtime::json_commands::form_options_html(std::path::Path::new("apps/dev_docs"), todo!(), "form", "field", todo!());
1552/// ```
1553///
1554/// # Related
1555///
1556/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1557pub fn form_options_html(
1558 app_root: &Path,
1559 fixture_path: &Path,
1560 form_id: &str,
1561 field_id: &str,
1562 selected: Option<&str>,
1563) -> Option<String> {
1564 if app_root.as_os_str().is_empty() || form_id.is_empty() || field_id.is_empty() {
1565 return None;
1566 }
1567 let root = read_json_file(fixture_path)?;
1568 let field = find_form_field_value(&root, form_id, field_id)?;
1569 let mut out = String::new();
1570 for option in field.get("options")?.as_array()? {
1571 let Some(value) = json_string(option, "value") else {
1572 continue;
1573 };
1574 let value = value.to_owned();
1575 let label = json_string(option, "label")
1576 .map(ToOwned::to_owned)
1577 .unwrap_or_else(|| value.clone());
1578 let params = serde_json::Map::from_iter([
1579 ("option_value".to_owned(), Value::String(value.to_string())),
1580 (
1581 "selected_attr".to_owned(),
1582 Value::String(
1583 if selected == Some(value.as_str()) {
1584 " selected"
1585 } else {
1586 Default::default()
1587 }
1588 .to_owned(),
1589 ),
1590 ),
1591 ("option_label".to_owned(), Value::String(label.to_string())),
1592 ]);
1593 out.push_str(
1594 &generated::render_generated_admin_fragment_from_value_runtime(
1595 app_root,
1596 "templates/shared/fragments/select_option.html",
1597 ¶ms,
1598 )?,
1599 );
1600 }
1601 Some(out)
1602}
1603
1604/// Handles form field JSON command.
1605///
1606/// # Parameters
1607///
1608/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_form_field_json_command`].
1609/// - `form_id`: Input consumed by [`crate::runtime::json_commands::handle_form_field_json_command`].
1610/// - `field_id`: Input consumed by [`crate::runtime::json_commands::handle_form_field_json_command`].
1611///
1612/// # Returns
1613///
1614/// Returns [`Some`] when [`crate::runtime::json_commands::handle_form_field_json_command`] can produce a value and [`None`] otherwise.
1615///
1616/// # Examples
1617///
1618/// ```rust,ignore
1619/// let _result = pharos_native::runtime::json_commands::handle_form_field_json_command(std::path::Path::new("fixture.json"), "form", "field");
1620/// ```
1621///
1622/// # Related
1623///
1624/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1625pub fn handle_form_field_json_command(
1626 path: &Path,
1627 form_id: &str,
1628 field_id: &str,
1629) -> Option<String> {
1630 form_field_json(path, form_id, field_id)
1631}
1632
1633/// Handles form field JSON lines command.
1634///
1635/// # Parameters
1636///
1637/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_form_field_json_lines_command`].
1638/// - `form_id`: Input consumed by [`crate::runtime::json_commands::handle_form_field_json_lines_command`].
1639///
1640/// # Returns
1641///
1642/// Returns [`Some`] when [`crate::runtime::json_commands::handle_form_field_json_lines_command`] can produce a value and [`None`] otherwise.
1643///
1644/// # Examples
1645///
1646/// ```rust,ignore
1647/// let _result = pharos_native::runtime::json_commands::handle_form_field_json_lines_command(std::path::Path::new("fixture.json"), "form");
1648/// ```
1649///
1650/// # Related
1651///
1652/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1653pub fn handle_form_field_json_lines_command(path: &Path, form_id: &str) -> Option<String> {
1654 form_field_json_lines(path, form_id).map(|lines| lines.join("\n"))
1655}
1656
1657/// Handles form options csv command.
1658///
1659/// # Parameters
1660///
1661/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_form_options_csv_command`].
1662/// - `form_id`: Input consumed by [`crate::runtime::json_commands::handle_form_options_csv_command`].
1663/// - `field_id`: Input consumed by [`crate::runtime::json_commands::handle_form_options_csv_command`].
1664///
1665/// # Returns
1666///
1667/// Returns [`Some`] when [`crate::runtime::json_commands::handle_form_options_csv_command`] can produce a value and [`None`] otherwise.
1668///
1669/// # Examples
1670///
1671/// ```rust,ignore
1672/// let _result = pharos_native::runtime::json_commands::handle_form_options_csv_command(std::path::Path::new("fixture.json"), "form", "field");
1673/// ```
1674///
1675/// # Related
1676///
1677/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1678pub fn handle_form_options_csv_command(
1679 path: &Path,
1680 form_id: &str,
1681 field_id: &str,
1682) -> Option<String> {
1683 form_options_csv(path, form_id, field_id)
1684}
1685
1686/// Handles form options HTML command.
1687///
1688/// # Parameters
1689///
1690/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_form_options_html_command`].
1691/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::handle_form_options_html_command`].
1692/// - `form_id`: Input consumed by [`crate::runtime::json_commands::handle_form_options_html_command`].
1693/// - `field_id`: Input consumed by [`crate::runtime::json_commands::handle_form_options_html_command`].
1694/// - `selected`: Input consumed by [`crate::runtime::json_commands::handle_form_options_html_command`].
1695///
1696/// # Returns
1697///
1698/// Returns [`Some`] when [`crate::runtime::json_commands::handle_form_options_html_command`] can produce a value and [`None`] otherwise.
1699///
1700/// # Examples
1701///
1702/// ```rust,ignore
1703/// let _result = pharos_native::runtime::json_commands::handle_form_options_html_command(std::path::Path::new("apps/dev_docs"), todo!(), "form", "field", todo!());
1704/// ```
1705///
1706/// # Related
1707///
1708/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1709pub fn handle_form_options_html_command(
1710 app_root: &Path,
1711 fixture_path: &Path,
1712 form_id: &str,
1713 field_id: &str,
1714 selected: Option<&str>,
1715) -> Option<String> {
1716 form_options_html(app_root, fixture_path, form_id, field_id, selected)
1717}
1718
1719/// Performs object value at path in value.
1720fn object_value_at_path_in_value<'a>(root: &'a Value, path: &str) -> Option<&'a Value> {
1721 if path.is_empty() {
1722 return None;
1723 }
1724 let mut current = root;
1725 for segment in path.split('.') {
1726 if segment.is_empty() {
1727 return None;
1728 }
1729 current = current.as_object()?.get(segment)?;
1730 }
1731 Some(current)
1732}
1733
1734/// Performs object field string.
1735///
1736/// # Parameters
1737///
1738/// - `text`: Input consumed by [`crate::runtime::json_commands::object_field_string`].
1739/// - `field`: Input consumed by [`crate::runtime::json_commands::object_field_string`].
1740///
1741/// # Returns
1742///
1743/// Returns [`Some`] when [`crate::runtime::json_commands::object_field_string`] can produce a value and [`None`] otherwise.
1744///
1745/// # Performance
1746///
1747/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_field_string`].
1748///
1749/// # Examples
1750///
1751/// ```rust,ignore
1752/// let _result = pharos_native::runtime::json_commands::object_field_string("{}", "field");
1753/// ```
1754///
1755/// # Related
1756///
1757/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1758pub fn object_field_string(text: &str, field: &str) -> Option<String> {
1759 if field.is_empty() {
1760 return None;
1761 }
1762 serde_json::from_str::<Value>(text)
1763 .ok()?
1764 .as_object()?
1765 .get(field)?
1766 .as_str()
1767 .map(ToOwned::to_owned)
1768}
1769
1770/// Performs object field number.
1771///
1772/// # Parameters
1773///
1774/// - `text`: Input consumed by [`crate::runtime::json_commands::object_field_number`].
1775/// - `field`: Input consumed by [`crate::runtime::json_commands::object_field_number`].
1776///
1777/// # Returns
1778///
1779/// Returns [`Some`] when [`crate::runtime::json_commands::object_field_number`] can produce a value and [`None`] otherwise.
1780///
1781/// # Performance
1782///
1783/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_field_number`].
1784///
1785/// # Examples
1786///
1787/// ```rust,ignore
1788/// let _result = pharos_native::runtime::json_commands::object_field_number("{}", "field");
1789/// ```
1790///
1791/// # Related
1792///
1793/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1794pub fn object_field_number(text: &str, field: &str) -> Option<i64> {
1795 if field.is_empty() {
1796 return None;
1797 }
1798 Some(
1799 serde_json::from_str::<Value>(text)
1800 .ok()?
1801 .as_object()?
1802 .get(field)
1803 .and_then(Value::as_i64)
1804 .unwrap_or(0),
1805 )
1806}
1807
1808/// Performs object field text.
1809///
1810/// # Parameters
1811///
1812/// - `text`: Input consumed by [`crate::runtime::json_commands::object_field_text`].
1813/// - `field`: Input consumed by [`crate::runtime::json_commands::object_field_text`].
1814///
1815/// # Returns
1816///
1817/// Returns [`Some`] when [`crate::runtime::json_commands::object_field_text`] can produce a value and [`None`] otherwise.
1818///
1819/// # Performance
1820///
1821/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_field_text`].
1822///
1823/// # Examples
1824///
1825/// ```rust,ignore
1826/// let _result = pharos_native::runtime::json_commands::object_field_text("{}", "field");
1827/// ```
1828///
1829/// # Related
1830///
1831/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1832pub fn object_field_text(text: &str, field: &str) -> Option<String> {
1833 if field.is_empty() {
1834 return None;
1835 }
1836 let value = serde_json::from_str::<Value>(text)
1837 .ok()?
1838 .as_object()?
1839 .get(field)?
1840 .clone();
1841 (!value.is_null()).then(|| match value {
1842 Value::String(text) => text,
1843 other => serde_json::to_string(&other).unwrap_or_default(),
1844 })
1845}
1846
1847/// Performs object array length.
1848///
1849/// # Parameters
1850///
1851/// - `text`: Input consumed by [`crate::runtime::json_commands::object_array_length`].
1852///
1853/// # Returns
1854///
1855/// Returns [`Some`] when [`crate::runtime::json_commands::object_array_length`] can produce a value and [`None`] otherwise.
1856///
1857/// # Performance
1858///
1859/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_array_length`].
1860///
1861/// # Examples
1862///
1863/// ```rust,ignore
1864/// let _result = pharos_native::runtime::json_commands::object_array_length("{}");
1865/// ```
1866///
1867/// # Related
1868///
1869/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1870pub fn object_array_length(text: &str) -> Option<usize> {
1871 Some(
1872 serde_json::from_str::<Value>(text)
1873 .ok()?
1874 .as_array()
1875 .map(Vec::len)
1876 .unwrap_or(0),
1877 )
1878}
1879
1880/// Performs object array all integers.
1881///
1882/// # Parameters
1883///
1884/// - `text`: Input consumed by [`crate::runtime::json_commands::object_array_all_integers`].
1885///
1886/// # Returns
1887///
1888/// Returns [`Some`] when [`crate::runtime::json_commands::object_array_all_integers`] can produce a value and [`None`] otherwise.
1889///
1890/// # Performance
1891///
1892/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_array_all_integers`].
1893///
1894/// # Examples
1895///
1896/// ```rust,ignore
1897/// let _result = pharos_native::runtime::json_commands::object_array_all_integers("{}");
1898/// ```
1899///
1900/// # Related
1901///
1902/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1903pub fn object_array_all_integers(text: &str) -> Option<bool> {
1904 let root = serde_json::from_str::<Value>(text).ok()?;
1905 let Some(items) = root.as_array() else {
1906 return Some(false);
1907 };
1908 Some(
1909 items
1910 .iter()
1911 .all(|item| item.as_i64().is_some() || item.as_u64().is_some()),
1912 )
1913}
1914
1915/// Performs object path JSON.
1916///
1917/// # Parameters
1918///
1919/// - `text`: Input consumed by [`crate::runtime::json_commands::object_path_json`].
1920/// - `path`: Input consumed by [`crate::runtime::json_commands::object_path_json`].
1921///
1922/// # Returns
1923///
1924/// Returns [`Some`] when [`crate::runtime::json_commands::object_path_json`] can produce a value and [`None`] otherwise.
1925///
1926/// # Performance
1927///
1928/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_path_json`].
1929///
1930/// # Examples
1931///
1932/// ```rust,ignore
1933/// let _result = pharos_native::runtime::json_commands::object_path_json("{}", std::path::Path::new("fixture.json"));
1934/// ```
1935///
1936/// # Related
1937///
1938/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1939pub fn object_path_json(text: &str, path: &str) -> Option<String> {
1940 let root = serde_json::from_str::<Value>(text).ok()?;
1941 let Some(value) = object_value_at_path_in_value(&root, path) else {
1942 return Some("null".to_owned());
1943 };
1944 serde_json::to_string(value).ok()
1945}
1946
1947/// Performs object path string.
1948///
1949/// # Parameters
1950///
1951/// - `text`: Input consumed by [`crate::runtime::json_commands::object_path_string`].
1952/// - `path`: Input consumed by [`crate::runtime::json_commands::object_path_string`].
1953///
1954/// # Returns
1955///
1956/// Returns [`Some`] when [`crate::runtime::json_commands::object_path_string`] can produce a value and [`None`] otherwise.
1957///
1958/// # Performance
1959///
1960/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_path_string`].
1961///
1962/// # Examples
1963///
1964/// ```rust,ignore
1965/// let _result = pharos_native::runtime::json_commands::object_path_string("{}", std::path::Path::new("fixture.json"));
1966/// ```
1967///
1968/// # Related
1969///
1970/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
1971pub fn object_path_string(text: &str, path: &str) -> Option<String> {
1972 let root = serde_json::from_str::<Value>(text).ok()?;
1973 object_value_at_path_in_value(&root, path)?
1974 .as_str()
1975 .map(ToOwned::to_owned)
1976}
1977
1978/// Performs object path number.
1979///
1980/// # Parameters
1981///
1982/// - `text`: Input consumed by [`crate::runtime::json_commands::object_path_number`].
1983/// - `path`: Input consumed by [`crate::runtime::json_commands::object_path_number`].
1984///
1985/// # Returns
1986///
1987/// Returns [`Some`] when [`crate::runtime::json_commands::object_path_number`] can produce a value and [`None`] otherwise.
1988///
1989/// # Performance
1990///
1991/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_path_number`].
1992///
1993/// # Examples
1994///
1995/// ```rust,ignore
1996/// let _result = pharos_native::runtime::json_commands::object_path_number("{}", std::path::Path::new("fixture.json"));
1997/// ```
1998///
1999/// # Related
2000///
2001/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2002pub fn object_path_number(text: &str, path: &str) -> Option<i64> {
2003 let root = serde_json::from_str::<Value>(text).ok()?;
2004 Some(
2005 object_value_at_path_in_value(&root, path)
2006 .and_then(Value::as_i64)
2007 .unwrap_or(0),
2008 )
2009}
2010
2011/// Performs object path bool.
2012///
2013/// # Parameters
2014///
2015/// - `text`: Input consumed by [`crate::runtime::json_commands::object_path_bool`].
2016/// - `path`: Input consumed by [`crate::runtime::json_commands::object_path_bool`].
2017///
2018/// # Returns
2019///
2020/// Returns [`Some`] when [`crate::runtime::json_commands::object_path_bool`] can produce a value and [`None`] otherwise.
2021///
2022/// # Performance
2023///
2024/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_path_bool`].
2025///
2026/// # Examples
2027///
2028/// ```rust,ignore
2029/// let _result = pharos_native::runtime::json_commands::object_path_bool("{}", std::path::Path::new("fixture.json"));
2030/// ```
2031///
2032/// # Related
2033///
2034/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2035pub fn object_path_bool(text: &str, path: &str) -> Option<bool> {
2036 let root = serde_json::from_str::<Value>(text).ok()?;
2037 Some(
2038 object_value_at_path_in_value(&root, path)
2039 .and_then(Value::as_bool)
2040 .unwrap_or(false),
2041 )
2042}
2043
2044/// Handles object field string command.
2045///
2046/// # Parameters
2047///
2048/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_field_string_command`].
2049/// - `field`: Input consumed by [`crate::runtime::json_commands::handle_object_field_string_command`].
2050///
2051/// # Returns
2052///
2053/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_field_string_command`] can produce a value and [`None`] otherwise.
2054///
2055/// # Examples
2056///
2057/// ```rust,ignore
2058/// let _result = pharos_native::runtime::json_commands::handle_object_field_string_command("{}", "field");
2059/// ```
2060///
2061/// # Related
2062///
2063/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2064pub fn handle_object_field_string_command(text: &str, field: &str) -> Option<String> {
2065 object_field_string(text, field)
2066}
2067
2068/// Handles object field number command.
2069///
2070/// # Parameters
2071///
2072/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_field_number_command`].
2073/// - `field`: Input consumed by [`crate::runtime::json_commands::handle_object_field_number_command`].
2074///
2075/// # Returns
2076///
2077/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_field_number_command`] can produce a value and [`None`] otherwise.
2078///
2079/// # Examples
2080///
2081/// ```rust,ignore
2082/// let _result = pharos_native::runtime::json_commands::handle_object_field_number_command("{}", "field");
2083/// ```
2084///
2085/// # Related
2086///
2087/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2088pub fn handle_object_field_number_command(text: &str, field: &str) -> Option<i64> {
2089 object_field_number(text, field)
2090}
2091
2092/// Handles object field text command.
2093///
2094/// # Parameters
2095///
2096/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_field_text_command`].
2097/// - `field`: Input consumed by [`crate::runtime::json_commands::handle_object_field_text_command`].
2098///
2099/// # Returns
2100///
2101/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_field_text_command`] can produce a value and [`None`] otherwise.
2102///
2103/// # Examples
2104///
2105/// ```rust,ignore
2106/// let _result = pharos_native::runtime::json_commands::handle_object_field_text_command("{}", "field");
2107/// ```
2108///
2109/// # Related
2110///
2111/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2112pub fn handle_object_field_text_command(text: &str, field: &str) -> Option<String> {
2113 object_field_text(text, field)
2114}
2115
2116/// Handles object array length command.
2117///
2118/// # Parameters
2119///
2120/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_array_length_command`].
2121///
2122/// # Returns
2123///
2124/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_array_length_command`] can produce a value and [`None`] otherwise.
2125///
2126/// # Examples
2127///
2128/// ```rust,ignore
2129/// let _result = pharos_native::runtime::json_commands::handle_object_array_length_command("{}");
2130/// ```
2131///
2132/// # Related
2133///
2134/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2135pub fn handle_object_array_length_command(text: &str) -> Option<usize> {
2136 object_array_length(text)
2137}
2138
2139/// Handles object array all integers command.
2140///
2141/// # Parameters
2142///
2143/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_array_all_integers_command`].
2144///
2145/// # Returns
2146///
2147/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_array_all_integers_command`] can produce a value and [`None`] otherwise.
2148///
2149/// # Examples
2150///
2151/// ```rust,ignore
2152/// let _result = pharos_native::runtime::json_commands::handle_object_array_all_integers_command("{}");
2153/// ```
2154///
2155/// # Related
2156///
2157/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2158pub fn handle_object_array_all_integers_command(text: &str) -> Option<bool> {
2159 object_array_all_integers(text)
2160}
2161
2162/// Handles object path JSON command.
2163///
2164/// # Parameters
2165///
2166/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_path_json_command`].
2167/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_object_path_json_command`].
2168///
2169/// # Returns
2170///
2171/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_path_json_command`] can produce a value and [`None`] otherwise.
2172///
2173/// # Examples
2174///
2175/// ```rust,ignore
2176/// let _result = pharos_native::runtime::json_commands::handle_object_path_json_command("{}", std::path::Path::new("fixture.json"));
2177/// ```
2178///
2179/// # Related
2180///
2181/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2182pub fn handle_object_path_json_command(text: &str, path: &str) -> Option<String> {
2183 object_path_json(text, path)
2184}
2185
2186/// Handles object path string command.
2187///
2188/// # Parameters
2189///
2190/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_path_string_command`].
2191/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_object_path_string_command`].
2192///
2193/// # Returns
2194///
2195/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_path_string_command`] can produce a value and [`None`] otherwise.
2196///
2197/// # Examples
2198///
2199/// ```rust,ignore
2200/// let _result = pharos_native::runtime::json_commands::handle_object_path_string_command("{}", std::path::Path::new("fixture.json"));
2201/// ```
2202///
2203/// # Related
2204///
2205/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2206pub fn handle_object_path_string_command(text: &str, path: &str) -> Option<String> {
2207 object_path_string(text, path)
2208}
2209
2210/// Handles object path number command.
2211///
2212/// # Parameters
2213///
2214/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_path_number_command`].
2215/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_object_path_number_command`].
2216///
2217/// # Returns
2218///
2219/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_path_number_command`] can produce a value and [`None`] otherwise.
2220///
2221/// # Examples
2222///
2223/// ```rust,ignore
2224/// let _result = pharos_native::runtime::json_commands::handle_object_path_number_command("{}", std::path::Path::new("fixture.json"));
2225/// ```
2226///
2227/// # Related
2228///
2229/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2230pub fn handle_object_path_number_command(text: &str, path: &str) -> Option<i64> {
2231 object_path_number(text, path)
2232}
2233
2234/// Handles object path bool command.
2235///
2236/// # Parameters
2237///
2238/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_path_bool_command`].
2239/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_object_path_bool_command`].
2240///
2241/// # Returns
2242///
2243/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_path_bool_command`] can produce a value and [`None`] otherwise.
2244///
2245/// # Examples
2246///
2247/// ```rust,ignore
2248/// let _result = pharos_native::runtime::json_commands::handle_object_path_bool_command("{}", std::path::Path::new("fixture.json"));
2249/// ```
2250///
2251/// # Related
2252///
2253/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2254pub fn handle_object_path_bool_command(text: &str, path: &str) -> Option<bool> {
2255 object_path_bool(text, path)
2256}
2257
2258/// Performs object path array length.
2259///
2260/// # Parameters
2261///
2262/// - `text`: Input consumed by [`crate::runtime::json_commands::object_path_array_length`].
2263/// - `path`: Input consumed by [`crate::runtime::json_commands::object_path_array_length`].
2264///
2265/// # Returns
2266///
2267/// Returns [`Some`] when [`crate::runtime::json_commands::object_path_array_length`] can produce a value and [`None`] otherwise.
2268///
2269/// # Performance
2270///
2271/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_path_array_length`].
2272///
2273/// # Examples
2274///
2275/// ```rust,ignore
2276/// let _result = pharos_native::runtime::json_commands::object_path_array_length("{}", std::path::Path::new("fixture.json"));
2277/// ```
2278///
2279/// # Related
2280///
2281/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2282pub fn object_path_array_length(text: &str, path: &str) -> Option<usize> {
2283 let root = serde_json::from_str::<Value>(text).ok()?;
2284 Some(
2285 object_value_at_path_in_value(&root, path)
2286 .and_then(Value::as_array)
2287 .map(Vec::len)
2288 .unwrap_or(0),
2289 )
2290}
2291
2292/// Performs object array item JSON lines.
2293///
2294/// # Parameters
2295///
2296/// - `text`: Input consumed by [`crate::runtime::json_commands::object_array_item_json_lines`].
2297///
2298/// # Returns
2299///
2300/// Returns [`Some`] when [`crate::runtime::json_commands::object_array_item_json_lines`] can produce a value and [`None`] otherwise.
2301///
2302/// # Performance
2303///
2304/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_array_item_json_lines`].
2305///
2306/// # Examples
2307///
2308/// ```rust,ignore
2309/// let _result = pharos_native::runtime::json_commands::object_array_item_json_lines("{}");
2310/// ```
2311///
2312/// # Related
2313///
2314/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2315pub fn object_array_item_json_lines(text: &str) -> Option<Vec<String>> {
2316 let root = serde_json::from_str::<Value>(text).ok()?;
2317 let mut lines = Vec::new();
2318 for item in root.as_array()? {
2319 lines.push(serde_json::to_string(item).ok()?);
2320 }
2321 Some(lines)
2322}
2323
2324/// Performs object array lines.
2325///
2326/// # Parameters
2327///
2328/// - `text`: Input consumed by [`crate::runtime::json_commands::object_array_lines`].
2329///
2330/// # Returns
2331///
2332/// Returns [`Some`] when [`crate::runtime::json_commands::object_array_lines`] 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::runtime::json_commands::object_array_lines`].
2337///
2338/// # Examples
2339///
2340/// ```rust,ignore
2341/// let _result = pharos_native::runtime::json_commands::object_array_lines("{}");
2342/// ```
2343///
2344/// # Related
2345///
2346/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2347pub fn object_array_lines(text: &str) -> Option<Vec<String>> {
2348 let root = serde_json::from_str::<Value>(text).ok()?;
2349 let mut lines = Vec::new();
2350 for item in root.as_array()? {
2351 match item {
2352 Value::String(text) => lines.push(text.clone()),
2353 other => lines.push(serde_json::to_string(other).ok()?),
2354 }
2355 }
2356 Some(lines)
2357}
2358
2359/// Performs object relative path array to absolute JSON.
2360///
2361/// # Parameters
2362///
2363/// - `text`: Input consumed by [`crate::runtime::json_commands::object_relative_path_array_to_absolute_json`].
2364/// - `app_root`: Input consumed by [`crate::runtime::json_commands::object_relative_path_array_to_absolute_json`].
2365///
2366/// # Returns
2367///
2368/// Returns [`Some`] when [`crate::runtime::json_commands::object_relative_path_array_to_absolute_json`] can produce a value and [`None`] otherwise.
2369///
2370/// # Performance
2371///
2372/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_relative_path_array_to_absolute_json`].
2373///
2374/// # Examples
2375///
2376/// ```rust,ignore
2377/// let _result = pharos_native::runtime::json_commands::object_relative_path_array_to_absolute_json("{}", std::path::Path::new("apps/dev_docs"));
2378/// ```
2379///
2380/// # Related
2381///
2382/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2383pub fn object_relative_path_array_to_absolute_json(text: &str, app_root: &Path) -> Option<String> {
2384 if app_root.as_os_str().is_empty() {
2385 return None;
2386 }
2387 let root = serde_json::from_str::<Value>(text).ok()?;
2388 let Some(items) = root.as_array() else {
2389 return Some("[]".to_owned());
2390 };
2391 let absolute: Vec<Value> = items
2392 .iter()
2393 .filter_map(|item| item.as_str())
2394 .map(|value| {
2395 if value.starts_with('/') {
2396 Value::String(value.to_owned())
2397 } else {
2398 Value::String(app_root.join(value).to_string_lossy().into_owned())
2399 }
2400 })
2401 .collect();
2402 serde_json::to_string(&absolute).ok()
2403}
2404
2405/// Handles object path array length command.
2406///
2407/// # Parameters
2408///
2409/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_path_array_length_command`].
2410/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_object_path_array_length_command`].
2411///
2412/// # Returns
2413///
2414/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_path_array_length_command`] can produce a value and [`None`] otherwise.
2415///
2416/// # Examples
2417///
2418/// ```rust,ignore
2419/// let _result = pharos_native::runtime::json_commands::handle_object_path_array_length_command("{}", std::path::Path::new("fixture.json"));
2420/// ```
2421///
2422/// # Related
2423///
2424/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2425pub fn handle_object_path_array_length_command(text: &str, path: &str) -> Option<usize> {
2426 object_path_array_length(text, path)
2427}
2428
2429/// Handles object array item JSON lines command.
2430///
2431/// # Parameters
2432///
2433/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_array_item_json_lines_command`].
2434///
2435/// # Returns
2436///
2437/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_array_item_json_lines_command`] can produce a value and [`None`] otherwise.
2438///
2439/// # Examples
2440///
2441/// ```rust,ignore
2442/// let _result = pharos_native::runtime::json_commands::handle_object_array_item_json_lines_command("{}");
2443/// ```
2444///
2445/// # Related
2446///
2447/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2448pub fn handle_object_array_item_json_lines_command(text: &str) -> Option<String> {
2449 object_array_item_json_lines(text).map(|lines| lines.join("\n"))
2450}
2451
2452/// Handles object array lines command.
2453///
2454/// # Parameters
2455///
2456/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_array_lines_command`].
2457///
2458/// # Returns
2459///
2460/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_array_lines_command`] can produce a value and [`None`] otherwise.
2461///
2462/// # Examples
2463///
2464/// ```rust,ignore
2465/// let _result = pharos_native::runtime::json_commands::handle_object_array_lines_command("{}");
2466/// ```
2467///
2468/// # Related
2469///
2470/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2471pub fn handle_object_array_lines_command(text: &str) -> Option<String> {
2472 object_array_lines(text).map(|lines| lines.join("\n"))
2473}
2474
2475/// Handles object relative path array to absolute JSON command.
2476///
2477/// # Parameters
2478///
2479/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_object_relative_path_array_to_absolute_json_command`].
2480/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_object_relative_path_array_to_absolute_json_command`].
2481///
2482/// # Returns
2483///
2484/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_relative_path_array_to_absolute_json_command`] can produce a value and [`None`] otherwise.
2485///
2486/// # Examples
2487///
2488/// ```rust,ignore
2489/// let _result = pharos_native::runtime::json_commands::handle_object_relative_path_array_to_absolute_json_command("{}", std::path::Path::new("apps/dev_docs"));
2490/// ```
2491///
2492/// # Related
2493///
2494/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2495pub fn handle_object_relative_path_array_to_absolute_json_command(
2496 text: &str,
2497 app_root: &Path,
2498) -> Option<String> {
2499 object_relative_path_array_to_absolute_json(text, app_root)
2500}
2501
2502/// Performs file compact object.
2503///
2504/// # Parameters
2505///
2506/// - `path`: Input consumed by [`crate::runtime::json_commands::file_compact_object`].
2507///
2508/// # Returns
2509///
2510/// Returns [`Some`] when [`crate::runtime::json_commands::file_compact_object`] can produce a value and [`None`] otherwise.
2511///
2512/// # Examples
2513///
2514/// ```rust,ignore
2515/// let _result = pharos_native::runtime::json_commands::file_compact_object(std::path::Path::new("fixture.json"));
2516/// ```
2517///
2518/// # Related
2519///
2520/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2521pub fn file_compact_object(path: &Path) -> Option<String> {
2522 let root = read_json_file(path)?;
2523 if !root.is_object() {
2524 return Some("{}".to_owned());
2525 }
2526 serde_json::to_string(&root).ok()
2527}
2528
2529/// Performs file field string.
2530///
2531/// # Parameters
2532///
2533/// - `path`: Input consumed by [`crate::runtime::json_commands::file_field_string`].
2534/// - `field`: Input consumed by [`crate::runtime::json_commands::file_field_string`].
2535///
2536/// # Returns
2537///
2538/// Returns [`Some`] when [`crate::runtime::json_commands::file_field_string`] can produce a value and [`None`] otherwise.
2539///
2540/// # Examples
2541///
2542/// ```rust,ignore
2543/// let _result = pharos_native::runtime::json_commands::file_field_string(std::path::Path::new("fixture.json"), "field");
2544/// ```
2545///
2546/// # Related
2547///
2548/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2549pub fn file_field_string(path: &Path, field: &str) -> Option<String> {
2550 if field.is_empty() {
2551 return None;
2552 }
2553 read_json_file(path)?
2554 .as_object()?
2555 .get(field)?
2556 .as_str()
2557 .map(ToOwned::to_owned)
2558}
2559
2560/// Performs file field number.
2561///
2562/// # Parameters
2563///
2564/// - `path`: Input consumed by [`crate::runtime::json_commands::file_field_number`].
2565/// - `field`: Input consumed by [`crate::runtime::json_commands::file_field_number`].
2566///
2567/// # Returns
2568///
2569/// Returns [`Some`] when [`crate::runtime::json_commands::file_field_number`] can produce a value and [`None`] otherwise.
2570///
2571/// # Examples
2572///
2573/// ```rust,ignore
2574/// let _result = pharos_native::runtime::json_commands::file_field_number(std::path::Path::new("fixture.json"), "field");
2575/// ```
2576///
2577/// # Related
2578///
2579/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2580pub fn file_field_number(path: &Path, field: &str) -> Option<i64> {
2581 if field.is_empty() {
2582 return None;
2583 }
2584 Some(
2585 read_json_file(path)?
2586 .as_object()?
2587 .get(field)
2588 .and_then(Value::as_i64)
2589 .unwrap_or(0),
2590 )
2591}
2592
2593/// Performs file section length.
2594///
2595/// # Parameters
2596///
2597/// - `path`: Input consumed by [`crate::runtime::json_commands::file_section_length`].
2598/// - `section`: Input consumed by [`crate::runtime::json_commands::file_section_length`].
2599///
2600/// # Returns
2601///
2602/// Returns [`Some`] when [`crate::runtime::json_commands::file_section_length`] can produce a value and [`None`] otherwise.
2603///
2604/// # Examples
2605///
2606/// ```rust,ignore
2607/// let _result = pharos_native::runtime::json_commands::file_section_length(std::path::Path::new("fixture.json"), "items");
2608/// ```
2609///
2610/// # Related
2611///
2612/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2613pub fn file_section_length(path: &Path, section: &str) -> Option<usize> {
2614 if section.is_empty() {
2615 return None;
2616 }
2617 Some(
2618 read_json_file(path)?
2619 .as_object()?
2620 .get(section)
2621 .and_then(Value::as_array)
2622 .map(Vec::len)
2623 .unwrap_or(0),
2624 )
2625}
2626
2627/// Handles file compact object command.
2628///
2629/// # Parameters
2630///
2631/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_file_compact_object_command`].
2632///
2633/// # Returns
2634///
2635/// Returns [`Some`] when [`crate::runtime::json_commands::handle_file_compact_object_command`] can produce a value and [`None`] otherwise.
2636///
2637/// # Examples
2638///
2639/// ```rust,ignore
2640/// let _result = pharos_native::runtime::json_commands::handle_file_compact_object_command(std::path::Path::new("fixture.json"));
2641/// ```
2642///
2643/// # Related
2644///
2645/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2646pub fn handle_file_compact_object_command(path: &Path) -> Option<String> {
2647 file_compact_object(path)
2648}
2649
2650/// Handles file field string command.
2651///
2652/// # Parameters
2653///
2654/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_file_field_string_command`].
2655/// - `field`: Input consumed by [`crate::runtime::json_commands::handle_file_field_string_command`].
2656///
2657/// # Returns
2658///
2659/// Returns [`Some`] when [`crate::runtime::json_commands::handle_file_field_string_command`] can produce a value and [`None`] otherwise.
2660///
2661/// # Examples
2662///
2663/// ```rust,ignore
2664/// let _result = pharos_native::runtime::json_commands::handle_file_field_string_command(std::path::Path::new("fixture.json"), "field");
2665/// ```
2666///
2667/// # Related
2668///
2669/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2670pub fn handle_file_field_string_command(path: &Path, field: &str) -> Option<String> {
2671 file_field_string(path, field)
2672}
2673
2674/// Handles file field number command.
2675///
2676/// # Parameters
2677///
2678/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_file_field_number_command`].
2679/// - `field`: Input consumed by [`crate::runtime::json_commands::handle_file_field_number_command`].
2680///
2681/// # Returns
2682///
2683/// Returns [`Some`] when [`crate::runtime::json_commands::handle_file_field_number_command`] can produce a value and [`None`] otherwise.
2684///
2685/// # Examples
2686///
2687/// ```rust,ignore
2688/// let _result = pharos_native::runtime::json_commands::handle_file_field_number_command(std::path::Path::new("fixture.json"), "field");
2689/// ```
2690///
2691/// # Related
2692///
2693/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2694pub fn handle_file_field_number_command(path: &Path, field: &str) -> Option<i64> {
2695 file_field_number(path, field)
2696}
2697
2698/// Handles file section length command.
2699///
2700/// # Parameters
2701///
2702/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_file_section_length_command`].
2703/// - `section`: Input consumed by [`crate::runtime::json_commands::handle_file_section_length_command`].
2704///
2705/// # Returns
2706///
2707/// Returns [`Some`] when [`crate::runtime::json_commands::handle_file_section_length_command`] can produce a value and [`None`] otherwise.
2708///
2709/// # Examples
2710///
2711/// ```rust,ignore
2712/// let _result = pharos_native::runtime::json_commands::handle_file_section_length_command(std::path::Path::new("fixture.json"), "items");
2713/// ```
2714///
2715/// # Related
2716///
2717/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2718pub fn handle_file_section_length_command(path: &Path, section: &str) -> Option<usize> {
2719 file_section_length(path, section)
2720}
2721
2722/// Performs manifest database contract path.
2723///
2724/// # Parameters
2725///
2726/// - `app_root`: Application root whose manifest-owned database contract path is being resolved.
2727///
2728/// # Returns
2729///
2730/// Returns [`Some`] when [`crate::runtime::json_commands::manifest_database_contract_path`] can produce a value and [`None`] otherwise.
2731///
2732/// # Performance
2733///
2734/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::manifest_database_contract_path`].
2735///
2736/// # Examples
2737///
2738/// ```rust,ignore
2739/// let _result = pharos_native::runtime::json_commands::manifest_database_contract_path(todo!());
2740/// ```
2741///
2742/// # Related
2743///
2744/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2745pub(crate) fn manifest_database_contract_path(app_root: &Path) -> Option<String> {
2746 let manifest_path = if app_root.join("pharos.app.json").exists() {
2747 app_root.join("pharos.app.json")
2748 } else {
2749 app_root.join("pharos.app.yml")
2750 };
2751 let manifest = read_json_or_yaml_file(&manifest_path)?;
2752 manifest
2753 .get("database")?
2754 .get("migration_contract")?
2755 .as_str()
2756 .filter(|path| !path.trim().is_empty())
2757 .map(ToOwned::to_owned)
2758}
2759
2760/// Declared database ownership never falls back to file state after a resolution failure.
2761pub(crate) fn manifest_declares_database(app_root: &Path) -> bool {
2762 let manifest_path = if app_root.join("pharos.app.yml").exists() {
2763 app_root.join("pharos.app.yml")
2764 } else {
2765 app_root.join("pharos.app.json")
2766 };
2767 manifest_path.exists()
2768 && read_json_or_yaml_file(&manifest_path)
2769 .is_none_or(|manifest| manifest.get("database").is_some())
2770}
2771
2772/// Performs relational runtime authority enabled.
2773fn relational_runtime_authority_enabled(app_root: &Path) -> bool {
2774 super::database::relational_runtime_authority_enabled(app_root)
2775}
2776
2777/// Performs dynamic config value.
2778///
2779/// # Parameters
2780///
2781/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_config_value`].
2782/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::dynamic_config_value`].
2783/// - `key`: Input consumed by [`crate::runtime::json_commands::dynamic_config_value`].
2784///
2785/// # Returns
2786///
2787/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_config_value`] can produce a value and [`None`] otherwise.
2788///
2789/// # Examples
2790///
2791/// ```rust,ignore
2792/// let _result = pharos_native::runtime::json_commands::dynamic_config_value(std::path::Path::new("apps/dev_docs"), todo!(), "key");
2793/// ```
2794///
2795/// # Related
2796///
2797/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2798pub fn dynamic_config_value(
2799 app_root: &Path,
2800 runtime_conf_path: Option<&Path>,
2801 key: &str,
2802) -> Option<String> {
2803 super::cache::dynamic_config_allowlist_value(app_root, runtime_conf_path, key)
2804}
2805
2806/// Performs dynamic safe upload filename.
2807///
2808/// # Parameters
2809///
2810/// - `raw_name`: Input consumed by [`crate::runtime::json_commands::dynamic_safe_upload_filename`].
2811///
2812/// # Returns
2813///
2814/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_safe_upload_filename`] can produce a value and [`None`] otherwise.
2815///
2816/// # Performance
2817///
2818/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_safe_upload_filename`].
2819///
2820/// # Examples
2821///
2822/// ```rust,ignore
2823/// let _result = pharos_native::runtime::json_commands::dynamic_safe_upload_filename(Some("file.txt"));
2824/// ```
2825///
2826/// # Related
2827///
2828/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2829pub fn dynamic_safe_upload_filename(raw_name: Option<&str>) -> Option<String> {
2830 let mut base_name = raw_name.unwrap_or("");
2831 if let Some(index) = base_name.rfind(['/', '\\']) {
2832 base_name = &base_name[index + 1..];
2833 }
2834 let mut safe_name = String::with_capacity(base_name.len());
2835 for ch in base_name.chars() {
2836 if ch.is_ascii_alphanumeric() || matches!(ch, '.' | '_' | '-') {
2837 safe_name.push(ch);
2838 } else {
2839 safe_name.push('_');
2840 }
2841 }
2842 let safe_name = safe_name
2843 .trim_start_matches(['_', '.', '-'])
2844 .trim_end_matches(['_', '.', '-'])
2845 .to_owned();
2846 Some(safe_name)
2847}
2848
2849/// Performs dynamic media disk path.
2850///
2851/// # Parameters
2852///
2853/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::dynamic_media_disk_path`].
2854/// - `media_url`: Input consumed by [`crate::runtime::json_commands::dynamic_media_disk_path`].
2855///
2856/// # Returns
2857///
2858/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_media_disk_path`] can produce a value and [`None`] otherwise.
2859///
2860/// # Examples
2861///
2862/// ```rust,ignore
2863/// let _result = pharos_native::runtime::json_commands::dynamic_media_disk_path(std::path::Path::new("build/dev_docs"), todo!());
2864/// ```
2865///
2866/// # Related
2867///
2868/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2869pub fn dynamic_media_disk_path(artifact_dir: &Path, media_url: &str) -> Option<String> {
2870 if artifact_dir.as_os_str().is_empty()
2871 || !media_url.starts_with("/media/")
2872 || media_url[7..]
2873 .split('/')
2874 .any(|part| part.is_empty() || part == ".." || part.contains(['\\', '\0']))
2875 {
2876 return None;
2877 }
2878 Some(
2879 super::config::app_media_root(artifact_dir)?
2880 .join(&media_url[7..])
2881 .to_string_lossy()
2882 .into_owned(),
2883 )
2884}
2885
2886/// Performs dynamic ensure business media tree.
2887///
2888/// # Parameters
2889///
2890/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::dynamic_ensure_business_media_tree`].
2891/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_ensure_business_media_tree`].
2892///
2893/// # Returns
2894///
2895/// Returns the computed value produced by [`crate::runtime::json_commands::dynamic_ensure_business_media_tree`].
2896///
2897/// # Performance
2898///
2899/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_ensure_business_media_tree`].
2900///
2901/// # Examples
2902///
2903/// ```rust,ignore
2904/// let _result = pharos_native::runtime::json_commands::dynamic_ensure_business_media_tree(std::path::Path::new("build/dev_docs"), "demo");
2905/// ```
2906///
2907/// # Related
2908///
2909/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2910pub fn dynamic_ensure_business_media_tree(artifact_dir: &Path, business_slug: &str) -> i32 {
2911 if artifact_dir.as_os_str().is_empty() || business_slug.is_empty() {
2912 return 64;
2913 }
2914 let Some(policy_root) = super::declarative::declarative_source_load_fixture(
2915 artifact_dir,
2916 "data/contracts/media-policy",
2917 ) else {
2918 return 69;
2919 };
2920 let Some(entries) = policy_root.get("media_subtrees").and_then(Value::as_array) else {
2921 return 69;
2922 };
2923 let Some(entry) = entries.iter().find(|entry| {
2924 entry
2925 .get("entity_type")
2926 .and_then(Value::as_str)
2927 .is_some_and(|value| value == "business")
2928 }) else {
2929 return 0;
2930 };
2931 let Some(base_template) = entry.get("base_path_template").and_then(Value::as_str) else {
2932 return 64;
2933 };
2934 let base_path = base_template.replace("{slug}", business_slug);
2935 let Some(media_root) = super::config::app_media_root(artifact_dir) else {
2936 return 64;
2937 };
2938 let Some(relative) = base_path.strip_prefix("media/") else {
2939 return 64;
2940 };
2941 let base_path = media_root.join(relative);
2942 if fs::create_dir_all(&base_path).is_err() {
2943 return 69;
2944 }
2945 if let Some(subdirs) = entry.get("subdirectories").and_then(Value::as_array) {
2946 for subdir in subdirs.iter().filter_map(Value::as_str) {
2947 if fs::create_dir_all(base_path.join(subdir)).is_err() {
2948 return 69;
2949 }
2950 }
2951 }
2952 0
2953}
2954
2955/// Performs dynamic remove business media URL file.
2956///
2957/// # Parameters
2958///
2959/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::dynamic_remove_business_media_url_file`].
2960/// - `media_url`: Input consumed by [`crate::runtime::json_commands::dynamic_remove_business_media_url_file`].
2961///
2962/// # Returns
2963///
2964/// Returns the computed value produced by [`crate::runtime::json_commands::dynamic_remove_business_media_url_file`].
2965///
2966/// # Examples
2967///
2968/// ```rust,ignore
2969/// let _result = pharos_native::runtime::json_commands::dynamic_remove_business_media_url_file(std::path::Path::new("build/dev_docs"), todo!());
2970/// ```
2971///
2972/// # Related
2973///
2974/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
2975pub fn dynamic_remove_business_media_url_file(artifact_dir: &Path, media_url: &str) -> i32 {
2976 if artifact_dir.as_os_str().is_empty() || !media_url.starts_with("/media/businesses/") {
2977 return 0;
2978 }
2979 if let Some(disk_path) = dynamic_media_disk_path(artifact_dir, media_url) {
2980 let path = Path::new(&disk_path);
2981 if path.exists() {
2982 let _ = fs::remove_file(path);
2983 }
2984 }
2985 0
2986}
2987
2988/// Performs dynamic store uploaded media.
2989///
2990/// # Parameters
2991///
2992/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media`].
2993/// - `content_type`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media`].
2994/// - `body_path`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media`].
2995/// - `field_name`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media`].
2996/// - `target_url_dir`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media`].
2997/// - `fallback_filename`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media`].
2998///
2999/// # Returns
3000///
3001/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_store_uploaded_media`] can produce a value and [`None`] otherwise.
3002///
3003/// # Examples
3004///
3005/// ```rust,ignore
3006/// let _result = pharos_native::runtime::json_commands::dynamic_store_uploaded_media(std::path::Path::new("build/dev_docs"), todo!(), todo!(), todo!(), todo!(), todo!());
3007/// ```
3008///
3009/// # Related
3010///
3011/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3012pub fn dynamic_store_uploaded_media(
3013 artifact_dir: &Path,
3014 content_type: &str,
3015 body_path: &Path,
3016 field_name: &str,
3017 target_url_dir: &str,
3018 fallback_filename: Option<&str>,
3019) -> Option<String> {
3020 dynamic_store_uploaded_media_bytes(
3021 artifact_dir,
3022 content_type,
3023 &fs::read(body_path).ok()?,
3024 field_name,
3025 target_url_dir,
3026 fallback_filename,
3027 )
3028}
3029
3030/// Stores one uploaded media part from an in-memory multipart body.
3031///
3032/// # Parameters
3033///
3034/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media_from_body`].
3035/// - `content_type`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media_from_body`].
3036/// - `body`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media_from_body`].
3037/// - `field_name`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media_from_body`].
3038/// - `target_url_dir`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media_from_body`].
3039/// - `fallback_filename`: Input consumed by [`crate::runtime::json_commands::dynamic_store_uploaded_media_from_body`].
3040///
3041/// # Returns
3042///
3043/// Returns [`Some`] when one non-empty media part was stored and [`None`] otherwise.
3044///
3045/// # Examples
3046///
3047/// ```rust,ignore
3048/// let _result = pharos_native::runtime::json_commands::dynamic_store_uploaded_media_from_body(std::path::Path::new("build/dev_docs"), todo!(), b"", todo!(), todo!(), todo!());
3049/// ```
3050///
3051/// # Related
3052///
3053/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3054pub fn dynamic_store_uploaded_media_from_body(
3055 artifact_dir: &Path,
3056 content_type: &str,
3057 body: &[u8],
3058 field_name: &str,
3059 target_url_dir: &str,
3060 fallback_filename: Option<&str>,
3061) -> Option<String> {
3062 dynamic_store_uploaded_media_bytes(
3063 artifact_dir,
3064 content_type,
3065 body,
3066 field_name,
3067 target_url_dir,
3068 fallback_filename,
3069 )
3070}
3071
3072/// Performs dynamic store uploaded media bytes.
3073fn dynamic_store_uploaded_media_bytes(
3074 artifact_dir: &Path,
3075 content_type: &str,
3076 body: &[u8],
3077 field_name: &str,
3078 target_url_dir: &str,
3079 fallback_filename: Option<&str>,
3080) -> Option<String> {
3081 if artifact_dir.as_os_str().is_empty()
3082 || content_type.is_empty()
3083 || field_name.is_empty()
3084 || target_url_dir.is_empty()
3085 {
3086 return None;
3087 }
3088 let part = multipart_find_part_from_body(body, content_type, field_name)?;
3089 if part.content.is_empty() {
3090 return None;
3091 }
3092 let mut safe_filename = dynamic_safe_upload_filename(part.filename.as_deref())?;
3093 if safe_filename.is_empty() {
3094 safe_filename = dynamic_safe_upload_filename(fallback_filename)?.to_owned();
3095 }
3096 if safe_filename.is_empty() {
3097 return None;
3098 }
3099 let target_dir = artifact_dir.join(target_url_dir.trim_start_matches('/'));
3100 fs::create_dir_all(&target_dir).ok()?;
3101 super::fs::write_file_within(
3102 artifact_dir,
3103 &target_dir.join(&safe_filename),
3104 &part.content,
3105 0o640,
3106 )
3107 .ok()?;
3108 Some(format!("{target_url_dir}/{safe_filename}"))
3109}
3110
3111/// Handles dynamic config value command.
3112///
3113/// # Parameters
3114///
3115/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_config_value_command`].
3116/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_config_value_command`].
3117/// - `key`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_config_value_command`].
3118///
3119/// # Returns
3120///
3121/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_config_value_command`] can produce a value and [`None`] otherwise.
3122///
3123/// # Examples
3124///
3125/// ```rust,ignore
3126/// let _result = pharos_native::runtime::json_commands::handle_dynamic_config_value_command(std::path::Path::new("apps/dev_docs"), todo!(), "key");
3127/// ```
3128///
3129/// # Related
3130///
3131/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3132pub fn handle_dynamic_config_value_command(
3133 app_root: &Path,
3134 runtime_conf_path: Option<&Path>,
3135 key: &str,
3136) -> Option<String> {
3137 dynamic_config_value(app_root, runtime_conf_path, key)
3138}
3139
3140/// Handles dynamic relational backend command.
3141///
3142/// # Parameters
3143///
3144/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_backend_command`].
3145/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_backend_command`].
3146///
3147/// # Returns
3148///
3149/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_relational_backend_command`] can produce a value and [`None`] otherwise.
3150///
3151/// # Examples
3152///
3153/// ```rust,ignore
3154/// let _result = pharos_native::runtime::json_commands::handle_dynamic_relational_backend_command(std::path::Path::new("apps/dev_docs"), todo!());
3155/// ```
3156///
3157/// # Related
3158///
3159/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3160pub fn handle_dynamic_relational_backend_command(
3161 app_root: &Path,
3162 runtime_conf_path: Option<&Path>,
3163) -> Option<String> {
3164 dynamic_relational_backend(app_root, runtime_conf_path)
3165}
3166
3167/// Handles dynamic safe upload filename command.
3168///
3169/// # Parameters
3170///
3171/// - `raw_name`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_safe_upload_filename_command`].
3172///
3173/// # Returns
3174///
3175/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_safe_upload_filename_command`] can produce a value and [`None`] otherwise.
3176///
3177/// # Examples
3178///
3179/// ```rust,ignore
3180/// let _result = pharos_native::runtime::json_commands::handle_dynamic_safe_upload_filename_command(Some("file.txt"));
3181/// ```
3182///
3183/// # Related
3184///
3185/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3186pub fn handle_dynamic_safe_upload_filename_command(raw_name: Option<&str>) -> Option<String> {
3187 dynamic_safe_upload_filename(raw_name)
3188}
3189
3190/// Handles dynamic media disk path command.
3191///
3192/// # Parameters
3193///
3194/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_media_disk_path_command`].
3195/// - `media_url`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_media_disk_path_command`].
3196///
3197/// # Returns
3198///
3199/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_media_disk_path_command`] can produce a value and [`None`] otherwise.
3200///
3201/// # Examples
3202///
3203/// ```rust,ignore
3204/// let _result = pharos_native::runtime::json_commands::handle_dynamic_media_disk_path_command(std::path::Path::new("build/dev_docs"), todo!());
3205/// ```
3206///
3207/// # Related
3208///
3209/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3210pub fn handle_dynamic_media_disk_path_command(
3211 artifact_dir: &Path,
3212 media_url: &str,
3213) -> Option<String> {
3214 dynamic_media_disk_path(artifact_dir, media_url)
3215}
3216
3217/// Handles dynamic ensure business media tree command.
3218///
3219/// # Parameters
3220///
3221/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_ensure_business_media_tree_command`].
3222/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_ensure_business_media_tree_command`].
3223///
3224/// # Returns
3225///
3226/// Returns the computed value produced by [`crate::runtime::json_commands::handle_dynamic_ensure_business_media_tree_command`].
3227///
3228/// # Examples
3229///
3230/// ```rust,ignore
3231/// let _result = pharos_native::runtime::json_commands::handle_dynamic_ensure_business_media_tree_command(std::path::Path::new("build/dev_docs"), "demo");
3232/// ```
3233///
3234/// # Related
3235///
3236/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3237pub fn handle_dynamic_ensure_business_media_tree_command(
3238 artifact_dir: &Path,
3239 business_slug: &str,
3240) -> i32 {
3241 dynamic_ensure_business_media_tree(artifact_dir, business_slug)
3242}
3243
3244/// Handles dynamic remove media URL file command.
3245///
3246/// # Parameters
3247///
3248/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_remove_media_url_file_command`].
3249/// - `media_url`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_remove_media_url_file_command`].
3250///
3251/// # Returns
3252///
3253/// Returns the computed value produced by [`crate::runtime::json_commands::handle_dynamic_remove_media_url_file_command`].
3254///
3255/// # Examples
3256///
3257/// ```rust,ignore
3258/// let _result = pharos_native::runtime::json_commands::handle_dynamic_remove_media_url_file_command(std::path::Path::new("build/dev_docs"), todo!());
3259/// ```
3260///
3261/// # Related
3262///
3263/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3264pub fn handle_dynamic_remove_media_url_file_command(artifact_dir: &Path, media_url: &str) -> i32 {
3265 dynamic_remove_business_media_url_file(artifact_dir, media_url)
3266}
3267
3268/// Handles dynamic store uploaded media command.
3269///
3270/// # Parameters
3271///
3272/// - `artifact_dir`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_store_uploaded_media_command`].
3273/// - `content_type`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_store_uploaded_media_command`].
3274/// - `body_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_store_uploaded_media_command`].
3275/// - `field_name`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_store_uploaded_media_command`].
3276/// - `target_url_dir`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_store_uploaded_media_command`].
3277/// - `fallback_filename`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_store_uploaded_media_command`].
3278///
3279/// # Returns
3280///
3281/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_store_uploaded_media_command`] can produce a value and [`None`] otherwise.
3282///
3283/// # Examples
3284///
3285/// ```rust,ignore
3286/// let _result = pharos_native::runtime::json_commands::handle_dynamic_store_uploaded_media_command(std::path::Path::new("build/dev_docs"), todo!(), todo!(), todo!(), todo!(), todo!());
3287/// ```
3288///
3289/// # Related
3290///
3291/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3292pub fn handle_dynamic_store_uploaded_media_command(
3293 artifact_dir: &Path,
3294 content_type: &str,
3295 body_path: &Path,
3296 field_name: &str,
3297 target_url_dir: &str,
3298 fallback_filename: Option<&str>,
3299) -> Option<String> {
3300 dynamic_store_uploaded_media(
3301 artifact_dir,
3302 content_type,
3303 body_path,
3304 field_name,
3305 target_url_dir,
3306 fallback_filename,
3307 )
3308}
3309
3310/// Returns whether safe SQL identifier.
3311///
3312/// # Parameters
3313///
3314/// - `text`: Input consumed by [`crate::runtime::json_commands::is_safe_sql_identifier`].
3315///
3316/// # Returns
3317///
3318/// Returns `true` when the condition checked by [`crate::runtime::json_commands::is_safe_sql_identifier`] holds and `false` otherwise.
3319///
3320/// # Performance
3321///
3322/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::is_safe_sql_identifier`].
3323///
3324/// # Examples
3325///
3326/// ```rust,ignore
3327/// let _result = pharos_native::runtime::json_commands::is_safe_sql_identifier("{}");
3328/// ```
3329///
3330/// # Related
3331///
3332/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3333pub fn is_safe_sql_identifier(text: &str) -> bool {
3334 !text.is_empty()
3335 && text
3336 .chars()
3337 .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
3338}
3339
3340/// Computes a SHA-256 checksum without invoking a host utility.
3341///
3342/// # Parameters
3343///
3344/// - `value`: Input consumed by [`crate::runtime::json_commands::checksum_string`].
3345///
3346/// # Returns
3347///
3348/// Returns [`Some`] when [`crate::runtime::json_commands::checksum_string`] can produce a value and [`None`] otherwise.
3349///
3350/// # Examples
3351///
3352/// ```rust,ignore
3353/// let _result = pharos_native::runtime::json_commands::checksum_string("value");
3354/// ```
3355///
3356/// # Related
3357///
3358/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3359pub fn checksum_string(value: &str) -> Option<String> {
3360 Some(migration_sha256_text(value))
3361}
3362
3363/// Performs dynamic file signature.
3364///
3365/// # Parameters
3366///
3367/// - `path`: Input consumed by [`crate::runtime::json_commands::dynamic_file_signature`].
3368///
3369/// # Returns
3370///
3371/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_file_signature`] can produce a value and [`None`] otherwise.
3372///
3373/// # Performance
3374///
3375/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_file_signature`].
3376///
3377/// # Examples
3378///
3379/// ```rust,ignore
3380/// let _result = pharos_native::runtime::json_commands::dynamic_file_signature(std::path::Path::new("fixture.json"));
3381/// ```
3382///
3383/// # Related
3384///
3385/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3386pub fn dynamic_file_signature(path: &Path) -> Option<String> {
3387 if path.as_os_str().is_empty() {
3388 return None;
3389 }
3390 if !path.exists() {
3391 return Some("missing".to_owned());
3392 }
3393 let bytes = fs::read(path).ok()?;
3394 let mut hash = 1469598103934665603u64;
3395 for value in &bytes {
3396 hash ^= u64::from(*value);
3397 hash = hash.wrapping_mul(1099511628211);
3398 }
3399 Some(format!("{hash:016x}:{}", bytes.len()))
3400}
3401
3402/// Handles dynamic file signature command.
3403///
3404/// # Parameters
3405///
3406/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_file_signature_command`].
3407///
3408/// # Returns
3409///
3410/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_file_signature_command`] can produce a value and [`None`] otherwise.
3411///
3412/// # Examples
3413///
3414/// ```rust,ignore
3415/// let _result = pharos_native::runtime::json_commands::handle_dynamic_file_signature_command(std::path::Path::new("fixture.json"));
3416/// ```
3417///
3418/// # Related
3419///
3420/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3421pub fn handle_dynamic_file_signature_command(path: &Path) -> Option<String> {
3422 dynamic_file_signature(path)
3423}
3424
3425/// Reports declared, applied, interrupted, and live-schema migration drift.
3426///
3427/// # Parameters
3428///
3429/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_migration_drift_report`].
3430/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::dynamic_migration_drift_report`].
3431/// - `app_id`: Input consumed by [`crate::runtime::json_commands::dynamic_migration_drift_report`].
3432///
3433/// # Returns
3434///
3435/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_migration_drift_report`] can produce a value and [`None`] otherwise.
3436///
3437/// # Examples
3438///
3439/// ```rust,ignore
3440/// let _result = pharos_native::runtime::json_commands::dynamic_migration_drift_report(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
3441/// ```
3442///
3443/// # Related
3444///
3445/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3446pub fn dynamic_migration_drift_report(
3447 app_root: &Path,
3448 runtime_conf_path: Option<&Path>,
3449 app_id: Option<&str>,
3450) -> Option<Value> {
3451 super::database::dynamic_migration_drift_report(app_root, runtime_conf_path, app_id)
3452}
3453
3454/// Handles dynamic relational runtime export JSON command.
3455///
3456/// # Parameters
3457///
3458/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_export_json_command`].
3459/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_export_json_command`].
3460/// - `app_id`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_export_json_command`].
3461///
3462/// # Returns
3463///
3464/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_relational_runtime_export_json_command`] can produce a value and [`None`] otherwise.
3465///
3466/// # Examples
3467///
3468/// ```rust,ignore
3469/// let _result = pharos_native::runtime::json_commands::handle_dynamic_relational_runtime_export_json_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
3470/// ```
3471///
3472/// # Related
3473///
3474/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3475pub fn handle_dynamic_relational_runtime_export_json_command(
3476 app_root: &Path,
3477 runtime_conf_path: Option<&Path>,
3478 app_id: Option<&str>,
3479) -> Option<String> {
3480 dynamic_relational_runtime_export_json(app_root, runtime_conf_path, app_id)
3481}
3482
3483/// Handles dynamic relational runtime audit count command.
3484///
3485/// # Parameters
3486///
3487/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_audit_count_command`].
3488/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_audit_count_command`].
3489/// - `app_id`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_audit_count_command`].
3490///
3491/// # Returns
3492///
3493/// Returns [`Some`] when [`crate::runtime::json_commands::handle_dynamic_relational_runtime_audit_count_command`] can produce a value and [`None`] otherwise.
3494///
3495/// # Examples
3496///
3497/// ```rust,ignore
3498/// let _result = pharos_native::runtime::json_commands::handle_dynamic_relational_runtime_audit_count_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
3499/// ```
3500///
3501/// # Related
3502///
3503/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3504pub fn handle_dynamic_relational_runtime_audit_count_command(
3505 app_root: &Path,
3506 runtime_conf_path: Option<&Path>,
3507 app_id: Option<&str>,
3508) -> Option<String> {
3509 dynamic_relational_runtime_audit_count(app_root, runtime_conf_path, app_id)
3510}
3511
3512/// Handles dynamic relational runtime import state command.
3513///
3514/// # Parameters
3515///
3516/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_import_state_command`].
3517/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_import_state_command`].
3518/// - `app_id`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_import_state_command`].
3519/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_import_state_command`].
3520/// - `expected_audit_count`: Input consumed by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_import_state_command`].
3521///
3522/// # Returns
3523///
3524/// Returns the computed value produced by [`crate::runtime::json_commands::handle_dynamic_relational_runtime_import_state_command`].
3525///
3526/// # Examples
3527///
3528/// ```rust,ignore
3529/// let _result = pharos_native::runtime::json_commands::handle_dynamic_relational_runtime_import_state_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
3530/// ```
3531///
3532/// # Related
3533///
3534/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3535pub fn handle_dynamic_relational_runtime_import_state_command(
3536 app_root: &Path,
3537 runtime_conf_path: Option<&Path>,
3538 app_id: Option<&str>,
3539 state_path: &Path,
3540 expected_audit_count: Option<&str>,
3541) -> i32 {
3542 dynamic_relational_runtime_import_state(
3543 app_root,
3544 runtime_conf_path,
3545 app_id,
3546 state_path,
3547 expected_audit_count,
3548 )
3549}
3550
3551/// Performs file normalize runtime state.
3552///
3553/// # Parameters
3554///
3555/// - `path`: Input consumed by [`crate::runtime::json_commands::file_normalize_runtime_state`].
3556/// - `schema_value`: Input consumed by [`crate::runtime::json_commands::file_normalize_runtime_state`].
3557/// - `sections_csv`: Input consumed by [`crate::runtime::json_commands::file_normalize_runtime_state`].
3558///
3559/// # Returns
3560///
3561/// Returns [`Some`] when [`crate::runtime::json_commands::file_normalize_runtime_state`] can produce a value and [`None`] otherwise.
3562///
3563/// # Performance
3564///
3565/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::file_normalize_runtime_state`].
3566///
3567/// # Examples
3568///
3569/// ```rust,ignore
3570/// let _result = pharos_native::runtime::json_commands::file_normalize_runtime_state(std::path::Path::new("fixture.json"), todo!(), todo!());
3571/// ```
3572///
3573/// # Related
3574///
3575/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3576pub fn file_normalize_runtime_state(
3577 path: &Path,
3578 schema_value: Option<&str>,
3579 sections_csv: Option<&str>,
3580) -> Option<String> {
3581 if path.as_os_str().is_empty() {
3582 return None;
3583 }
3584 let mut root = read_json_file(path)?;
3585 let object = root.as_object_mut()?;
3586 if let Some(schema_value) = schema_value.filter(|value| !value.is_empty())
3587 && !object.contains_key("schema")
3588 {
3589 object.insert("schema".to_owned(), Value::String(schema_value.to_owned()));
3590 }
3591 if let Some(sections_csv) = sections_csv.filter(|value| !value.is_empty()) {
3592 for token in sections_csv.split(',') {
3593 let key = token.trim();
3594 if !key.is_empty() && !object.contains_key(key) {
3595 object.insert(key.to_owned(), Value::Array(Vec::new()));
3596 }
3597 }
3598 }
3599 serde_json::to_string(&root).ok()
3600}
3601
3602/// Performs request checkbox ID array JSON.
3603///
3604/// # Parameters
3605///
3606/// - `text`: Input consumed by [`crate::runtime::json_commands::request_checkbox_id_array_json`].
3607/// - `prefix`: Input consumed by [`crate::runtime::json_commands::request_checkbox_id_array_json`].
3608///
3609/// # Returns
3610///
3611/// Returns [`Some`] when [`crate::runtime::json_commands::request_checkbox_id_array_json`] can produce a value and [`None`] otherwise.
3612///
3613/// # Performance
3614///
3615/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_checkbox_id_array_json`].
3616///
3617/// # Examples
3618///
3619/// ```rust,ignore
3620/// let _result = pharos_native::runtime::json_commands::request_checkbox_id_array_json("{}", todo!());
3621/// ```
3622///
3623/// # Related
3624///
3625/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3626pub fn request_checkbox_id_array_json(text: &str, prefix: &str) -> Option<String> {
3627 if prefix.is_empty() {
3628 return None;
3629 }
3630 let mut values: Vec<i64> = Vec::new();
3631 for pair in text.split('&') {
3632 let Some((key, value)) = pair.split_once('=') else {
3633 continue;
3634 };
3635 if !key.starts_with(prefix) || value.is_empty() {
3636 continue;
3637 }
3638 let suffix = &key[prefix.len()..];
3639 if suffix.is_empty() || !suffix.chars().all(|ch| ch.is_ascii_digit()) {
3640 continue;
3641 }
3642 let Some(parsed) = value.parse::<i64>().ok() else {
3643 continue;
3644 };
3645 if !values.contains(&parsed) && values.len() < 512 {
3646 values.push(parsed);
3647 }
3648 }
3649 serde_json::to_string(&values).ok()
3650}
3651
3652/// Performs request lines to booking options JSON.
3653///
3654/// # Parameters
3655///
3656/// - `text`: Input consumed by [`crate::runtime::json_commands::request_lines_to_booking_options_json`].
3657///
3658/// # Returns
3659///
3660/// Returns [`Some`] when [`crate::runtime::json_commands::request_lines_to_booking_options_json`] can produce a value and [`None`] otherwise.
3661///
3662/// # Performance
3663///
3664/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::request_lines_to_booking_options_json`].
3665///
3666/// # Examples
3667///
3668/// ```rust,ignore
3669/// let _result = pharos_native::runtime::json_commands::request_lines_to_booking_options_json("{}");
3670/// ```
3671///
3672/// # Related
3673///
3674/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3675pub fn request_lines_to_booking_options_json(text: &str) -> Option<String> {
3676 let mut rows = Vec::new();
3677 for line in text.lines() {
3678 let trimmed = line.trim();
3679 if trimmed.is_empty() {
3680 continue;
3681 }
3682 let mut parts = trimmed.splitn(3, '|');
3683 let label = parts.next().unwrap_or_default().trim();
3684 let duration_text = parts.next().unwrap_or_default().trim();
3685 let cost = parts.next().unwrap_or_default().trim();
3686 if label.is_empty() && duration_text.is_empty() && cost.is_empty() {
3687 continue;
3688 }
3689 let duration =
3690 if !duration_text.is_empty() && duration_text.chars().all(|ch| ch.is_ascii_digit()) {
3691 duration_text.parse::<u64>().ok().unwrap_or(0)
3692 } else {
3693 0
3694 };
3695 rows.push(serde_json::json!({
3696 "label": label,
3697 "duration_minutes": duration,
3698 "cost": cost,
3699 }));
3700 }
3701 serde_json::to_string(&rows).ok()
3702}
3703
3704/// Handles request checkbox ID array JSON command.
3705///
3706/// # Parameters
3707///
3708/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_request_checkbox_id_array_json_command`].
3709/// - `prefix`: Input consumed by [`crate::runtime::json_commands::handle_request_checkbox_id_array_json_command`].
3710///
3711/// # Returns
3712///
3713/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_checkbox_id_array_json_command`] can produce a value and [`None`] otherwise.
3714///
3715/// # Examples
3716///
3717/// ```rust,ignore
3718/// let _result = pharos_native::runtime::json_commands::handle_request_checkbox_id_array_json_command("{}", todo!());
3719/// ```
3720///
3721/// # Related
3722///
3723/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3724pub fn handle_request_checkbox_id_array_json_command(text: &str, prefix: &str) -> Option<String> {
3725 request_checkbox_id_array_json(text, prefix)
3726}
3727
3728/// Handles request lines to booking options JSON command.
3729///
3730/// # Parameters
3731///
3732/// - `text`: Input consumed by [`crate::runtime::json_commands::handle_request_lines_to_booking_options_json_command`].
3733///
3734/// # Returns
3735///
3736/// Returns [`Some`] when [`crate::runtime::json_commands::handle_request_lines_to_booking_options_json_command`] can produce a value and [`None`] otherwise.
3737///
3738/// # Examples
3739///
3740/// ```rust,ignore
3741/// let _result = pharos_native::runtime::json_commands::handle_request_lines_to_booking_options_json_command("{}");
3742/// ```
3743///
3744/// # Related
3745///
3746/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3747pub fn handle_request_lines_to_booking_options_json_command(text: &str) -> Option<String> {
3748 request_lines_to_booking_options_json(text)
3749}
3750
3751/// Handles object merge command.
3752///
3753/// # Parameters
3754///
3755/// - `base_json`: Input consumed by [`crate::runtime::json_commands::handle_object_merge_command`].
3756/// - `overlay_json`: Input consumed by [`crate::runtime::json_commands::handle_object_merge_command`].
3757///
3758/// # Returns
3759///
3760/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_merge_command`] can produce a value and [`None`] otherwise.
3761///
3762/// # Examples
3763///
3764/// ```rust,ignore
3765/// let _result = pharos_native::runtime::json_commands::handle_object_merge_command("{}", "{}");
3766/// ```
3767///
3768/// # Related
3769///
3770/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3771pub fn handle_object_merge_command(base_json: &str, overlay_json: &str) -> Option<String> {
3772 object_merge(base_json, overlay_json)
3773}
3774
3775/// Handles object build command.
3776///
3777/// # Parameters
3778///
3779/// - `ops`: Input consumed by [`crate::runtime::json_commands::handle_object_build_command`].
3780///
3781/// # Returns
3782///
3783/// Returns [`Some`] when [`crate::runtime::json_commands::handle_object_build_command`] can produce a value and [`None`] otherwise.
3784///
3785/// # Examples
3786///
3787/// ```rust,ignore
3788/// let _result = pharos_native::runtime::json_commands::handle_object_build_command(&[]);
3789/// ```
3790///
3791/// # Related
3792///
3793/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3794pub fn handle_object_build_command(ops: &[ObjectBuildOp]) -> Option<String> {
3795 object_build(ops)
3796}
3797
3798/// Performs object merge.
3799///
3800/// # Parameters
3801///
3802/// - `base_json`: Input consumed by [`crate::runtime::json_commands::object_merge`].
3803/// - `overlay_json`: Input consumed by [`crate::runtime::json_commands::object_merge`].
3804///
3805/// # Returns
3806///
3807/// Returns [`Some`] when [`crate::runtime::json_commands::object_merge`] can produce a value and [`None`] otherwise.
3808///
3809/// # Performance
3810///
3811/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_merge`].
3812///
3813/// # Examples
3814///
3815/// ```rust,ignore
3816/// let _result = pharos_native::runtime::json_commands::object_merge("{}", "{}");
3817/// ```
3818///
3819/// # Related
3820///
3821/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3822pub fn object_merge(base_json: &str, overlay_json: &str) -> Option<String> {
3823 let mut base = serde_json::from_str::<Value>(base_json).ok()?;
3824 let overlay = serde_json::from_str::<Value>(overlay_json).ok()?;
3825 let base_obj = base.as_object_mut()?;
3826 let overlay_obj = overlay.as_object()?;
3827 for (key, value) in overlay_obj {
3828 base_obj.insert(key.clone(), value.clone());
3829 }
3830 serde_json::to_string(&base).ok()
3831}
3832
3833#[derive(Debug, Clone, Eq, PartialEq)]
3834/// Public enum `ObjectBuildOp` used by the runtime JSON-command surface.
3835///
3836/// # Variants
3837///
3838/// - `String`: Supported `ObjectBuildOp` variant.
3839/// - `Number`: Supported `ObjectBuildOp` variant.
3840/// - `Bool`: Supported `ObjectBuildOp` variant.
3841/// - `Null`: Supported `ObjectBuildOp` variant.
3842/// - `Json`: Supported `ObjectBuildOp` variant.
3843///
3844/// # Examples
3845///
3846/// ```rust,ignore
3847/// let _ = pharos_native::runtime::json_commands::ObjectBuildOp::String("title".to_owned(), "Example".to_owned());
3848/// ```
3849///
3850/// # Related
3851///
3852/// - [`crate::runtime::json_commands`] for constructors, helpers, and neighboring runtime types.
3853pub enum ObjectBuildOp {
3854 String(String, String),
3855 Number(String, i64),
3856 Bool(String, bool),
3857 Null(String),
3858 Json(String, String),
3859}
3860
3861/// Performs object build.
3862///
3863/// # Parameters
3864///
3865/// - `ops`: Input consumed by [`crate::runtime::json_commands::object_build`].
3866///
3867/// # Returns
3868///
3869/// Returns [`Some`] when [`crate::runtime::json_commands::object_build`] can produce a value and [`None`] otherwise.
3870///
3871/// # Performance
3872///
3873/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_build`].
3874///
3875/// # Examples
3876///
3877/// ```rust,ignore
3878/// let _result = pharos_native::runtime::json_commands::object_build(&[]);
3879/// ```
3880///
3881/// # Related
3882///
3883/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3884pub fn object_build(ops: &[ObjectBuildOp]) -> Option<String> {
3885 let mut object = serde_json::Map::new();
3886 for op in ops {
3887 match op {
3888 ObjectBuildOp::String(key, value) => {
3889 object.insert(key.clone(), Value::String(value.clone()));
3890 }
3891 ObjectBuildOp::Number(key, value) => {
3892 object.insert(key.clone(), serde_json::json!(*value));
3893 }
3894 ObjectBuildOp::Bool(key, value) => {
3895 object.insert(key.clone(), serde_json::json!(*value));
3896 }
3897 ObjectBuildOp::Null(key) => {
3898 object.insert(key.clone(), Value::Null);
3899 }
3900 ObjectBuildOp::Json(key, value) => {
3901 object.insert(key.clone(), serde_json::from_str::<Value>(value).ok()?);
3902 }
3903 }
3904 }
3905 serde_json::to_string(&Value::Object(object)).ok()
3906}
3907
3908/// Performs template reference contains unsafe char.
3909fn template_reference_contains_unsafe_char(text: &str) -> bool {
3910 text.bytes().any(|ch| ch < 0x20 || ch == 0x7f)
3911}
3912
3913/// Performs template reference has scheme.
3914fn template_reference_has_scheme(text: &str) -> bool {
3915 let mut chars = text.chars();
3916 let Some(first) = chars.next() else {
3917 return false;
3918 };
3919 if !first.is_ascii_alphabetic() {
3920 return false;
3921 }
3922 for ch in chars {
3923 if ch == ':' {
3924 return true;
3925 }
3926 if !(ch.is_ascii_alphanumeric() || matches!(ch, '+' | '-' | '.')) {
3927 return false;
3928 }
3929 }
3930 false
3931}
3932
3933/// Performs template path reference allowed.
3934///
3935/// # Parameters
3936///
3937/// - `text`: Input consumed by [`crate::runtime::json_commands::template_path_reference_allowed`].
3938///
3939/// # Returns
3940///
3941/// Returns `true` when the condition checked by [`crate::runtime::json_commands::template_path_reference_allowed`] holds and `false` otherwise.
3942///
3943/// # Examples
3944///
3945/// ```rust,ignore
3946/// let _result = pharos_native::runtime::json_commands::template_path_reference_allowed("{}");
3947/// ```
3948///
3949/// # Related
3950///
3951/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3952pub fn template_path_reference_allowed(text: &str) -> bool {
3953 if text.is_empty() {
3954 return true;
3955 }
3956 !template_reference_contains_unsafe_char(text) && !template_reference_has_scheme(text)
3957}
3958
3959/// Performs template URL reference allowed.
3960///
3961/// # Parameters
3962///
3963/// - `text`: Input consumed by [`crate::runtime::json_commands::template_url_reference_allowed`].
3964///
3965/// # Returns
3966///
3967/// Returns `true` when the condition checked by [`crate::runtime::json_commands::template_url_reference_allowed`] holds and `false` otherwise.
3968///
3969/// # Examples
3970///
3971/// ```rust,ignore
3972/// let _result = pharos_native::runtime::json_commands::template_url_reference_allowed("{}");
3973/// ```
3974///
3975/// # Related
3976///
3977/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
3978pub fn template_url_reference_allowed(text: &str) -> bool {
3979 if text.is_empty() {
3980 return true;
3981 }
3982 if template_reference_contains_unsafe_char(text) {
3983 return false;
3984 }
3985 if text.starts_with("https://")
3986 || text.starts_with("http://")
3987 || text.starts_with("mailto:")
3988 || text.starts_with("tel:")
3989 {
3990 return true;
3991 }
3992 !template_reference_has_scheme(text)
3993}
3994
3995/// Performs join base relative path.
3996///
3997/// # Parameters
3998///
3999/// - `base_path`: Input consumed by [`crate::runtime::json_commands::join_base_relative_path`].
4000/// - `relative_path`: Input consumed by [`crate::runtime::json_commands::join_base_relative_path`].
4001///
4002/// # Returns
4003///
4004/// Returns [`Some`] when [`crate::runtime::json_commands::join_base_relative_path`] can produce a value and [`None`] otherwise.
4005///
4006/// # Examples
4007///
4008/// ```rust,ignore
4009/// let _result = pharos_native::runtime::json_commands::join_base_relative_path("/app", "page");
4010/// ```
4011///
4012/// # Related
4013///
4014/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4015pub fn join_base_relative_path(base_path: &str, relative_path: &str) -> Option<String> {
4016 if base_path.is_empty() || relative_path.is_empty() {
4017 return None;
4018 }
4019 Some(format!("{base_path}/{relative_path}"))
4020}
4021
4022/// Renders file template.
4023///
4024/// # Parameters
4025///
4026/// - `template_file`: Input consumed by [`crate::runtime::json_commands::render_file_template`].
4027/// - `params_root`: Input consumed by [`crate::runtime::json_commands::render_file_template`].
4028///
4029/// # Returns
4030///
4031/// Returns [`Some`] when [`crate::runtime::json_commands::render_file_template`] can produce a value and [`None`] otherwise.
4032///
4033/// # Performance
4034///
4035/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::render_file_template`].
4036///
4037/// # Examples
4038///
4039/// ```rust,ignore
4040/// let _result = pharos_native::runtime::json_commands::render_file_template(std::path::Path::new("template.html"), &serde_json::json!({}));
4041/// ```
4042///
4043/// # Related
4044///
4045/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4046pub fn render_file_template(template_file: &Path, params_root: &Value) -> Option<String> {
4047 if template_file.as_os_str().is_empty() || !params_root.is_object() {
4048 return None;
4049 }
4050 let text = fs::read_to_string(template_file).ok()?;
4051 let text = if text.contains("{include:") {
4052 let root = template_file
4053 .ancestors()
4054 .find(|path| path.file_name().is_some_and(|name| name == "templates"))?
4055 .canonicalize()
4056 .ok()?;
4057 expand_template_includes(&root, &text, 0, &mut 64)?
4058 } else {
4059 text
4060 };
4061 render_template_text_with_default_kind(&text, params_root, "text")
4062}
4063
4064/// Expands app-owned HTML fragments before binding values, with bounded depth,
4065/// reads and output. Canonical paths must stay within the templates directory.
4066fn expand_template_includes(
4067 root: &Path,
4068 text: &str,
4069 depth: usize,
4070 remaining: &mut usize,
4071) -> Option<String> {
4072 const LIMIT: usize = 1024 * 1024;
4073 if depth > 16 || text.len() > LIMIT {
4074 return None;
4075 }
4076 let mut output = String::new();
4077 let mut cursor = 0;
4078 while let Some(offset) = text[cursor..].find("{include:") {
4079 let start = cursor + offset;
4080 let path_start = start + "{include:".len();
4081 let end = text[path_start..].find('}')? + path_start;
4082 let relative = Path::new(&text[path_start..end]);
4083 if !relative
4084 .components()
4085 .all(|part| matches!(part, std::path::Component::Normal(_)))
4086 || relative.extension().is_none_or(|ext| ext != "html")
4087 {
4088 return None;
4089 }
4090 *remaining = remaining.checked_sub(1)?;
4091 let path = root.join(relative).canonicalize().ok()?;
4092 if !path.starts_with(root) || fs::metadata(&path).ok()?.len() > LIMIT as u64 {
4093 return None;
4094 }
4095 let child =
4096 expand_template_includes(root, &fs::read_to_string(path).ok()?, depth + 1, remaining)?;
4097 if output
4098 .len()
4099 .checked_add(start - cursor)?
4100 .checked_add(child.len())?
4101 > LIMIT
4102 {
4103 return None;
4104 }
4105 output.push_str(&text[cursor..start]);
4106 output.push_str(&child);
4107 cursor = end + 1;
4108 }
4109 if output.len().checked_add(text.len() - cursor)? > LIMIT {
4110 return None;
4111 }
4112 output.push_str(&text[cursor..]);
4113 Some(output)
4114}
4115
4116/// Performs HTML escape text.
4117///
4118/// # Parameters
4119///
4120/// - `text`: Input consumed by [`crate::runtime::json_commands::html_escape_text`].
4121///
4122/// # Returns
4123///
4124/// Returns the computed value produced by [`crate::runtime::json_commands::html_escape_text`].
4125///
4126/// # Examples
4127///
4128/// ```rust,ignore
4129/// let _result = pharos_native::runtime::json_commands::html_escape_text("{}");
4130/// ```
4131///
4132/// # Related
4133///
4134/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4135pub fn html_escape_text(text: &str) -> String {
4136 text.replace('&', "&")
4137 .replace('<', "<")
4138 .replace('>', ">")
4139 .replace('"', """)
4140 .replace('\'', "'")
4141}
4142
4143/// Performs YAML to JSON value.
4144fn yaml_to_json_value(value: &saphyr::Yaml<'_>) -> Option<Value> {
4145 match value {
4146 saphyr::Yaml::BadValue | saphyr::Yaml::Alias(_) | saphyr::Yaml::Representation(_, _, _) => {
4147 None
4148 }
4149 saphyr::Yaml::Tagged(_, node) => yaml_to_json_value(node),
4150 saphyr::Yaml::Sequence(items) => items
4151 .iter()
4152 .map(yaml_to_json_value)
4153 .collect::<Option<Vec<_>>>()
4154 .map(Value::Array),
4155 saphyr::Yaml::Mapping(entries) => {
4156 let mut map = serde_json::Map::new();
4157 for (key, entry) in entries {
4158 let key = if let Some(text) = key.as_str() {
4159 text.to_owned()
4160 } else if let Some(number) = key.as_integer() {
4161 number.to_string()
4162 } else if let Some(flag) = key.as_bool() {
4163 flag.to_string()
4164 } else {
4165 let number = key.clone().into_floating_point()?;
4166 number.to_string()
4167 };
4168 map.insert(key, yaml_to_json_value(entry)?);
4169 }
4170 Some(Value::Object(map))
4171 }
4172 saphyr::Yaml::Value(scalar) => match scalar {
4173 saphyr::Scalar::Null => Some(Value::Null),
4174 saphyr::Scalar::Boolean(flag) => Some(Value::Bool(*flag)),
4175 saphyr::Scalar::Integer(number) => Some(Value::Number((*number).into())),
4176 saphyr::Scalar::FloatingPoint(number) => {
4177 serde_json::Number::from_f64((*number).into()).map(Value::Number)
4178 }
4179 saphyr::Scalar::String(text) => Some(Value::String(text.to_string())),
4180 },
4181 }
4182}
4183
4184/// Reads JSON or YAML file.
4185fn read_json_or_yaml_file(path: &Path) -> Option<Value> {
4186 match path.extension().and_then(|value| value.to_str()) {
4187 Some("yml") | Some("yaml") => {
4188 let text = fs::read_to_string(path).ok()?;
4189 pharos_runtime_core::yaml::validate_unique_mapping_keys(&text).ok()?;
4190 let documents = saphyr::Yaml::load_from_str(&text).ok()?;
4191 yaml_to_json_value(&documents.into_iter().next()?)
4192 }
4193 _ => read_json_file(path),
4194 }
4195}
4196
4197/// Performs app root relative path.
4198fn app_root_relative_path(app_root: &Path, relative_path: &str) -> Option<String> {
4199 if app_root.as_os_str().is_empty() || relative_path.is_empty() {
4200 return None;
4201 }
4202 Some(app_root.join(relative_path).to_string_lossy().into_owned())
4203}
4204
4205/// Performs runtime find declared fragment relative path in directory.
4206fn runtime_find_declared_fragment_relative_path_in_directory(
4207 absolute_dir: &Path,
4208 relative_dir: &str,
4209 stem: &str,
4210 suffixes: &[&str],
4211) -> Option<String> {
4212 if absolute_dir.as_os_str().is_empty() || stem.is_empty() {
4213 return None;
4214 }
4215 let entries = fs::read_dir(absolute_dir).ok()?;
4216 for entry in entries {
4217 let entry = entry.ok()?;
4218 let path = entry.path();
4219 let file_name = entry.file_name();
4220 let file_name = file_name.to_string_lossy();
4221 if file_name == "." || file_name == ".." {
4222 continue;
4223 }
4224 let relative_child = if relative_dir.is_empty() {
4225 file_name.to_string()
4226 } else {
4227 format!("{relative_dir}/{file_name}")
4228 };
4229 if path.is_dir() {
4230 if let Some(found) = runtime_find_declared_fragment_relative_path_in_directory(
4231 &path,
4232 &relative_child,
4233 stem,
4234 suffixes,
4235 ) {
4236 return Some(found);
4237 }
4238 continue;
4239 }
4240 for suffix in suffixes {
4241 if let Some(candidate_stem) = file_name.strip_suffix(suffix)
4242 && candidate_stem == stem
4243 {
4244 return Some(relative_child);
4245 }
4246 }
4247 }
4248 None
4249}
4250
4251/// Finds one bounded page declaration by its explicit `page_id` field.
4252fn declared_page_relative_path_by_id(app_root: &Path, page_id: &str) -> Option<String> {
4253 const MAX_PAGE_DECLARATION_FILES: usize = 4_096;
4254 const MAX_PAGE_DECLARATION_DEPTH: usize = 32;
4255 let templates_root = app_root.join("templates");
4256 let mut directories = vec![(templates_root, 0_usize)];
4257 let mut matching_path = None;
4258 let mut visited_files = 0_usize;
4259
4260 while let Some((directory, depth)) = directories.pop() {
4261 if depth > MAX_PAGE_DECLARATION_DEPTH {
4262 return None;
4263 }
4264 let mut entries = fs::read_dir(&directory)
4265 .ok()?
4266 .collect::<Result<Vec<_>, _>>()
4267 .ok()?;
4268 entries.sort_by_key(|entry| entry.file_name());
4269 for entry in entries {
4270 let path = entry.path();
4271 let metadata = fs::symlink_metadata(&path).ok()?;
4272 if metadata.file_type().is_symlink() {
4273 return None;
4274 }
4275 if metadata.is_dir() {
4276 directories.push((path, depth + 1));
4277 continue;
4278 }
4279 if !metadata.is_file() {
4280 continue;
4281 }
4282 visited_files += 1;
4283 if visited_files > MAX_PAGE_DECLARATION_FILES {
4284 return None;
4285 }
4286 let file_name = path.file_name()?.to_str()?;
4287 if ![".page.json", ".page.yml", ".page.yaml"]
4288 .iter()
4289 .any(|suffix| file_name.ends_with(suffix))
4290 {
4291 continue;
4292 }
4293 let declaration = read_json_or_yaml_file(&path)?;
4294 if declaration.get("page_id").and_then(Value::as_str) != Some(page_id) {
4295 continue;
4296 }
4297 let relative = path
4298 .strip_prefix(app_root)
4299 .ok()?
4300 .to_string_lossy()
4301 .into_owned();
4302 if matching_path.replace(relative).is_some() {
4303 return None;
4304 }
4305 }
4306 }
4307 matching_path
4308}
4309
4310/// Resolves a page declaration by conventional filename or explicit `page_id`.
4311///
4312/// # Parameters
4313///
4314/// - `app_root`: Input consumed by [`crate::runtime::json_commands::declared_page_relative_path`].
4315/// - `page_id`: Input consumed by [`crate::runtime::json_commands::declared_page_relative_path`].
4316///
4317/// # Returns
4318///
4319/// Returns [`Some`] when [`crate::runtime::json_commands::declared_page_relative_path`] can produce a value and [`None`] otherwise.
4320///
4321/// # Performance
4322///
4323/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::declared_page_relative_path`].
4324///
4325/// # Examples
4326///
4327/// ```rust,ignore
4328/// let _result = pharos_native::runtime::json_commands::declared_page_relative_path(std::path::Path::new("apps/dev_docs"), "home");
4329/// ```
4330///
4331/// # Related
4332///
4333/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4334pub fn declared_page_relative_path(app_root: &Path, page_id: &str) -> Option<String> {
4335 if app_root.as_os_str().is_empty() || page_id.is_empty() {
4336 return None;
4337 }
4338 let direct_patterns = [
4339 format!("templates/{page_id}.page.json"),
4340 format!("templates/{page_id}.page.yml"),
4341 format!("templates/{page_id}/{page_id}.page.json"),
4342 format!("templates/{page_id}/{page_id}.page.yml"),
4343 ];
4344 for relative in direct_patterns {
4345 if app_root.join(&relative).exists() {
4346 return Some(relative);
4347 }
4348 }
4349 runtime_find_declared_fragment_relative_path_in_directory(
4350 &app_root.join("templates"),
4351 "templates",
4352 &format!("{page_id}.page"),
4353 &[".json", ".yml", ".yaml"],
4354 )
4355 .or_else(|| declared_page_relative_path_by_id(app_root, page_id))
4356}
4357
4358/// Performs template declared page contract.
4359///
4360/// # Parameters
4361///
4362/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_declared_page_contract`].
4363/// - `page_id`: Input consumed by [`crate::runtime::json_commands::template_declared_page_contract`].
4364///
4365/// # Returns
4366///
4367/// Returns [`Some`] when [`crate::runtime::json_commands::template_declared_page_contract`] can produce a value and [`None`] otherwise.
4368///
4369/// # Examples
4370///
4371/// ```rust,ignore
4372/// let _result = pharos_native::runtime::json_commands::template_declared_page_contract(std::path::Path::new("apps/dev_docs"), "home");
4373/// ```
4374///
4375/// # Related
4376///
4377/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4378pub fn template_declared_page_contract(app_root: &Path, page_id: &str) -> Option<String> {
4379 let relative_path = declared_page_relative_path(app_root, page_id)?;
4380 let spec_path = app_root.join(&relative_path);
4381 let root = read_json_or_yaml_file(&spec_path)?;
4382 let template_rel = root
4383 .get("template_file")
4384 .and_then(Value::as_str)
4385 .unwrap_or_default();
4386 let title = root
4387 .get("title")
4388 .and_then(Value::as_str)
4389 .unwrap_or_default();
4390 let layout_id = root
4391 .get("layout_id")
4392 .and_then(Value::as_str)
4393 .unwrap_or_default();
4394 serde_json::to_string(&serde_json::json!({
4395 "spec_path": spec_path.to_string_lossy(),
4396 "template_path": app_root.join(template_rel).to_string_lossy(),
4397 "title": title,
4398 "layout_id": layout_id,
4399 }))
4400 .ok()
4401}
4402
4403/// Performs template render path.
4404///
4405/// # Parameters
4406///
4407/// - `template_path`: Input consumed by [`crate::runtime::json_commands::template_render_path`].
4408/// - `params_root`: Input consumed by [`crate::runtime::json_commands::template_render_path`].
4409///
4410/// # Returns
4411///
4412/// Returns [`Some`] when [`crate::runtime::json_commands::template_render_path`] can produce a value and [`None`] otherwise.
4413///
4414/// # Performance
4415///
4416/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::template_render_path`].
4417///
4418/// # Examples
4419///
4420/// ```rust,ignore
4421/// let _result = pharos_native::runtime::json_commands::template_render_path("template.html", &serde_json::json!({}));
4422/// ```
4423///
4424/// # Related
4425///
4426/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4427pub fn template_render_path(template_path: &str, params_root: &Value) -> Option<String> {
4428 if template_path.is_empty() || !params_root.is_object() {
4429 return None;
4430 }
4431 let params = params_root.as_object()?;
4432 let mut out = String::with_capacity(template_path.len() + 32);
4433 let mut cursor = 0usize;
4434 while let Some(relative_open) = template_path[cursor..].find('{') {
4435 let open = cursor + relative_open;
4436 out.push_str(&template_path[cursor..open]);
4437 let key_start = open + 1;
4438 let relative_close = template_path[key_start..].find('}')?;
4439 let close = key_start + relative_close;
4440 let key = &template_path[key_start..close];
4441 let value = params.get(key)?;
4442 let rendered = match value {
4443 Value::String(text) => text.clone(),
4444 Value::Number(number) => number.to_string(),
4445 Value::Bool(flag) => flag.to_string(),
4446 Value::Null => String::new(),
4447 _ => serde_json::to_string(value).ok()?,
4448 };
4449 if !template_path_reference_allowed(&rendered) {
4450 return None;
4451 }
4452 out.push_str(&rendered);
4453 cursor = close + 1;
4454 }
4455 out.push_str(&template_path[cursor..]);
4456 Some(out)
4457}
4458
4459/// Performs template render file.
4460///
4461/// # Parameters
4462///
4463/// - `template_file`: Input consumed by [`crate::runtime::json_commands::template_render_file`].
4464/// - `params_root`: Input consumed by [`crate::runtime::json_commands::template_render_file`].
4465///
4466/// # Returns
4467///
4468/// Returns [`Some`] when [`crate::runtime::json_commands::template_render_file`] can produce a value and [`None`] otherwise.
4469///
4470/// # Examples
4471///
4472/// ```rust,ignore
4473/// let _result = pharos_native::runtime::json_commands::template_render_file(std::path::Path::new("template.html"), &serde_json::json!({}));
4474/// ```
4475///
4476/// # Related
4477///
4478/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4479pub fn template_render_file(template_file: &Path, params_root: &Value) -> Option<String> {
4480 render_file_template(template_file, params_root)
4481}
4482
4483/// Handles template render path command.
4484///
4485/// # Parameters
4486///
4487/// - `template_path`: Input consumed by [`crate::runtime::json_commands::handle_template_render_path_command`].
4488/// - `params_root`: Input consumed by [`crate::runtime::json_commands::handle_template_render_path_command`].
4489///
4490/// # Returns
4491///
4492/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_render_path_command`] can produce a value and [`None`] otherwise.
4493///
4494/// # Examples
4495///
4496/// ```rust,ignore
4497/// let _result = pharos_native::runtime::json_commands::handle_template_render_path_command("template.html", &serde_json::json!({}));
4498/// ```
4499///
4500/// # Related
4501///
4502/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4503pub fn handle_template_render_path_command(
4504 template_path: &str,
4505 params_root: &Value,
4506) -> Option<String> {
4507 template_render_path(template_path, params_root)
4508}
4509
4510/// Handles template render file command.
4511///
4512/// # Parameters
4513///
4514/// - `template_file`: Input consumed by [`crate::runtime::json_commands::handle_template_render_file_command`].
4515/// - `params_root`: Input consumed by [`crate::runtime::json_commands::handle_template_render_file_command`].
4516///
4517/// # Returns
4518///
4519/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_render_file_command`] can produce a value and [`None`] otherwise.
4520///
4521/// # Examples
4522///
4523/// ```rust,ignore
4524/// let _result = pharos_native::runtime::json_commands::handle_template_render_file_command(std::path::Path::new("template.html"), &serde_json::json!({}));
4525/// ```
4526///
4527/// # Related
4528///
4529/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4530pub fn handle_template_render_file_command(
4531 template_file: &Path,
4532 params_root: &Value,
4533) -> Option<String> {
4534 template_render_file(template_file, params_root)
4535}
4536
4537/// Performs template declared fragment path.
4538///
4539/// # Parameters
4540///
4541/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_declared_fragment_path`].
4542/// - `fragment_id`: Input consumed by [`crate::runtime::json_commands::template_declared_fragment_path`].
4543///
4544/// # Returns
4545///
4546/// Returns [`Some`] when [`crate::runtime::json_commands::template_declared_fragment_path`] can produce a value and [`None`] otherwise.
4547///
4548/// # Examples
4549///
4550/// ```rust,ignore
4551/// let _result = pharos_native::runtime::json_commands::template_declared_fragment_path(std::path::Path::new("apps/dev_docs"), "header");
4552/// ```
4553///
4554/// # Related
4555///
4556/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4557pub fn template_declared_fragment_path(app_root: &Path, fragment_id: &str) -> Option<String> {
4558 if app_root.as_os_str().is_empty() || fragment_id.is_empty() {
4559 return None;
4560 }
4561 app_root_relative_path(
4562 app_root,
4563 &runtime_find_declared_fragment_relative_path_in_directory(
4564 &app_root.join("templates"),
4565 "templates",
4566 fragment_id,
4567 &[".html"],
4568 )?,
4569 )
4570}
4571
4572/// Performs template apply base path.
4573///
4574/// # Parameters
4575///
4576/// - `html`: Input consumed by [`crate::runtime::json_commands::template_apply_base_path`].
4577/// - `base_path`: Input consumed by [`crate::runtime::json_commands::template_apply_base_path`].
4578///
4579/// # Returns
4580///
4581/// Returns [`Some`] when [`crate::runtime::json_commands::template_apply_base_path`] can produce a value and [`None`] otherwise.
4582///
4583/// # Performance
4584///
4585/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::template_apply_base_path`].
4586///
4587/// # Examples
4588///
4589/// ```rust,ignore
4590/// let _result = pharos_native::runtime::json_commands::template_apply_base_path(todo!(), "/app");
4591/// ```
4592///
4593/// # Related
4594///
4595/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4596pub fn template_apply_base_path(html: &str, base_path: Option<&str>) -> Option<String> {
4597 let base_path = base_path.unwrap_or("");
4598 if base_path.is_empty() || base_path == "/" {
4599 return Some(html.to_owned());
4600 }
4601 let prefixes = [
4602 "href=\"/",
4603 "src=\"/",
4604 "action=\"/",
4605 "formaction=\"/",
4606 "hx-post=\"/",
4607 "hx-get=\"/",
4608 "hx-put=\"/",
4609 "hx-delete=\"/",
4610 "value=\"/",
4611 ];
4612 let mut out = String::with_capacity(html.len() + 64);
4613 let mut index = 0usize;
4614 while index < html.len() {
4615 let mut matched = false;
4616 for prefix in prefixes {
4617 if html[index..].starts_with(prefix) {
4618 let suffix_index = index + prefix.len() - 1;
4619 let already_scoped = html[suffix_index..].starts_with(base_path)
4620 && matches!(
4621 html[suffix_index + base_path.len()..].chars().next(),
4622 Some('/') | Some('"') | None
4623 );
4624 if already_scoped {
4625 out.push_str(prefix);
4626 } else {
4627 out.push_str(&prefix[..prefix.len() - 1]);
4628 out.push_str(base_path);
4629 out.push('/');
4630 }
4631 index += prefix.len();
4632 matched = true;
4633 break;
4634 }
4635 }
4636 if matched {
4637 continue;
4638 }
4639 out.push(html[index..].chars().next()?);
4640 index += html[index..].chars().next()?.len_utf8();
4641 }
4642 Some(out)
4643}
4644
4645/// Renders fragment with params.
4646fn render_fragment_with_params(
4647 app_root: &Path,
4648 relative_path: &str,
4649 params: serde_json::Map<String, Value>,
4650) -> Option<String> {
4651 generated::render_generated_admin_fragment_from_value_runtime(app_root, relative_path, ¶ms)
4652}
4653
4654fn app_navigation_text(app_root: &Path, item_id: &str) -> Option<(String, String)> {
4655 let manifest = read_json_or_yaml_file(&app_root.join("pharos.app.json"))
4656 .or_else(|| read_json_or_yaml_file(&app_root.join("pharos.app.yml")))
4657 .or_else(|| read_json_or_yaml_file(&app_root.join("pharos.app.yaml")))?;
4658 let items = manifest
4659 .get("ui")
4660 .and_then(|ui| ui.get("navigation"))
4661 .and_then(Value::as_array)?;
4662 let item = items
4663 .iter()
4664 .find(|item| json_string(item, "item_id") == Some(item_id))?;
4665 let label = json_string(item, "label")?.to_owned();
4666 let title = json_string(item, "title")
4667 .map(ToOwned::to_owned)
4668 .unwrap_or_else(|| label.clone());
4669 Some((label, title))
4670}
4671
4672/// Renders error alert HTML.
4673fn render_error_alert_html(app_root: &Path, message: &str) -> Option<String> {
4674 if message.is_empty() {
4675 return Some(String::new());
4676 }
4677 render_fragment_with_params(
4678 app_root,
4679 "templates/shared/fragments/error_alert.html",
4680 serde_json::Map::from_iter([("message".to_owned(), Value::String(message.to_owned()))]),
4681 )
4682 .or_else(|| {
4683 Some(format!(
4684 "<p class=\"error-message\" role=\"alert\">{}</p>",
4685 html_escape_text(message)
4686 ))
4687 })
4688}
4689
4690/// Renders hidden next input HTML inner.
4691fn render_hidden_next_input_html_inner(app_root: &Path, next_path: &str) -> Option<String> {
4692 if next_path.is_empty() {
4693 return Some(String::new());
4694 }
4695 render_fragment_with_params(
4696 app_root,
4697 "templates/shared/fragments/ui_hidden_input.html",
4698 Map::from_iter([
4699 ("id".to_owned(), Value::String("next".to_owned())),
4700 ("name".to_owned(), Value::String("next".to_owned())),
4701 ("value".to_owned(), Value::String(next_path.to_owned())),
4702 ("attributes_html".to_owned(), Value::String(String::new())),
4703 ]),
4704 )
4705 .or_else(|| {
4706 Some(format!(
4707 "<input type=\"hidden\" name=\"next\" value=\"{}\">",
4708 html_escape_text(next_path)
4709 ))
4710 })
4711}
4712
4713/// Performs template page params JSON.
4714fn template_page_params_json(
4715 error_html: &str,
4716 next_input: &str,
4717 login_action_path: &str,
4718 account_type_options_html: &str,
4719 no_email_access_html: &str,
4720 reset_post_path: &str,
4721 invite_email: &str,
4722 invite_path: &str,
4723 return_path: &str,
4724 invite_kind: &str,
4725 target_html: &str,
4726 form_action: &str,
4727) -> Option<String> {
4728 serde_json::to_string(&serde_json::json!({
4729 "error_html": error_html,
4730 "next_input": next_input,
4731 "login_action_path": login_action_path,
4732 "account_type_options_html": account_type_options_html,
4733 "no_email_access_html": no_email_access_html,
4734 "reset_post_path": reset_post_path,
4735 "invite_email": invite_email,
4736 "invite_path": invite_path,
4737 "return_path": return_path,
4738 "invite_kind": invite_kind,
4739 "target_html": target_html,
4740 "form_action": form_action,
4741 }))
4742 .ok()
4743}
4744
4745/// Renders the shared inline error fragment used by declarative form pages.
4746///
4747/// # Parameters
4748///
4749/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_error_alert_html`].
4750/// - `message`: Input consumed by [`crate::runtime::json_commands::template_error_alert_html`].
4751///
4752/// # Returns
4753///
4754/// Returns [`Some`] when [`crate::runtime::json_commands::template_error_alert_html`] can produce a value and [`None`] otherwise.
4755///
4756/// # Examples
4757///
4758/// ```rust,ignore
4759/// let _result = pharos_native::runtime::json_commands::template_error_alert_html(std::path::Path::new("apps/dev_docs"), "example");
4760/// ```
4761///
4762/// # Related
4763///
4764/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4765pub fn template_error_alert_html(app_root: &Path, message: &str) -> Option<String> {
4766 render_error_alert_html(app_root, message)
4767}
4768
4769/// Performs template login page JSON.
4770///
4771/// # Parameters
4772///
4773/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_login_page_json`].
4774/// - `error_message`: Input consumed by [`crate::runtime::json_commands::template_login_page_json`].
4775/// - `next_path`: Input consumed by [`crate::runtime::json_commands::template_login_page_json`].
4776///
4777/// # Returns
4778///
4779/// Returns [`Some`] when [`crate::runtime::json_commands::template_login_page_json`] can produce a value and [`None`] otherwise.
4780///
4781/// # Examples
4782///
4783/// ```rust,ignore
4784/// let _result = pharos_native::runtime::json_commands::template_login_page_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
4785/// ```
4786///
4787/// # Related
4788///
4789/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4790pub fn template_login_page_json(
4791 app_root: &Path,
4792 error_message: Option<&str>,
4793 next_path: Option<&str>,
4794) -> Option<String> {
4795 let login_action_path = crate::config::app_local_conf_path(app_root)
4796 .and_then(|path| crate::config::dynamic_config_lookup_raw(&path, "base_path"))
4797 .filter(|value| value.starts_with('/') && !value.contains(['\r', '\n']))
4798 .filter(|value| value != "/")
4799 .map(|base_path| format!("{}/accounts/login/", base_path.trim_end_matches('/')))
4800 .unwrap_or_else(|| "/accounts/login/".to_owned());
4801 template_page_params_json(
4802 &render_error_alert_html(app_root, error_message.unwrap_or_default())?,
4803 &render_hidden_next_input_html_inner(app_root, next_path.unwrap_or_default())?,
4804 &login_action_path,
4805 "",
4806 "",
4807 "",
4808 "",
4809 "",
4810 "",
4811 "",
4812 "",
4813 "",
4814 )
4815}
4816
4817/// Performs template register page JSON.
4818///
4819/// # Parameters
4820///
4821/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_register_page_json`].
4822/// - `error_message`: Input consumed by [`crate::runtime::json_commands::template_register_page_json`].
4823/// - `next_path`: Input consumed by [`crate::runtime::json_commands::template_register_page_json`].
4824/// - `account_type_options_html`: Input consumed by [`crate::runtime::json_commands::template_register_page_json`].
4825///
4826/// # Returns
4827///
4828/// Returns [`Some`] when [`crate::runtime::json_commands::template_register_page_json`] can produce a value and [`None`] otherwise.
4829///
4830/// # Examples
4831///
4832/// ```rust,ignore
4833/// let _result = pharos_native::runtime::json_commands::template_register_page_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
4834/// ```
4835///
4836/// # Related
4837///
4838/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4839pub fn template_register_page_json(
4840 app_root: &Path,
4841 error_message: Option<&str>,
4842 next_path: Option<&str>,
4843 account_type_options_html: Option<&str>,
4844) -> Option<String> {
4845 template_page_params_json(
4846 &render_error_alert_html(app_root, error_message.unwrap_or_default())?,
4847 &render_hidden_next_input_html_inner(app_root, next_path.unwrap_or_default())?,
4848 "",
4849 account_type_options_html.unwrap_or_default(),
4850 "",
4851 "",
4852 "",
4853 "",
4854 "",
4855 "",
4856 "",
4857 "",
4858 )
4859}
4860
4861/// Performs template password reset request page JSON.
4862///
4863/// # Parameters
4864///
4865/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_password_reset_request_page_json`].
4866/// - `error_message`: Input consumed by [`crate::runtime::json_commands::template_password_reset_request_page_json`].
4867///
4868/// # Returns
4869///
4870/// Returns [`Some`] when [`crate::runtime::json_commands::template_password_reset_request_page_json`] can produce a value and [`None`] otherwise.
4871///
4872/// # Examples
4873///
4874/// ```rust,ignore
4875/// let _result = pharos_native::runtime::json_commands::template_password_reset_request_page_json(std::path::Path::new("apps/dev_docs"), todo!());
4876/// ```
4877///
4878/// # Related
4879///
4880/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4881pub fn template_password_reset_request_page_json(
4882 app_root: &Path,
4883 error_message: Option<&str>,
4884) -> Option<String> {
4885 template_page_params_json(
4886 &render_error_alert_html(app_root, error_message.unwrap_or_default())?,
4887 "",
4888 "",
4889 "",
4890 "",
4891 "",
4892 "",
4893 "",
4894 "",
4895 "",
4896 "",
4897 "",
4898 )
4899}
4900
4901/// Performs template password reset sent page JSON.
4902///
4903/// # Parameters
4904///
4905/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_password_reset_sent_page_json`].
4906/// - `preview_path`: Input consumed by [`crate::runtime::json_commands::template_password_reset_sent_page_json`].
4907///
4908/// # Returns
4909///
4910/// Returns [`Some`] when [`crate::runtime::json_commands::template_password_reset_sent_page_json`] can produce a value and [`None`] otherwise.
4911///
4912/// # Examples
4913///
4914/// ```rust,ignore
4915/// let _result = pharos_native::runtime::json_commands::template_password_reset_sent_page_json(std::path::Path::new("apps/dev_docs"), todo!());
4916/// ```
4917///
4918/// # Related
4919///
4920/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4921pub fn template_password_reset_sent_page_json(
4922 app_root: &Path,
4923 preview_path: Option<&str>,
4924) -> Option<String> {
4925 let preview_path = preview_path.unwrap_or_default();
4926 if preview_path.is_empty() {
4927 return template_page_params_json("", "", "", "", "", "", "", "", "", "", "", "");
4928 }
4929 let no_email_access_html = render_fragment_with_params(
4930 app_root,
4931 "templates/shared/fragments/no_email_access_link.html",
4932 serde_json::Map::from_iter([
4933 (
4934 "intro_text".to_owned(),
4935 Value::String(
4936 "Email delivery is unavailable. Use this local access link:".to_owned(),
4937 ),
4938 ),
4939 (
4940 "href_path".to_owned(),
4941 Value::String(preview_path.to_owned()),
4942 ),
4943 (
4944 "access_path".to_owned(),
4945 Value::String(preview_path.to_owned()),
4946 ),
4947 ]),
4948 )?;
4949 template_page_params_json(
4950 "",
4951 "",
4952 "",
4953 "",
4954 &no_email_access_html,
4955 "",
4956 "",
4957 "",
4958 "",
4959 "",
4960 "",
4961 "",
4962 )
4963}
4964
4965/// Performs template password reset complete page JSON.
4966///
4967/// # Parameters
4968///
4969/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_password_reset_complete_page_json`].
4970/// - `error_message`: Input consumed by [`crate::runtime::json_commands::template_password_reset_complete_page_json`].
4971/// - `reset_post_path`: Input consumed by [`crate::runtime::json_commands::template_password_reset_complete_page_json`].
4972///
4973/// # Returns
4974///
4975/// Returns [`Some`] when [`crate::runtime::json_commands::template_password_reset_complete_page_json`] can produce a value and [`None`] otherwise.
4976///
4977/// # Examples
4978///
4979/// ```rust,ignore
4980/// let _result = pharos_native::runtime::json_commands::template_password_reset_complete_page_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
4981/// ```
4982///
4983/// # Related
4984///
4985/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
4986pub fn template_password_reset_complete_page_json(
4987 app_root: &Path,
4988 error_message: Option<&str>,
4989 reset_post_path: Option<&str>,
4990) -> Option<String> {
4991 template_page_params_json(
4992 &render_error_alert_html(app_root, error_message.unwrap_or_default())?,
4993 "",
4994 "",
4995 "",
4996 "",
4997 &html_escape_text(reset_post_path.unwrap_or_default()),
4998 "",
4999 "",
5000 "",
5001 "",
5002 "",
5003 "",
5004 )
5005}
5006
5007/// Performs template invite created page JSON.
5008///
5009/// # Parameters
5010///
5011/// - `invite_email`: Input consumed by [`crate::runtime::json_commands::template_invite_created_page_json`].
5012/// - `invite_path`: Input consumed by [`crate::runtime::json_commands::template_invite_created_page_json`].
5013/// - `return_path`: Input consumed by [`crate::runtime::json_commands::template_invite_created_page_json`].
5014///
5015/// # Returns
5016///
5017/// Returns [`Some`] when [`crate::runtime::json_commands::template_invite_created_page_json`] can produce a value and [`None`] otherwise.
5018///
5019/// # Examples
5020///
5021/// ```rust,ignore
5022/// let _result = pharos_native::runtime::json_commands::template_invite_created_page_json(todo!(), todo!(), todo!());
5023/// ```
5024///
5025/// # Related
5026///
5027/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5028pub fn template_invite_created_page_json(
5029 invite_email: Option<&str>,
5030 invite_path: Option<&str>,
5031 return_path: Option<&str>,
5032) -> Option<String> {
5033 template_page_params_json(
5034 "",
5035 "",
5036 "",
5037 "",
5038 "",
5039 "",
5040 &html_escape_text(invite_email.unwrap_or_default()),
5041 &html_escape_text(invite_path.unwrap_or_default()),
5042 &html_escape_text(return_path.unwrap_or_default()),
5043 "",
5044 "",
5045 "",
5046 )
5047}
5048
5049/// Performs template invite accept page JSON.
5050///
5051/// # Parameters
5052///
5053/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_invite_accept_page_json`].
5054/// - `invite_kind`: Input consumed by [`crate::runtime::json_commands::template_invite_accept_page_json`].
5055/// - `target_business_name`: Input consumed by [`crate::runtime::json_commands::template_invite_accept_page_json`].
5056/// - `error_message`: Input consumed by [`crate::runtime::json_commands::template_invite_accept_page_json`].
5057/// - `invite_email`: Input consumed by [`crate::runtime::json_commands::template_invite_accept_page_json`].
5058/// - `form_action`: Input consumed by [`crate::runtime::json_commands::template_invite_accept_page_json`].
5059///
5060/// # Returns
5061///
5062/// Returns [`Some`] when [`crate::runtime::json_commands::template_invite_accept_page_json`] can produce a value and [`None`] otherwise.
5063///
5064/// # Examples
5065///
5066/// ```rust,ignore
5067/// let _result = pharos_native::runtime::json_commands::template_invite_accept_page_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!(), todo!());
5068/// ```
5069///
5070/// # Related
5071///
5072/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5073pub fn template_invite_accept_page_json(
5074 app_root: &Path,
5075 invite_kind: Option<&str>,
5076 target_business_name: Option<&str>,
5077 error_message: Option<&str>,
5078 invite_email: Option<&str>,
5079 form_action: Option<&str>,
5080) -> Option<String> {
5081 let target_html = if let Some(target_business_name) =
5082 target_business_name.filter(|value| !value.is_empty())
5083 {
5084 render_fragment_with_params(
5085 app_root,
5086 "templates/shared/fragments/invite_target.html",
5087 serde_json::Map::from_iter([(
5088 "business_name".to_owned(),
5089 Value::String(target_business_name.to_owned()),
5090 )]),
5091 )?
5092 } else {
5093 String::new()
5094 };
5095 template_page_params_json(
5096 &render_error_alert_html(app_root, error_message.unwrap_or_default())?,
5097 "",
5098 "",
5099 "",
5100 "",
5101 "",
5102 &html_escape_text(invite_email.unwrap_or_default()),
5103 "",
5104 "",
5105 &html_escape_text(invite_kind.unwrap_or("account_invite")),
5106 &target_html,
5107 &html_escape_text(form_action.unwrap_or_default()),
5108 )
5109}
5110
5111/// Renders select fragment.
5112fn render_select_fragment(
5113 app_root: &Path,
5114 relative_path: &str,
5115 value: &str,
5116 selected: bool,
5117 label: &str,
5118) -> Option<String> {
5119 render_fragment_with_params(
5120 app_root,
5121 relative_path,
5122 serde_json::Map::from_iter([
5123 ("option_value".to_owned(), Value::String(value.to_owned())),
5124 (
5125 "selected_attr".to_owned(),
5126 Value::String(if selected { " selected" } else { "" }.to_owned()),
5127 ),
5128 ("option_label".to_owned(), Value::String(label.to_owned())),
5129 ]),
5130 )
5131}
5132
5133/// Builds layout page params JSON.
5134fn build_layout_page_params_json(
5135 app_root: &Path,
5136 layout_id: Option<&str>,
5137 title: &str,
5138 body: &str,
5139 current_lang: Option<&str>,
5140 current_request_target: Option<&str>,
5141 has_user: bool,
5142 managed_business_count: i64,
5143) -> Option<String> {
5144 let effective_layout_id = layout_id
5145 .filter(|value| !value.is_empty())
5146 .unwrap_or("default_shell");
5147 let resolved_lang = current_lang
5148 .filter(|value| !value.is_empty())
5149 .unwrap_or("en");
5150 let (nav_home_path, nav_primary, nav_account): (String, String, String);
5151
5152 let languages = [
5153 ("en", "English"),
5154 ("ja", "日本語"),
5155 ("es", "Español"),
5156 ("fr", "Français"),
5157 ("de", "Deutsch"),
5158 ("pt", "Português"),
5159 ];
5160 let mut language_options_html = String::new();
5161 for (code, label) in languages {
5162 language_options_html.push_str(&render_select_fragment(
5163 app_root,
5164 "templates/layout/fragments/language_option.html",
5165 code,
5166 resolved_lang == code,
5167 label,
5168 )?);
5169 }
5170 let language_select_ui_html = render_fragment_with_params(
5171 app_root,
5172 "templates/shared/fragments/ui_select.html",
5173 serde_json::Map::from_iter([
5174 ("id".to_owned(), Value::String("language-select".to_owned())),
5175 ("name".to_owned(), Value::String("language".to_owned())),
5176 ("label".to_owned(), Value::String("Language".to_owned())),
5177 (
5178 "label_class".to_owned(),
5179 Value::String("sr-only".to_owned()),
5180 ),
5181 (
5182 "class_name".to_owned(),
5183 Value::String("form-control".to_owned()),
5184 ),
5185 ("attributes_html".to_owned(), Value::String(String::new())),
5186 (
5187 "options_html".to_owned(),
5188 Value::String(language_options_html),
5189 ),
5190 ("help_html".to_owned(), Value::String(String::new())),
5191 ]),
5192 )?;
5193 let language_form_html = render_fragment_with_params(
5194 app_root,
5195 "templates/layout/fragments/language_form.html",
5196 serde_json::Map::from_iter([
5197 (
5198 "current_request_target".to_owned(),
5199 Value::String(current_request_target.unwrap_or("/").to_owned()),
5200 ),
5201 (
5202 "language_select_ui_html".to_owned(),
5203 Value::String(language_select_ui_html),
5204 ),
5205 ]),
5206 )?;
5207
5208 if has_user {
5209 let mut resolved_nav_home_path = "/".to_owned();
5210 let nav_primary_value: String;
5211 if effective_layout_id == "architect_shell" {
5212 let (home_label, home_title) = app_navigation_text(app_root, "home")?;
5213 let home_link_html = render_fragment_with_params(
5214 app_root,
5215 "templates/layout/fragments/nav_link.html",
5216 serde_json::Map::from_iter([
5217 ("link_path".to_owned(), Value::String("/".to_owned())),
5218 ("link_label".to_owned(), Value::String(home_label)),
5219 ("link_title".to_owned(), Value::String(home_title)),
5220 (
5221 "class_name".to_owned(),
5222 Value::String("nav-icon-button architect-nav-home-link".to_owned()),
5223 ),
5224 (
5225 "attributes_html".to_owned(),
5226 Value::String(" data-pharos-nav=\"architect-main\"".to_owned()),
5227 ),
5228 ]),
5229 )?;
5230 let (file_label, file_title) = app_navigation_text(app_root, "file")?;
5231 let file_link_html = render_fragment_with_params(
5232 app_root,
5233 "templates/layout/fragments/nav_link.html",
5234 serde_json::Map::from_iter([
5235 ("link_path".to_owned(), Value::String("/editor".to_owned())),
5236 ("link_label".to_owned(), Value::String(file_label)),
5237 ("link_title".to_owned(), Value::String(file_title)),
5238 (
5239 "class_name".to_owned(),
5240 Value::String("nav-icon-button architect-nav-file-link".to_owned()),
5241 ),
5242 (
5243 "attributes_html".to_owned(),
5244 Value::String(" data-pharos-nav=\"architect-main\"".to_owned()),
5245 ),
5246 ]),
5247 )?;
5248 let settings_link_html = if managed_business_count > 0 {
5249 let (settings_label, settings_title) = app_navigation_text(app_root, "settings")?;
5250 render_fragment_with_params(
5251 app_root,
5252 "templates/layout/fragments/nav_link.html",
5253 serde_json::Map::from_iter([
5254 (
5255 "link_path".to_owned(),
5256 Value::String("/settings".to_owned()),
5257 ),
5258 ("link_label".to_owned(), Value::String(settings_label)),
5259 ("link_title".to_owned(), Value::String(settings_title)),
5260 (
5261 "class_name".to_owned(),
5262 Value::String("nav-icon-button architect-nav-settings-link".to_owned()),
5263 ),
5264 (
5265 "attributes_html".to_owned(),
5266 Value::String(" data-pharos-nav=\"architect-main\"".to_owned()),
5267 ),
5268 ]),
5269 )?
5270 } else {
5271 String::new()
5272 };
5273 let logout_button_ui_html = render_fragment_with_params(
5274 app_root,
5275 "templates/layout/fragments/nav_logout_button.html",
5276 serde_json::Map::new(),
5277 )?;
5278 let logout_form_html = render_fragment_with_params(
5279 app_root,
5280 "templates/layout/fragments/logout_form.html",
5281 serde_json::Map::from_iter([(
5282 "logout_button_ui_html".to_owned(),
5283 Value::String(logout_button_ui_html),
5284 )]),
5285 )?;
5286 nav_home_path = "/".to_owned();
5287 nav_primary = format!("{home_link_html} {file_link_html}");
5288 nav_account = format!("{settings_link_html}{logout_form_html}");
5289 let browser_title = layout_browser_title(app_root, title);
5290 return serde_json::to_string(&serde_json::json!({
5291 "current_lang": resolved_lang,
5292 "current_request_target": current_request_target.unwrap_or("/"),
5293 "title": browser_title,
5294 "nav_home_path": nav_home_path,
5295 "nav_primary": nav_primary,
5296 "nav_account": nav_account,
5297 "body": body,
5298 }))
5299 .ok();
5300 }
5301 let appointments_link_html = render_fragment_with_params(
5302 app_root,
5303 "templates/layout/fragments/nav_link.html",
5304 serde_json::Map::from_iter([
5305 (
5306 "link_path".to_owned(),
5307 Value::String("/appointments/".to_owned()),
5308 ),
5309 (
5310 "link_label".to_owned(),
5311 Value::String("Appointments".to_owned()),
5312 ),
5313 ]),
5314 )?;
5315 let profile_link_html = render_fragment_with_params(
5316 app_root,
5317 "templates/layout/fragments/nav_link.html",
5318 serde_json::Map::from_iter([
5319 (
5320 "link_path".to_owned(),
5321 Value::String("/profile/".to_owned()),
5322 ),
5323 ("link_label".to_owned(), Value::String("Profile".to_owned())),
5324 ]),
5325 )?;
5326 let logout_button_ui_html = render_fragment_with_params(
5327 app_root,
5328 "templates/shared/fragments/ui_button.html",
5329 serde_json::Map::from_iter([
5330 ("id".to_owned(), Value::String("logout_button".to_owned())),
5331 ("button_type".to_owned(), Value::String("submit".to_owned())),
5332 (
5333 "class_name".to_owned(),
5334 Value::String("btn btn-secondary".to_owned()),
5335 ),
5336 ("attributes_html".to_owned(), Value::String(String::new())),
5337 (
5338 "content_html".to_owned(),
5339 Value::String("Log out".to_owned()),
5340 ),
5341 ]),
5342 )?;
5343 let logout_form_html = render_fragment_with_params(
5344 app_root,
5345 "templates/layout/fragments/logout_form.html",
5346 serde_json::Map::from_iter([(
5347 "logout_button_ui_html".to_owned(),
5348 Value::String(logout_button_ui_html),
5349 )]),
5350 )?;
5351 if matches!(
5352 effective_layout_id,
5353 "public_catalog_shell" | "public_business_shell"
5354 ) {
5355 if effective_layout_id == "public_business_shell" && managed_business_count > 0 {
5356 let manage_business_link_html = render_fragment_with_params(
5357 app_root,
5358 "templates/layout/fragments/nav_link.html",
5359 serde_json::Map::from_iter([
5360 (
5361 "link_path".to_owned(),
5362 Value::String("/businesses/".to_owned()),
5363 ),
5364 (
5365 "link_label".to_owned(),
5366 Value::String("Manage Businesses".to_owned()),
5367 ),
5368 ]),
5369 )?;
5370 nav_primary_value = format!("{appointments_link_html} {manage_business_link_html}");
5371 } else {
5372 nav_primary_value = appointments_link_html;
5373 }
5374 resolved_nav_home_path = "/explore/".to_owned();
5375 } else if matches!(effective_layout_id, "business_shell" | "account_shell")
5376 || managed_business_count > 0
5377 {
5378 let manage_business_link_html = render_fragment_with_params(
5379 app_root,
5380 "templates/layout/fragments/nav_link.html",
5381 serde_json::Map::from_iter([
5382 (
5383 "link_path".to_owned(),
5384 Value::String("/businesses/".to_owned()),
5385 ),
5386 (
5387 "link_label".to_owned(),
5388 Value::String("Manage Businesses".to_owned()),
5389 ),
5390 ]),
5391 )?;
5392 nav_primary_value = format!("{appointments_link_html} {manage_business_link_html}");
5393 resolved_nav_home_path = "/explore/".to_owned();
5394 } else {
5395 nav_primary_value = String::new();
5396 }
5397 nav_home_path = resolved_nav_home_path;
5398 nav_primary = nav_primary_value;
5399 nav_account = format!("{language_form_html} {profile_link_html}{logout_form_html}");
5400 } else if effective_layout_id == "architect_shell" {
5401 nav_home_path = "/accounts/login/".to_owned();
5402 nav_primary = String::new();
5403 nav_account = language_form_html;
5404 } else {
5405 nav_home_path = "/accounts/login/".to_owned();
5406 let login_link_html = render_fragment_with_params(
5407 app_root,
5408 "templates/layout/fragments/nav_link.html",
5409 serde_json::Map::from_iter([
5410 (
5411 "link_path".to_owned(),
5412 Value::String("/accounts/login/".to_owned()),
5413 ),
5414 ("link_label".to_owned(), Value::String("Log in".to_owned())),
5415 ]),
5416 )?;
5417 let register_link_html = render_fragment_with_params(
5418 app_root,
5419 "templates/layout/fragments/nav_link.html",
5420 serde_json::Map::from_iter([
5421 (
5422 "link_path".to_owned(),
5423 Value::String("/accounts/register/".to_owned()),
5424 ),
5425 ("link_label".to_owned(), Value::String("Sign Up".to_owned())),
5426 ]),
5427 )?;
5428 nav_primary = format!("{login_link_html} {register_link_html}");
5429 nav_account = language_form_html;
5430 }
5431
5432 let browser_title = layout_browser_title(app_root, title);
5433 serde_json::to_string(&serde_json::json!({
5434 "current_lang": resolved_lang,
5435 "current_request_target": current_request_target.unwrap_or("/"),
5436 "title": browser_title,
5437 "nav_home_path": nav_home_path,
5438 "nav_primary": nav_primary,
5439 "nav_account": nav_account,
5440 "body": body,
5441 }))
5442 .ok()
5443}
5444
5445pub(crate) fn layout_browser_title(app_root: &Path, title: &str) -> String {
5446 let page_title = title.trim();
5447 let app_name = read_json_or_yaml_file(&app_root.join("pharos.app.json"))
5448 .or_else(|| read_json_or_yaml_file(&app_root.join("pharos.app.yml")))
5449 .or_else(|| read_json_or_yaml_file(&app_root.join("pharos.app.yaml")))
5450 .and_then(|manifest| {
5451 manifest
5452 .get("name")
5453 .and_then(Value::as_str)
5454 .map(str::trim)
5455 .filter(|value| !value.is_empty())
5456 .map(ToOwned::to_owned)
5457 });
5458 let Some(app_name) = app_name else {
5459 return page_title.to_owned();
5460 };
5461 let page_title = page_title
5462 .strip_prefix(&format!("{app_name} — "))
5463 .or_else(|| page_title.strip_prefix(&format!("{app_name} - ")))
5464 .or_else(|| page_title.strip_suffix(&format!(" — {app_name}")))
5465 .or_else(|| page_title.strip_suffix(&format!(" - {app_name}")))
5466 .unwrap_or(page_title)
5467 .trim();
5468 if page_title.is_empty() || page_title == app_name {
5469 app_name
5470 } else {
5471 format!("{app_name} - {page_title}")
5472 }
5473}
5474
5475/// Performs template layout page JSON.
5476///
5477/// # Parameters
5478///
5479/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5480/// - `layout_id`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5481/// - `current_lang`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5482/// - `title`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5483/// - `body`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5484/// - `current_request_target`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5485/// - `has_user`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5486/// - `managed_business_count`: Input consumed by [`crate::runtime::json_commands::template_layout_page_json`].
5487///
5488/// # Returns
5489///
5490/// Returns [`Some`] when [`crate::runtime::json_commands::template_layout_page_json`] can produce a value and [`None`] otherwise.
5491///
5492/// # Examples
5493///
5494/// ```rust,ignore
5495/// let _result = pharos_native::runtime::json_commands::template_layout_page_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), b"", todo!(), todo!(), todo!());
5496/// ```
5497///
5498/// # Related
5499///
5500/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5501pub fn template_layout_page_json(
5502 app_root: &Path,
5503 layout_id: Option<&str>,
5504 current_lang: Option<&str>,
5505 title: &str,
5506 body: &str,
5507 current_request_target: Option<&str>,
5508 has_user: bool,
5509 managed_business_count: i64,
5510) -> Option<String> {
5511 if app_root.as_os_str().is_empty() || title.is_empty() || body.is_empty() {
5512 return None;
5513 }
5514 build_layout_page_params_json(
5515 app_root,
5516 layout_id,
5517 title,
5518 body,
5519 current_lang,
5520 current_request_target,
5521 has_user,
5522 managed_business_count,
5523 )
5524}
5525
5526/// Performs template layout page HTML.
5527///
5528/// # Parameters
5529///
5530/// - `app_root`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5531/// - `base_path`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5532/// - `layout_id`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5533/// - `current_lang`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5534/// - `title`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5535/// - `body`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5536/// - `current_request_target`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5537/// - `has_user`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5538/// - `managed_business_count`: Input consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5539///
5540/// # Returns
5541///
5542/// Returns [`Some`] when [`crate::runtime::json_commands::template_layout_page_html`] can produce a value and [`None`] otherwise.
5543///
5544/// # Performance
5545///
5546/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::template_layout_page_html`].
5547///
5548/// # Examples
5549///
5550/// ```rust,ignore
5551/// let _result = pharos_native::runtime::json_commands::template_layout_page_html(std::path::Path::new("apps/dev_docs"), "/app", todo!(), todo!(), todo!(), b"", todo!(), todo!(), todo!());
5552/// ```
5553///
5554/// # Related
5555///
5556/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5557pub fn template_layout_page_html(
5558 app_root: &Path,
5559 base_path: Option<&str>,
5560 layout_id: Option<&str>,
5561 current_lang: Option<&str>,
5562 title: &str,
5563 body: &str,
5564 current_request_target: Option<&str>,
5565 has_user: bool,
5566 managed_business_count: i64,
5567) -> Option<String> {
5568 let render_params_json = build_layout_page_params_json(
5569 app_root,
5570 layout_id,
5571 title,
5572 body,
5573 current_lang,
5574 current_request_target,
5575 has_user,
5576 managed_business_count,
5577 )?;
5578 let render_params_root = serde_json::from_str::<Value>(&render_params_json).ok()?;
5579 let rendered = render_file_template(
5580 &app_root.join("templates/layout/base.html"),
5581 &render_params_root,
5582 )?;
5583 template_apply_base_path(&rendered, base_path)
5584}
5585
5586/// Performs validation empty report.
5587///
5588/// # Parameters
5589///
5590/// - `form_id`: Input consumed by [`crate::runtime::json_commands::validation_empty_report`].
5591/// - `action_id`: Input consumed by [`crate::runtime::json_commands::validation_empty_report`].
5592/// - `actor_scope`: Input consumed by [`crate::runtime::json_commands::validation_empty_report`].
5593/// - `tenant_scope`: Input consumed by [`crate::runtime::json_commands::validation_empty_report`].
5594///
5595/// # Returns
5596///
5597/// Returns [`Some`] when [`crate::runtime::json_commands::validation_empty_report`] can produce a value and [`None`] otherwise.
5598///
5599/// # Examples
5600///
5601/// ```rust,ignore
5602/// let _result = pharos_native::runtime::json_commands::validation_empty_report("form", todo!(), todo!(), todo!());
5603/// ```
5604///
5605/// # Related
5606///
5607/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5608pub fn validation_empty_report(
5609 form_id: Option<&str>,
5610 action_id: Option<&str>,
5611 actor_scope: Option<&str>,
5612 tenant_scope: Option<&str>,
5613) -> Option<String> {
5614 serde_json::to_string(&serde_json::json!({
5615 "schema": "pharos.validation.report.v1",
5616 "report_id": "pharos.validation.report.v1",
5617 "form_id": form_id.unwrap_or_default(),
5618 "action_id": action_id.unwrap_or_default(),
5619 "field_errors": [],
5620 "form_errors": [],
5621 "actor_scope": actor_scope.unwrap_or("anonymous"),
5622 "tenant_scope": tenant_scope.unwrap_or("global"),
5623 }))
5624 .ok()
5625}
5626
5627/// Appends validation error.
5628fn append_validation_error(
5629 report_json: &str,
5630 array_name: &str,
5631 field_id: Option<&str>,
5632 rule_id: Option<&str>,
5633 code_text: Option<&str>,
5634 message: Option<&str>,
5635) -> Option<String> {
5636 let mut root = serde_json::from_str::<Value>(report_json).ok()?;
5637 let object = root.as_object_mut()?;
5638 if !matches!(object.get(array_name), Some(Value::Array(_))) {
5639 object.insert(array_name.to_owned(), Value::Array(Vec::new()));
5640 }
5641 let errors = object.get_mut(array_name)?.as_array_mut()?;
5642 let mut entry = serde_json::Map::new();
5643 if let Some(field_id) = field_id.filter(|value| !value.is_empty()) {
5644 entry.insert("field_id".to_owned(), Value::String(field_id.to_owned()));
5645 }
5646 entry.insert(
5647 "rule_id".to_owned(),
5648 Value::String(rule_id.unwrap_or_default().to_owned()),
5649 );
5650 entry.insert(
5651 "code".to_owned(),
5652 Value::String(code_text.unwrap_or_default().to_owned()),
5653 );
5654 entry.insert(
5655 "message".to_owned(),
5656 Value::String(message.unwrap_or_default().to_owned()),
5657 );
5658 errors.push(Value::Object(entry));
5659 serde_json::to_string(&root).ok()
5660}
5661
5662/// Performs validation add field error.
5663///
5664/// # Parameters
5665///
5666/// - `report_json`: Input consumed by [`crate::runtime::json_commands::validation_add_field_error`].
5667/// - `field_id`: Input consumed by [`crate::runtime::json_commands::validation_add_field_error`].
5668/// - `rule_id`: Input consumed by [`crate::runtime::json_commands::validation_add_field_error`].
5669/// - `code_text`: Input consumed by [`crate::runtime::json_commands::validation_add_field_error`].
5670/// - `message`: Input consumed by [`crate::runtime::json_commands::validation_add_field_error`].
5671///
5672/// # Returns
5673///
5674/// Returns [`Some`] when [`crate::runtime::json_commands::validation_add_field_error`] can produce a value and [`None`] otherwise.
5675///
5676/// # Examples
5677///
5678/// ```rust,ignore
5679/// let _result = pharos_native::runtime::json_commands::validation_add_field_error("{}", "field", todo!(), todo!(), "example");
5680/// ```
5681///
5682/// # Related
5683///
5684/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5685pub fn validation_add_field_error(
5686 report_json: &str,
5687 field_id: Option<&str>,
5688 rule_id: Option<&str>,
5689 code_text: Option<&str>,
5690 message: Option<&str>,
5691) -> Option<String> {
5692 append_validation_error(
5693 report_json,
5694 "field_errors",
5695 field_id,
5696 rule_id,
5697 code_text,
5698 message,
5699 )
5700}
5701
5702/// Performs validation add form error.
5703///
5704/// # Parameters
5705///
5706/// - `report_json`: Input consumed by [`crate::runtime::json_commands::validation_add_form_error`].
5707/// - `rule_id`: Input consumed by [`crate::runtime::json_commands::validation_add_form_error`].
5708/// - `code_text`: Input consumed by [`crate::runtime::json_commands::validation_add_form_error`].
5709/// - `message`: Input consumed by [`crate::runtime::json_commands::validation_add_form_error`].
5710///
5711/// # Returns
5712///
5713/// Returns [`Some`] when [`crate::runtime::json_commands::validation_add_form_error`] can produce a value and [`None`] otherwise.
5714///
5715/// # Examples
5716///
5717/// ```rust,ignore
5718/// let _result = pharos_native::runtime::json_commands::validation_add_form_error("{}", todo!(), todo!(), "example");
5719/// ```
5720///
5721/// # Related
5722///
5723/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5724pub fn validation_add_form_error(
5725 report_json: &str,
5726 rule_id: Option<&str>,
5727 code_text: Option<&str>,
5728 message: Option<&str>,
5729) -> Option<String> {
5730 append_validation_error(
5731 report_json,
5732 "form_errors",
5733 None,
5734 rule_id,
5735 code_text,
5736 message,
5737 )
5738}
5739
5740/// Performs validation has errors.
5741///
5742/// # Parameters
5743///
5744/// - `report_json`: Input consumed by [`crate::runtime::json_commands::validation_has_errors`].
5745///
5746/// # Returns
5747///
5748/// Returns [`Some`] when [`crate::runtime::json_commands::validation_has_errors`] can produce a value and [`None`] otherwise.
5749///
5750/// # Performance
5751///
5752/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::validation_has_errors`].
5753///
5754/// # Examples
5755///
5756/// ```rust,ignore
5757/// let _result = pharos_native::runtime::json_commands::validation_has_errors("{}");
5758/// ```
5759///
5760/// # Related
5761///
5762/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5763pub fn validation_has_errors(report_json: &str) -> Option<bool> {
5764 let Some(root) = serde_json::from_str::<Value>(report_json).ok() else {
5765 return Some(false);
5766 };
5767 let field_count = root
5768 .get("field_errors")
5769 .and_then(Value::as_array)
5770 .map(Vec::len)
5771 .unwrap_or(0);
5772 let form_count = root
5773 .get("form_errors")
5774 .and_then(Value::as_array)
5775 .map(Vec::len)
5776 .unwrap_or(0);
5777 Some(field_count + form_count > 0)
5778}
5779
5780/// Performs validation summary text.
5781///
5782/// # Parameters
5783///
5784/// - `report_json`: Input consumed by [`crate::runtime::json_commands::validation_summary_text`].
5785///
5786/// # Returns
5787///
5788/// Returns [`Some`] when [`crate::runtime::json_commands::validation_summary_text`] can produce a value and [`None`] otherwise.
5789///
5790/// # Performance
5791///
5792/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::validation_summary_text`].
5793///
5794/// # Examples
5795///
5796/// ```rust,ignore
5797/// let _result = pharos_native::runtime::json_commands::validation_summary_text("{}");
5798/// ```
5799///
5800/// # Related
5801///
5802/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5803pub fn validation_summary_text(report_json: &str) -> Option<String> {
5804 let root = serde_json::from_str::<Value>(report_json).ok()?;
5805 let object = root.as_object()?;
5806 let mut items = Vec::new();
5807 for array_name in ["field_errors", "form_errors"] {
5808 if let Some(errors) = object.get(array_name).and_then(Value::as_array) {
5809 for item in errors {
5810 let rule_id = item
5811 .get("rule_id")
5812 .and_then(Value::as_str)
5813 .unwrap_or_default();
5814 let message = item
5815 .get("message")
5816 .and_then(Value::as_str)
5817 .unwrap_or_default();
5818 if !message.is_empty() {
5819 items.push(format!("[{rule_id}] {message}"));
5820 }
5821 }
5822 }
5823 }
5824 Some(items.join(" "))
5825}
5826
5827/// Performs validation field list HTML.
5828///
5829/// # Parameters
5830///
5831/// - `app_root`: Input consumed by [`crate::runtime::json_commands::validation_field_list_html`].
5832/// - `report_json`: Input consumed by [`crate::runtime::json_commands::validation_field_list_html`].
5833///
5834/// # Returns
5835///
5836/// Returns [`Some`] when [`crate::runtime::json_commands::validation_field_list_html`] can produce a value and [`None`] otherwise.
5837///
5838/// # Performance
5839///
5840/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::validation_field_list_html`].
5841///
5842/// # Examples
5843///
5844/// ```rust,ignore
5845/// let _result = pharos_native::runtime::json_commands::validation_field_list_html(std::path::Path::new("apps/dev_docs"), "{}");
5846/// ```
5847///
5848/// # Related
5849///
5850/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5851pub fn validation_field_list_html(app_root: &Path, report_json: &str) -> Option<String> {
5852 if app_root.as_os_str().is_empty() {
5853 return None;
5854 }
5855 let root = serde_json::from_str::<Value>(report_json).ok()?;
5856 let object = root.as_object()?;
5857 let mut out = String::new();
5858 if let Some(errors) = object.get("field_errors").and_then(Value::as_array) {
5859 for item in errors {
5860 let rule_id = item
5861 .get("rule_id")
5862 .and_then(Value::as_str)
5863 .unwrap_or_default();
5864 let field_id = item
5865 .get("field_id")
5866 .and_then(Value::as_str)
5867 .unwrap_or_default();
5868 let message = item
5869 .get("message")
5870 .and_then(Value::as_str)
5871 .unwrap_or_default();
5872 out.push_str(&render_fragment_with_params(
5873 app_root,
5874 "templates/shared/fragments/validation_field_error_item.html",
5875 serde_json::Map::from_iter([
5876 ("rule_id".to_owned(), Value::String(rule_id.to_owned())),
5877 ("field_id".to_owned(), Value::String(field_id.to_owned())),
5878 ("message".to_owned(), Value::String(message.to_owned())),
5879 ]),
5880 )?);
5881 }
5882 }
5883 if let Some(errors) = object.get("form_errors").and_then(Value::as_array) {
5884 for item in errors {
5885 let rule_id = item
5886 .get("rule_id")
5887 .and_then(Value::as_str)
5888 .unwrap_or_default();
5889 let message = item
5890 .get("message")
5891 .and_then(Value::as_str)
5892 .unwrap_or_default();
5893 out.push_str(&render_fragment_with_params(
5894 app_root,
5895 "templates/shared/fragments/validation_form_error_item.html",
5896 serde_json::Map::from_iter([
5897 ("rule_id".to_owned(), Value::String(rule_id.to_owned())),
5898 ("message".to_owned(), Value::String(message.to_owned())),
5899 ]),
5900 )?);
5901 }
5902 }
5903 Some(out)
5904}
5905
5906/// Trims ascii whitespace range.
5907///
5908/// # Parameters
5909///
5910/// - `text`: Input consumed by [`crate::runtime::json_commands::trim_ascii_whitespace_range`].
5911///
5912/// # Returns
5913///
5914/// Returns the computed value produced by [`crate::runtime::json_commands::trim_ascii_whitespace_range`].
5915///
5916/// # Examples
5917///
5918/// ```rust,ignore
5919/// let _result = pharos_native::runtime::json_commands::trim_ascii_whitespace_range("{}");
5920/// ```
5921///
5922/// # Related
5923///
5924/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5925pub fn trim_ascii_whitespace_range(text: &str) -> (usize, usize) {
5926 let bytes = text.as_bytes();
5927 let mut start = 0usize;
5928 let mut end = bytes.len();
5929 while start < end && bytes[start].is_ascii_whitespace() {
5930 start += 1;
5931 }
5932 while end > start && bytes[end - 1].is_ascii_whitespace() {
5933 end -= 1;
5934 }
5935 (start, end)
5936}
5937
5938/// Performs JSON string array lines.
5939///
5940/// # Parameters
5941///
5942/// - `text`: Input consumed by [`crate::runtime::json_commands::json_string_array_lines`].
5943///
5944/// # Returns
5945///
5946/// Returns [`Some`] when [`crate::runtime::json_commands::json_string_array_lines`] can produce a value and [`None`] otherwise.
5947///
5948/// # Performance
5949///
5950/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_string_array_lines`].
5951///
5952/// # Examples
5953///
5954/// ```rust,ignore
5955/// let _result = pharos_native::runtime::json_commands::json_string_array_lines("{}");
5956/// ```
5957///
5958/// # Related
5959///
5960/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5961pub fn json_string_array_lines(text: &str) -> Option<Vec<String>> {
5962 serde_json::from_str::<Value>(text)
5963 .ok()?
5964 .as_array()?
5965 .iter()
5966 .map(|value| value.as_str().map(ToOwned::to_owned))
5967 .collect()
5968}
5969
5970/// Renders empty card list item.
5971///
5972/// # Parameters
5973///
5974/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_empty_card_list_item`].
5975/// - `message`: Input consumed by [`crate::runtime::json_commands::render_empty_card_list_item`].
5976///
5977/// # Returns
5978///
5979/// Returns [`Some`] when [`crate::runtime::json_commands::render_empty_card_list_item`] can produce a value and [`None`] otherwise.
5980///
5981/// # Examples
5982///
5983/// ```rust,ignore
5984/// let _result = pharos_native::runtime::json_commands::render_empty_card_list_item(std::path::Path::new("apps/dev_docs"), "example");
5985/// ```
5986///
5987/// # Related
5988///
5989/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
5990pub fn render_empty_card_list_item(app_root: &str, message: &str) -> Option<String> {
5991 super::business::render_empty_card_list_item_business_logic_local(app_root, message)
5992}
5993
5994/// Performs surface find by ID.
5995///
5996/// # Parameters
5997///
5998/// - `root`: Input consumed by [`crate::runtime::json_commands::surface_find_by_id`].
5999/// - `surface_id`: Input consumed by [`crate::runtime::json_commands::surface_find_by_id`].
6000///
6001/// # Returns
6002///
6003/// Returns [`Some`] when [`crate::runtime::json_commands::surface_find_by_id`] can produce a value and [`None`] otherwise.
6004///
6005/// # Performance
6006///
6007/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::surface_find_by_id`].
6008///
6009/// # Examples
6010///
6011/// ```rust,ignore
6012/// let _result = pharos_native::runtime::json_commands::surface_find_by_id(std::path::Path::new("."), "surface");
6013/// ```
6014///
6015/// # Related
6016///
6017/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6018pub fn surface_find_by_id(root: &Value, surface_id: &str) -> Option<String> {
6019 root.get("surfaces")?
6020 .as_array()?
6021 .iter()
6022 .find(|surface| {
6023 surface.get("id").and_then(Value::as_str) == Some(surface_id)
6024 || surface.get("surface_id").and_then(Value::as_str) == Some(surface_id)
6025 })
6026 .and_then(compact)
6027}
6028
6029/// Performs error envelope.
6030///
6031/// # Parameters
6032///
6033/// - `error_type`: Input consumed by [`crate::runtime::json_commands::error_envelope`].
6034/// - `error_code`: Input consumed by [`crate::runtime::json_commands::error_envelope`].
6035/// - `message`: Input consumed by [`crate::runtime::json_commands::error_envelope`].
6036/// - `actor_scope`: Input consumed by [`crate::runtime::json_commands::error_envelope`].
6037/// - `tenant_scope`: Input consumed by [`crate::runtime::json_commands::error_envelope`].
6038/// - `details_json`: Input consumed by [`crate::runtime::json_commands::error_envelope`].
6039///
6040/// # Returns
6041///
6042/// Returns [`Some`] when [`crate::runtime::json_commands::error_envelope`] can produce a value and [`None`] otherwise.
6043///
6044/// # Performance
6045///
6046/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::error_envelope`].
6047///
6048/// # Examples
6049///
6050/// ```rust,ignore
6051/// let _result = pharos_native::runtime::json_commands::error_envelope(todo!(), todo!(), "example", todo!(), todo!(), todo!());
6052/// ```
6053///
6054/// # Related
6055///
6056/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6057pub fn error_envelope(
6058 error_type: Option<&str>,
6059 error_code: Option<&str>,
6060 message: Option<&str>,
6061 actor_scope: Option<&str>,
6062 tenant_scope: Option<&str>,
6063 details_json: Option<&str>,
6064) -> Option<String> {
6065 let details = details_json
6066 .filter(|value| !value.is_empty())
6067 .and_then(|value| serde_json::from_str::<Value>(value).ok());
6068 let mut envelope = pharos_runtime_core::error::envelope(
6069 pharos_runtime_core::error::ErrorCode::InvalidInput,
6070 message.unwrap_or_default(),
6071 None,
6072 );
6073 let error = envelope.get_mut("error")?.as_object_mut()?;
6074 error.insert(
6075 "context".to_owned(),
6076 Value::String(error_type.unwrap_or_default().to_owned()),
6077 );
6078 error.insert(
6079 "declared_code".to_owned(),
6080 Value::String(error_code.unwrap_or_default().to_owned()),
6081 );
6082 error.insert(
6083 "actor_scope".to_owned(),
6084 Value::String(actor_scope.unwrap_or("anonymous").to_owned()),
6085 );
6086 error.insert(
6087 "tenant_scope".to_owned(),
6088 Value::String(tenant_scope.unwrap_or("global").to_owned()),
6089 );
6090 error.insert("details".to_owned(), details.unwrap_or(Value::Null));
6091 serde_json::to_string(&envelope).ok()
6092}
6093
6094/// Performs validation error envelope.
6095///
6096/// # Parameters
6097///
6098/// - `report_json`: Input consumed by [`crate::runtime::json_commands::validation_error_envelope`].
6099///
6100/// # Returns
6101///
6102/// Returns [`Some`] when [`crate::runtime::json_commands::validation_error_envelope`] can produce a value and [`None`] otherwise.
6103///
6104/// # Performance
6105///
6106/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::validation_error_envelope`].
6107///
6108/// # Examples
6109///
6110/// ```rust,ignore
6111/// let _result = pharos_native::runtime::json_commands::validation_error_envelope("{}");
6112/// ```
6113///
6114/// # Related
6115///
6116/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6117pub fn validation_error_envelope(report_json: &str) -> Option<String> {
6118 let root = serde_json::from_str::<Value>(report_json).ok()?;
6119 let actor_scope = root
6120 .get("actor_scope")
6121 .and_then(Value::as_str)
6122 .unwrap_or("anonymous");
6123 let tenant_scope = root
6124 .get("tenant_scope")
6125 .and_then(Value::as_str)
6126 .unwrap_or("global");
6127 let mut envelope = pharos_runtime_core::error::envelope(
6128 pharos_runtime_core::error::ErrorCode::InvalidInput,
6129 "Validation failed.",
6130 None,
6131 );
6132 let error = envelope.get_mut("error")?.as_object_mut()?;
6133 error.insert(
6134 "context".to_owned(),
6135 Value::String("validation_error".to_owned()),
6136 );
6137 error.insert(
6138 "declared_code".to_owned(),
6139 Value::String("validation_failed".to_owned()),
6140 );
6141 error.insert(
6142 "actor_scope".to_owned(),
6143 Value::String(actor_scope.to_owned()),
6144 );
6145 error.insert(
6146 "tenant_scope".to_owned(),
6147 Value::String(tenant_scope.to_owned()),
6148 );
6149 error.insert("details".to_owned(), root);
6150 serde_json::to_string(&envelope).ok()
6151}
6152
6153/// Handles validation empty report command.
6154///
6155/// # Parameters
6156///
6157/// - `form_id`: Input consumed by [`crate::runtime::json_commands::handle_validation_empty_report_command`].
6158/// - `action_id`: Input consumed by [`crate::runtime::json_commands::handle_validation_empty_report_command`].
6159/// - `actor_scope`: Input consumed by [`crate::runtime::json_commands::handle_validation_empty_report_command`].
6160/// - `tenant_scope`: Input consumed by [`crate::runtime::json_commands::handle_validation_empty_report_command`].
6161///
6162/// # Returns
6163///
6164/// Returns [`Some`] when [`crate::runtime::json_commands::handle_validation_empty_report_command`] can produce a value and [`None`] otherwise.
6165///
6166/// # Examples
6167///
6168/// ```rust,ignore
6169/// let _result = pharos_native::runtime::json_commands::handle_validation_empty_report_command("form", todo!(), todo!(), todo!());
6170/// ```
6171///
6172/// # Related
6173///
6174/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6175pub fn handle_validation_empty_report_command(
6176 form_id: Option<&str>,
6177 action_id: Option<&str>,
6178 actor_scope: Option<&str>,
6179 tenant_scope: Option<&str>,
6180) -> Option<String> {
6181 validation_empty_report(form_id, action_id, actor_scope, tenant_scope)
6182}
6183
6184/// Handles validation add field error command.
6185///
6186/// # Parameters
6187///
6188/// - `report_json`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_field_error_command`].
6189/// - `field_id`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_field_error_command`].
6190/// - `rule_id`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_field_error_command`].
6191/// - `code_text`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_field_error_command`].
6192/// - `message`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_field_error_command`].
6193///
6194/// # Returns
6195///
6196/// Returns [`Some`] when [`crate::runtime::json_commands::handle_validation_add_field_error_command`] can produce a value and [`None`] otherwise.
6197///
6198/// # Examples
6199///
6200/// ```rust,ignore
6201/// let _result = pharos_native::runtime::json_commands::handle_validation_add_field_error_command("{}", "field", todo!(), todo!(), "example");
6202/// ```
6203///
6204/// # Related
6205///
6206/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6207pub fn handle_validation_add_field_error_command(
6208 report_json: &str,
6209 field_id: Option<&str>,
6210 rule_id: Option<&str>,
6211 code_text: Option<&str>,
6212 message: Option<&str>,
6213) -> Option<String> {
6214 validation_add_field_error(report_json, field_id, rule_id, code_text, message)
6215}
6216
6217/// Handles validation add form error command.
6218///
6219/// # Parameters
6220///
6221/// - `report_json`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_form_error_command`].
6222/// - `rule_id`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_form_error_command`].
6223/// - `code_text`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_form_error_command`].
6224/// - `message`: Input consumed by [`crate::runtime::json_commands::handle_validation_add_form_error_command`].
6225///
6226/// # Returns
6227///
6228/// Returns [`Some`] when [`crate::runtime::json_commands::handle_validation_add_form_error_command`] can produce a value and [`None`] otherwise.
6229///
6230/// # Examples
6231///
6232/// ```rust,ignore
6233/// let _result = pharos_native::runtime::json_commands::handle_validation_add_form_error_command("{}", todo!(), todo!(), "example");
6234/// ```
6235///
6236/// # Related
6237///
6238/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6239pub fn handle_validation_add_form_error_command(
6240 report_json: &str,
6241 rule_id: Option<&str>,
6242 code_text: Option<&str>,
6243 message: Option<&str>,
6244) -> Option<String> {
6245 validation_add_form_error(report_json, rule_id, code_text, message)
6246}
6247
6248/// Handles validation has errors command.
6249///
6250/// # Parameters
6251///
6252/// - `report_json`: Input consumed by [`crate::runtime::json_commands::handle_validation_has_errors_command`].
6253///
6254/// # Returns
6255///
6256/// Returns [`Some`] when [`crate::runtime::json_commands::handle_validation_has_errors_command`] can produce a value and [`None`] otherwise.
6257///
6258/// # Examples
6259///
6260/// ```rust,ignore
6261/// let _result = pharos_native::runtime::json_commands::handle_validation_has_errors_command("{}");
6262/// ```
6263///
6264/// # Related
6265///
6266/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6267pub fn handle_validation_has_errors_command(report_json: &str) -> Option<bool> {
6268 validation_has_errors(report_json)
6269}
6270
6271/// Handles validation summary text command.
6272///
6273/// # Parameters
6274///
6275/// - `report_json`: Input consumed by [`crate::runtime::json_commands::handle_validation_summary_text_command`].
6276///
6277/// # Returns
6278///
6279/// Returns [`Some`] when [`crate::runtime::json_commands::handle_validation_summary_text_command`] can produce a value and [`None`] otherwise.
6280///
6281/// # Examples
6282///
6283/// ```rust,ignore
6284/// let _result = pharos_native::runtime::json_commands::handle_validation_summary_text_command("{}");
6285/// ```
6286///
6287/// # Related
6288///
6289/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6290pub fn handle_validation_summary_text_command(report_json: &str) -> Option<String> {
6291 validation_summary_text(report_json)
6292}
6293
6294/// Handles validation field list HTML command.
6295///
6296/// # Parameters
6297///
6298/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_validation_field_list_html_command`].
6299/// - `report_json`: Input consumed by [`crate::runtime::json_commands::handle_validation_field_list_html_command`].
6300///
6301/// # Returns
6302///
6303/// Returns [`Some`] when [`crate::runtime::json_commands::handle_validation_field_list_html_command`] can produce a value and [`None`] otherwise.
6304///
6305/// # Examples
6306///
6307/// ```rust,ignore
6308/// let _result = pharos_native::runtime::json_commands::handle_validation_field_list_html_command(std::path::Path::new("apps/dev_docs"), "{}");
6309/// ```
6310///
6311/// # Related
6312///
6313/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6314pub fn handle_validation_field_list_html_command(
6315 app_root: &Path,
6316 report_json: &str,
6317) -> Option<String> {
6318 validation_field_list_html(app_root, report_json)
6319}
6320
6321/// Handles error envelope command.
6322///
6323/// # Parameters
6324///
6325/// - `error_type`: Input consumed by [`crate::runtime::json_commands::handle_error_envelope_command`].
6326/// - `error_code`: Input consumed by [`crate::runtime::json_commands::handle_error_envelope_command`].
6327/// - `message`: Input consumed by [`crate::runtime::json_commands::handle_error_envelope_command`].
6328/// - `actor_scope`: Input consumed by [`crate::runtime::json_commands::handle_error_envelope_command`].
6329/// - `tenant_scope`: Input consumed by [`crate::runtime::json_commands::handle_error_envelope_command`].
6330/// - `details_json`: Input consumed by [`crate::runtime::json_commands::handle_error_envelope_command`].
6331///
6332/// # Returns
6333///
6334/// Returns [`Some`] when [`crate::runtime::json_commands::handle_error_envelope_command`] can produce a value and [`None`] otherwise.
6335///
6336/// # Examples
6337///
6338/// ```rust,ignore
6339/// let _result = pharos_native::runtime::json_commands::handle_error_envelope_command(todo!(), todo!(), "example", todo!(), todo!(), todo!());
6340/// ```
6341///
6342/// # Related
6343///
6344/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6345pub fn handle_error_envelope_command(
6346 error_type: Option<&str>,
6347 error_code: Option<&str>,
6348 message: Option<&str>,
6349 actor_scope: Option<&str>,
6350 tenant_scope: Option<&str>,
6351 details_json: Option<&str>,
6352) -> Option<String> {
6353 error_envelope(
6354 error_type,
6355 error_code,
6356 message,
6357 actor_scope,
6358 tenant_scope,
6359 details_json,
6360 )
6361}
6362
6363/// Handles validation error envelope command.
6364///
6365/// # Parameters
6366///
6367/// - `report_json`: Input consumed by [`crate::runtime::json_commands::handle_validation_error_envelope_command`].
6368///
6369/// # Returns
6370///
6371/// Returns [`Some`] when [`crate::runtime::json_commands::handle_validation_error_envelope_command`] can produce a value and [`None`] otherwise.
6372///
6373/// # Examples
6374///
6375/// ```rust,ignore
6376/// let _result = pharos_native::runtime::json_commands::handle_validation_error_envelope_command("{}");
6377/// ```
6378///
6379/// # Related
6380///
6381/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6382pub fn handle_validation_error_envelope_command(report_json: &str) -> Option<String> {
6383 validation_error_envelope(report_json)
6384}
6385
6386/// Performs route template params.
6387fn route_template_params(
6388 template_path: &str,
6389 request_path: &str,
6390) -> Option<serde_json::Map<String, Value>> {
6391 if template_path.is_empty() || request_path.is_empty() {
6392 return None;
6393 }
6394 let template_segments: Vec<&str> = template_path.trim_matches('/').split('/').collect();
6395 let request_segments: Vec<&str> = request_path
6396 .split('?')
6397 .next()
6398 .unwrap_or_default()
6399 .trim_matches('/')
6400 .split('/')
6401 .collect();
6402 if template_segments.len() != request_segments.len() {
6403 return None;
6404 }
6405 let mut params = serde_json::Map::new();
6406 for (template, actual) in template_segments.iter().zip(request_segments.iter()) {
6407 if let Some(name) = template
6408 .strip_prefix('{')
6409 .and_then(|value| value.strip_suffix('}'))
6410 {
6411 if name.is_empty() || actual.is_empty() {
6412 return None;
6413 }
6414 params.insert(name.to_owned(), Value::String((*actual).to_owned()));
6415 } else if template != actual {
6416 return None;
6417 }
6418 }
6419 Some(params)
6420}
6421
6422/// Matches one request against generated API/admin operation declarations in a contract file.
6423///
6424/// # Parameters
6425///
6426/// - `file_path`: Input consumed by [`crate::runtime::json_commands::file_generated_surface_match`].
6427/// - `method`: Input consumed by [`crate::runtime::json_commands::file_generated_surface_match`].
6428/// - `request_path`: Input consumed by [`crate::runtime::json_commands::file_generated_surface_match`].
6429///
6430/// # Returns
6431///
6432/// Returns [`Some`] when [`crate::runtime::json_commands::file_generated_surface_match`] can produce a value and [`None`] otherwise.
6433///
6434/// # Performance
6435///
6436/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::file_generated_surface_match`].
6437///
6438/// # Examples
6439///
6440/// ```rust,ignore
6441/// let _result = pharos_native::runtime::json_commands::file_generated_surface_match(todo!(), todo!(), "/");
6442/// ```
6443///
6444/// # Related
6445///
6446/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6447pub fn file_generated_surface_match(
6448 file_path: &Path,
6449 method: &str,
6450 request_path: &str,
6451) -> Option<String> {
6452 if file_path.as_os_str().is_empty() || method.is_empty() || request_path.is_empty() {
6453 return None;
6454 }
6455 let root = read_json_file(file_path)?;
6456 for surface in root.get("surfaces").and_then(Value::as_array)? {
6457 if !matches!(
6458 surface.get("surface_kind").and_then(Value::as_str),
6459 Some("generated_api") | Some("generated_admin")
6460 ) {
6461 continue;
6462 }
6463 let Some(operations) = surface.get("operations").and_then(Value::as_object) else {
6464 continue;
6465 };
6466 for (operation_name, operation_config) in operations {
6467 let operation_method = operation_config
6468 .get("method")
6469 .and_then(Value::as_str)
6470 .unwrap_or("GET");
6471 let operation_path = operation_config.get("path").and_then(Value::as_str)?;
6472 if operation_method != method {
6473 continue;
6474 }
6475 let Some(params) = route_template_params(operation_path, request_path) else {
6476 continue;
6477 };
6478 return serde_json::to_string(&serde_json::json!({
6479 "surface": surface,
6480 "operation": operation_name,
6481 "operation_config": operation_config,
6482 "params": Value::Object(params),
6483 }))
6484 .ok();
6485 }
6486 }
6487 None
6488}
6489
6490/// Performs generated surface label.
6491fn generated_surface_label(field_name: &str) -> String {
6492 field_name
6493 .split('_')
6494 .filter(|part| !part.is_empty())
6495 .map(|part| {
6496 let mut chars = part.chars();
6497 match chars.next() {
6498 Some(first) => format!("{}{}", first.to_ascii_uppercase(), chars.as_str()),
6499 None => String::new(),
6500 }
6501 })
6502 .collect::<Vec<_>>()
6503 .join(" ")
6504}
6505
6506/// Returns generated-admin list-column metadata, applying the native fallback contract.
6507///
6508/// # Parameters
6509///
6510/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_list_columns`].
6511///
6512/// # Returns
6513///
6514/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_list_columns`] can produce a value and [`None`] otherwise.
6515///
6516/// # Performance
6517///
6518/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_list_columns`].
6519///
6520/// # Examples
6521///
6522/// ```rust,ignore
6523/// let _result = pharos_native::runtime::json_commands::generated_surface_list_columns("{}");
6524/// ```
6525///
6526/// # Related
6527///
6528/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6529pub fn generated_surface_list_columns(surface_json: &str) -> Option<String> {
6530 let root = serde_json::from_str::<Value>(surface_json).ok()?;
6531 if let Some(columns) = root
6532 .get("admin")
6533 .and_then(|value| value.get("list_columns"))
6534 .filter(|value| value.is_array())
6535 {
6536 return serde_json::to_string(columns).ok();
6537 }
6538 let mut columns = Vec::new();
6539 for field in root.get("response_fields").and_then(Value::as_array)? {
6540 let Some(field_name) = field.as_str() else {
6541 continue;
6542 };
6543 if matches!(field_name, "id" | "business_id") || columns.len() >= 4 {
6544 continue;
6545 }
6546 columns.push(serde_json::json!({
6547 "field": field_name,
6548 "label": generated_surface_label(field_name),
6549 }));
6550 }
6551 serde_json::to_string(&Value::Array(columns)).ok()
6552}
6553
6554/// Builds the context and identity parameter object used by generated routes.
6555///
6556/// # Parameters
6557///
6558/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_params`].
6559/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_params`].
6560/// - `identity_value`: Input consumed by [`crate::runtime::json_commands::generated_surface_params`].
6561///
6562/// # Returns
6563///
6564/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_params`] can produce a value and [`None`] otherwise.
6565///
6566/// # Performance
6567///
6568/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_params`].
6569///
6570/// # Examples
6571///
6572/// ```rust,ignore
6573/// let _result = pharos_native::runtime::json_commands::generated_surface_params("{}", todo!(), todo!());
6574/// ```
6575///
6576/// # Related
6577///
6578/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6579pub fn generated_surface_params(
6580 surface_json: &str,
6581 context_json: &str,
6582 identity_value: Option<&str>,
6583) -> Option<String> {
6584 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
6585 let context = serde_json::from_str::<Value>(context_json).ok()?;
6586 let mut params = serde_json::Map::new();
6587 if let Some(param) = surface
6588 .get("context_binding")
6589 .and_then(|value| value.get("param"))
6590 .and_then(Value::as_str)
6591 .filter(|value| !value.is_empty())
6592 && let Some(value) = context.get("context_value")
6593 && !value.is_null()
6594 {
6595 params.insert(param.to_owned(), value.clone());
6596 }
6597 if let Some(param) = surface
6598 .get("identity_param")
6599 .and_then(Value::as_str)
6600 .filter(|value| !value.is_empty())
6601 && let Some(identity_value) = identity_value.filter(|value| !value.is_empty())
6602 {
6603 params.insert(param.to_owned(), Value::String(identity_value.to_owned()));
6604 }
6605 serde_json::to_string(&Value::Object(params)).ok()
6606}
6607
6608/// Converts one generated-surface field value to the native display-text contract.
6609///
6610/// # Parameters
6611///
6612/// - `item_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_display_value`].
6613/// - `field_name`: Input consumed by [`crate::runtime::json_commands::generated_surface_display_value`].
6614///
6615/// # Returns
6616///
6617/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_display_value`] can produce a value and [`None`] otherwise.
6618///
6619/// # Performance
6620///
6621/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_display_value`].
6622///
6623/// # Examples
6624///
6625/// ```rust,ignore
6626/// let _result = pharos_native::runtime::json_commands::generated_surface_display_value("{}", todo!());
6627/// ```
6628///
6629/// # Related
6630///
6631/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6632pub fn generated_surface_display_value(item_json: &str, field_name: &str) -> Option<String> {
6633 if field_name.is_empty() {
6634 return None;
6635 }
6636 let root = serde_json::from_str::<Value>(item_json).ok()?;
6637 let value = root.get(field_name);
6638 match value {
6639 None | Some(Value::Null) => Some(String::new()),
6640 Some(Value::Array(items)) => Some(
6641 items
6642 .iter()
6643 .map(|item| match item {
6644 Value::Bool(flag) => {
6645 if *flag {
6646 "yes".to_owned()
6647 } else {
6648 "no".to_owned()
6649 }
6650 }
6651 _ => value_to_text(item),
6652 })
6653 .collect::<Vec<_>>()
6654 .join(", "),
6655 ),
6656 Some(Value::Bool(flag)) => Some(if *flag { "yes" } else { "no" }.to_owned()),
6657 Some(value) => Some(value_to_text(value)),
6658 }
6659}
6660
6661/// Performs value to text.
6662fn value_to_text(value: &Value) -> String {
6663 match value {
6664 Value::Null => String::new(),
6665 Value::String(text) => text.clone(),
6666 Value::Bool(flag) => flag.to_string(),
6667 Value::Number(number) => number.to_string(),
6668 _ => serde_json::to_string(value).unwrap_or_default(),
6669 }
6670}
6671
6672/// Renders UI element HTML.
6673fn render_ui_element_html(
6674 app_root: &Path,
6675 element_type: &str,
6676 input_type: Option<&str>,
6677 id: &str,
6678 name: &str,
6679 label: &str,
6680 value: &str,
6681 options_html: &str,
6682) -> Option<String> {
6683 let params = super::ui::ui_render_param_map(
6684 id,
6685 input_type,
6686 Some(if element_type == "button" {
6687 "submit"
6688 } else {
6689 "button"
6690 }),
6691 name,
6692 label,
6693 label,
6694 value,
6695 None,
6696 None,
6697 "",
6698 options_html,
6699 "",
6700 value,
6701 element_type,
6702 )?;
6703 let fragment_path = match element_type {
6704 "select" => "templates/shared/fragments/ui_select.html",
6705 "textarea" => "templates/shared/fragments/ui_textarea.html",
6706 "checkbox" => "templates/shared/fragments/ui_checkbox.html",
6707 _ => "templates/shared/fragments/ui_input.html",
6708 };
6709 generated::render_generated_admin_fragment_from_value_runtime(app_root, fragment_path, ¶ms)
6710}
6711
6712/// Renders select options from one generated-surface field declaration.
6713///
6714/// # Parameters
6715///
6716/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_select_options_html`].
6717/// - `field_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_select_options_html`].
6718/// - `selected_value`: Input consumed by [`crate::runtime::json_commands::generated_surface_select_options_html`].
6719///
6720/// # Returns
6721///
6722/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_select_options_html`] can produce a value and [`None`] otherwise.
6723///
6724/// # Performance
6725///
6726/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_select_options_html`].
6727///
6728/// # Examples
6729///
6730/// ```rust,ignore
6731/// let _result = pharos_native::runtime::json_commands::generated_surface_select_options_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
6732/// ```
6733///
6734/// # Related
6735///
6736/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6737pub fn generated_surface_select_options_html(
6738 app_root: &Path,
6739 field_json: &str,
6740 selected_value: Option<&str>,
6741) -> Option<String> {
6742 if app_root.as_os_str().is_empty() {
6743 return None;
6744 }
6745 let field = serde_json::from_str::<Value>(field_json).ok()?;
6746 let mut out = String::new();
6747 let mut selected_found = false;
6748 for option in field.get("options").and_then(Value::as_array)? {
6749 let value = option
6750 .get("value")
6751 .map(value_to_text)
6752 .filter(|value| !value.is_empty())
6753 .or_else(|| option.get("id").map(value_to_text))
6754 .or_else(|| option.get("name").map(value_to_text))?;
6755 let label = option
6756 .get("label")
6757 .map(value_to_text)
6758 .filter(|value| !value.is_empty())
6759 .or_else(|| option.get("name").map(value_to_text))
6760 .unwrap_or_else(|| value.clone());
6761 let selected = selected_value == Some(value.as_str());
6762 selected_found |= selected;
6763 out.push_str(&render_fragment_with_params(
6764 app_root,
6765 "templates/shared/fragments/select_option.html",
6766 serde_json::Map::from_iter([
6767 ("option_value".to_owned(), Value::String(value)),
6768 (
6769 "selected_attr".to_owned(),
6770 Value::String(if selected { " selected" } else { "" }.to_owned()),
6771 ),
6772 ("option_label".to_owned(), Value::String(label)),
6773 ]),
6774 )?);
6775 }
6776 if !selected_found && let Some(selected) = selected_value.filter(|value| !value.is_empty()) {
6777 out.push_str(&render_fragment_with_params(
6778 app_root,
6779 "templates/shared/fragments/select_option.html",
6780 serde_json::Map::from_iter([
6781 (
6782 "option_value".to_owned(),
6783 Value::String(selected.to_owned()),
6784 ),
6785 (
6786 "selected_attr".to_owned(),
6787 Value::String(" selected".to_owned()),
6788 ),
6789 (
6790 "option_label".to_owned(),
6791 Value::String(selected.to_owned()),
6792 ),
6793 ]),
6794 )?);
6795 }
6796 Some(out)
6797}
6798
6799/// Renders generated-surface filter controls from a query string and declaration.
6800///
6801/// # Parameters
6802///
6803/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_filter_controls_html`].
6804/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_filter_controls_html`].
6805/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::generated_surface_filter_controls_html`].
6806///
6807/// # Returns
6808///
6809/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_filter_controls_html`] can produce a value and [`None`] otherwise.
6810///
6811/// # Performance
6812///
6813/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_filter_controls_html`].
6814///
6815/// # Examples
6816///
6817/// ```rust,ignore
6818/// let _result = pharos_native::runtime::json_commands::generated_surface_filter_controls_html(std::path::Path::new("apps/dev_docs"), "{}", todo!());
6819/// ```
6820///
6821/// # Related
6822///
6823/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6824pub fn generated_surface_filter_controls_html(
6825 app_root: &Path,
6826 surface_json: &str,
6827 raw_query: &str,
6828) -> Option<String> {
6829 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
6830 let mut out = String::new();
6831 for filter in surface.get("filters").and_then(Value::as_array)? {
6832 let query = filter
6833 .get("query")
6834 .and_then(Value::as_str)
6835 .unwrap_or_default();
6836 let field = filter.get("field").and_then(Value::as_str).unwrap_or(query);
6837 if query.is_empty() {
6838 continue;
6839 }
6840 let label = filter
6841 .get("label")
6842 .and_then(Value::as_str)
6843 .filter(|value| !value.is_empty())
6844 .unwrap_or(if field.is_empty() { query } else { field });
6845 let match_kind = filter
6846 .get("match")
6847 .and_then(Value::as_str)
6848 .filter(|value| !value.is_empty())
6849 .unwrap_or("exact");
6850 let value = request_query_value(raw_query, query)
6851 .filter(|value| !value.is_empty())
6852 .or_else(|| filter.get("default").map(value_to_text))
6853 .unwrap_or_default();
6854 let options = if match_kind == "boolean" {
6855 render_fragment_with_params(
6856 app_root,
6857 "templates/shared/fragments/boolean_filter_options.html",
6858 serde_json::Map::from_iter([
6859 (
6860 "selected_true_attr".to_owned(),
6861 Value::String(
6862 if value.is_empty() || value == "true" {
6863 " selected"
6864 } else {
6865 ""
6866 }
6867 .to_owned(),
6868 ),
6869 ),
6870 (
6871 "selected_false_attr".to_owned(),
6872 Value::String(if value == "false" { " selected" } else { "" }.to_owned()),
6873 ),
6874 (
6875 "selected_all_attr".to_owned(),
6876 Value::String(if value == "all" { " selected" } else { "" }.to_owned()),
6877 ),
6878 ]),
6879 )?
6880 } else {
6881 let mut html = render_fragment_with_params(
6882 app_root,
6883 "templates/shared/fragments/select_option.html",
6884 serde_json::Map::from_iter([
6885 ("option_value".to_owned(), Value::String(String::new())),
6886 (
6887 "selected_attr".to_owned(),
6888 Value::String(if value.is_empty() { " selected" } else { "" }.to_owned()),
6889 ),
6890 ("option_label".to_owned(), Value::String("All".to_owned())),
6891 ]),
6892 )?;
6893 html.push_str(&generated_surface_select_options_html(
6894 app_root,
6895 &serde_json::to_string(filter).ok()?,
6896 Some(&value),
6897 )?);
6898 html
6899 };
6900 let element = if match_kind == "boolean" || filter.get("options").is_some() {
6901 "select"
6902 } else {
6903 "input"
6904 };
6905 let html = render_ui_element_html(
6906 app_root,
6907 element,
6908 Some(if match_kind == "array_contains_integer" {
6909 "number"
6910 } else {
6911 "text"
6912 }),
6913 query,
6914 query,
6915 label,
6916 &value,
6917 &options,
6918 )?;
6919 out.push_str(&html);
6920 }
6921 Some(out)
6922}
6923
6924/// Renders generated-surface sort options using the native selected-sort rules.
6925///
6926/// # Parameters
6927///
6928/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_sort_options_html`].
6929/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_sort_options_html`].
6930/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::generated_surface_sort_options_html`].
6931///
6932/// # Returns
6933///
6934/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_sort_options_html`] can produce a value and [`None`] otherwise.
6935///
6936/// # Performance
6937///
6938/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_sort_options_html`].
6939///
6940/// # Examples
6941///
6942/// ```rust,ignore
6943/// let _result = pharos_native::runtime::json_commands::generated_surface_sort_options_html(std::path::Path::new("apps/dev_docs"), "{}", todo!());
6944/// ```
6945///
6946/// # Related
6947///
6948/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
6949pub fn generated_surface_sort_options_html(
6950 app_root: &Path,
6951 surface_json: &str,
6952 raw_query: &str,
6953) -> Option<String> {
6954 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
6955 let current = request_query_value(raw_query, "sort")
6956 .filter(|value| !value.is_empty())
6957 .or_else(|| {
6958 surface
6959 .get("default_sort")
6960 .and_then(Value::as_str)
6961 .map(ToOwned::to_owned)
6962 })
6963 .unwrap_or_default();
6964 let mut out = String::new();
6965 for sort in surface.get("sorts").and_then(Value::as_array)? {
6966 let key = sort.get("key").and_then(Value::as_str).unwrap_or_default();
6967 if key.is_empty() {
6968 continue;
6969 }
6970 let label = sort
6971 .get("label")
6972 .and_then(Value::as_str)
6973 .filter(|value| !value.is_empty())
6974 .unwrap_or(key);
6975 out.push_str(&render_fragment_with_params(
6976 app_root,
6977 "templates/shared/fragments/select_option.html",
6978 serde_json::Map::from_iter([
6979 ("option_value".to_owned(), Value::String(key.to_owned())),
6980 (
6981 "selected_attr".to_owned(),
6982 Value::String(if current == key { " selected" } else { "" }.to_owned()),
6983 ),
6984 ("option_label".to_owned(), Value::String(label.to_owned())),
6985 ]),
6986 )?);
6987 }
6988 Some(out)
6989}
6990
6991/// Performs state option i64.
6992fn state_option_i64(options: &Value, key: &str, default_value: i64) -> i64 {
6993 options
6994 .get(key)
6995 .and_then(|value| match value {
6996 Value::Number(number) => number.as_i64(),
6997 Value::String(text) => text.parse::<i64>().ok(),
6998 _ => None,
6999 })
7000 .unwrap_or(default_value)
7001}
7002
7003/// Performs state option string.
7004fn state_option_string<'a>(options: &'a Value, key: &str) -> Option<&'a str> {
7005 options
7006 .get(key)
7007 .and_then(Value::as_str)
7008 .filter(|value| !value.is_empty())
7009}
7010
7011/// Performs state rows.
7012fn state_rows<'a>(root: &'a Value, section: &str) -> &'a [Value] {
7013 root.get(section)
7014 .and_then(Value::as_array)
7015 .map(Vec::as_slice)
7016 .unwrap_or(&[])
7017}
7018
7019/// Performs state row by ID.
7020fn state_row_by_id<'a>(root: &'a Value, section: &str, id: i64) -> Option<&'a Value> {
7021 (id > 0).then_some(())?;
7022 state_rows(root, section).iter().find(|row| {
7023 row.get("id").and_then(Value::as_i64) == Some(id)
7024 || row
7025 .get("id")
7026 .and_then(Value::as_str)
7027 .and_then(|value| value.parse::<i64>().ok())
7028 == Some(id)
7029 })
7030}
7031
7032/// Performs state row by text.
7033fn state_row_by_text<'a>(
7034 root: &'a Value,
7035 section: &str,
7036 field: &str,
7037 wanted: &str,
7038) -> Option<&'a Value> {
7039 if field.is_empty() || wanted.is_empty() {
7040 return None;
7041 }
7042 state_rows(root, section)
7043 .iter()
7044 .find(|row| row.get(field).and_then(Value::as_str) == Some(wanted))
7045}
7046
7047/// Performs state JSON or empty.
7048fn state_json_or_empty(value: Option<&Value>) -> String {
7049 value
7050 .and_then(|value| serde_json::to_string(value).ok())
7051 .unwrap_or_else(|| "{}".to_owned())
7052}
7053
7054/// Performs state lines.
7055fn state_lines(rows: &[Value], field: &str) -> String {
7056 rows.iter()
7057 .filter_map(|row| row.get(field))
7058 .map(value_to_text)
7059 .filter(|value| !value.is_empty())
7060 .collect::<Vec<_>>()
7061 .join("\n")
7062}
7063
7064/// Performs state next ID.
7065fn state_next_id(root: &Value, section: &str) -> i64 {
7066 state_rows(root, section)
7067 .iter()
7068 .filter_map(|row| row.get("id"))
7069 .filter_map(|value| {
7070 value
7071 .as_i64()
7072 .or_else(|| value.as_str()?.parse::<i64>().ok())
7073 })
7074 .max()
7075 .unwrap_or(0)
7076 + 1
7077}
7078
7079/// Performs state active token.
7080fn state_active_token<'a>(
7081 root: &'a Value,
7082 section: &str,
7083 token_hash: &str,
7084 now_iso: &str,
7085) -> Option<&'a Value> {
7086 state_rows(root, section).iter().find(|row| {
7087 row.get("token_hash").and_then(Value::as_str) == Some(token_hash)
7088 && row
7089 .get("claimed_at")
7090 .is_none_or(|value| value.is_null() || value.as_str() == Some(""))
7091 && row
7092 .get("expires_at")
7093 .and_then(Value::as_str)
7094 .is_some_and(|expires| expires > now_iso)
7095 })
7096}
7097
7098/// Performs state write.
7099fn state_write(root: &Value, state_path: &Path) -> Result<String, i32> {
7100 if generated::write_json_file(state_path, root) {
7101 Ok(String::new())
7102 } else {
7103 Err(69)
7104 }
7105}
7106
7107/// Executes one state JSON command against the persisted runtime-state contract.
7108///
7109/// # Parameters
7110///
7111/// - `state_path`: Input consumed by [`crate::runtime::json_commands::state_named_command`].
7112/// - `name`: Input consumed by [`crate::runtime::json_commands::state_named_command`].
7113/// - `options_json`: Input consumed by [`crate::runtime::json_commands::state_named_command`].
7114///
7115/// # Returns
7116///
7117/// Returns the successful value produced by [`crate::runtime::json_commands::state_named_command`] or an error when the operation fails.
7118///
7119/// # Errors
7120///
7121/// Returns an error value when [`crate::runtime::json_commands::state_named_command`] cannot complete successfully.
7122///
7123/// # Performance
7124///
7125/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::state_named_command`].
7126///
7127/// # Examples
7128///
7129/// ```rust,ignore
7130/// let _result = pharos_native::runtime::json_commands::state_named_command(todo!(), "name", todo!());
7131/// ```
7132///
7133/// # Related
7134///
7135/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
7136pub fn state_named_command(
7137 state_path: &Path,
7138 name: &str,
7139 options_json: &str,
7140) -> Result<String, i32> {
7141 if state_path.as_os_str().is_empty() || name.is_empty() {
7142 return Err(64);
7143 }
7144 let mut root = read_json_file(state_path).ok_or(66)?;
7145 if !root.is_object() {
7146 return Err(66);
7147 }
7148 let options = if options_json.is_empty() {
7149 serde_json::json!({})
7150 } else {
7151 serde_json::from_str::<Value>(options_json).map_err(|_| 64)?
7152 };
7153 if !options.is_object() {
7154 return Err(64);
7155 }
7156
7157 let id = |key: &str| state_option_i64(&options, key, 0);
7158 let text = |key: &str| state_option_string(&options, key).unwrap_or_default();
7159 let output_json = |value: Option<&Value>| Ok(state_json_or_empty(value));
7160
7161 match name {
7162 "import-row-json-lines" => {
7163 let section = text("section");
7164 let rows = options.get("rows").and_then(Value::as_array).ok_or(64)?.clone();
7165 if section.is_empty() { return Err(64); }
7166 let target = root.as_object_mut().ok_or(66)?.entry(section.to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)?;
7167 target.extend(rows.into_iter().filter(Value::is_object));
7168 state_write(&root, state_path)
7169 }
7170 "business-json-by-slug" => output_json(generated::dynamic_find_business_by_slug(&root, text("slug"))),
7171 "business-id-by-slug" => Ok(state_row_by_text(&root, "businesses", "slug", text("slug"))
7172 .and_then(|row| row.get("id"))
7173 .map(value_to_text)
7174 .unwrap_or_else(|| "0".to_owned())),
7175 "tenant-id-for-business-slug" => {
7176 let business_id = state_row_by_text(&root, "businesses", "slug", text("slug"))
7177 .and_then(|row| row.get("id"))
7178 .and_then(|value| value.as_i64())
7179 .unwrap_or(0);
7180 Ok(super::business::tenant_id_for_business_base(&root, business_id).to_string())
7181 }
7182 "tenant-id-for-business-id" => Ok(super::business::tenant_id_for_business_base(&root, id("business_id")).to_string()),
7183 "tenant-id-for-service-id" => {
7184 let service_id = id("service_id");
7185 let business_id = state_row_by_id(&root, "services", service_id)
7186 .and_then(|row| row.get("business_id"))
7187 .and_then(Value::as_i64)
7188 .unwrap_or(0);
7189 Ok(super::business::tenant_id_for_business_base(&root, business_id).to_string())
7190 }
7191 "tenant-id-for-technician-id" => {
7192 let business_id = state_row_by_id(&root, "technicians", id("technician_id"))
7193 .and_then(|row| row.get("business_id"))
7194 .and_then(Value::as_i64)
7195 .unwrap_or(0);
7196 Ok(super::business::tenant_id_for_business_base(&root, business_id).to_string())
7197 }
7198 "appointment-json-by-id" => output_json(state_row_by_id(&root, "appointments", id("appointment_id"))),
7199 "service-json-by-id" => output_json(state_row_by_id(&root, "services", id("service_id"))),
7200 "service-duration-minutes" => Ok(state_row_by_id(&root, "services", id("service_id"))
7201 .and_then(|row| row.get("duration_minutes"))
7202 .map(value_to_text)
7203 .unwrap_or_else(|| "45".to_owned())),
7204 "user-json-by-id" => output_json(state_row_by_id(&root, "users", id("user_id"))),
7205 "user-json-by-username" => output_json(state_row_by_text(&root, "users", "username", text("username"))),
7206 "user-json-by-email" => output_json(state_rows(&root, "users").iter().find(|row| {
7207 row.get("email").and_then(Value::as_str).is_some_and(|email| email.eq_ignore_ascii_case(text("email")))
7208 })),
7209 "business-name-by-id" | "business-slug-by-id" | "business-time-format-by-id" => {
7210 let field = match name {
7211 "business-name-by-id" => "name",
7212 "business-slug-by-id" => "slug",
7213 _ => "time_format",
7214 };
7215 Ok(state_row_by_id(&root, "businesses", id("business_id"))
7216 .and_then(|row| row.get(field))
7217 .map(value_to_text)
7218 .unwrap_or_else(|| if field == "time_format" { "am_pm" } else { "" }.to_owned()))
7219 }
7220 "business-image-url-by-slug" => Ok(state_row_by_text(&root, "businesses", "slug", text("slug"))
7221 .and_then(|row| row.get("image_url"))
7222 .map(value_to_text)
7223 .unwrap_or_default()),
7224 "service-image-url-for-business" => Ok(state_rows(&root, "services").iter().find(|row| {
7225 row.get("id").and_then(Value::as_i64) == Some(id("service_id"))
7226 && row.get("business_id").and_then(Value::as_i64) == Some(id("business_id"))
7227 }).and_then(|row| row.get("image_url")).map(value_to_text).unwrap_or_default()),
7228 "service-json-for-business" => output_json(state_rows(&root, "services").iter().find(|row| {
7229 row.get("id").and_then(Value::as_i64) == Some(id("service_id"))
7230 && row.get("business_id").and_then(Value::as_i64) == Some(id("business_id"))
7231 })),
7232 "first-service-json-for-business" => output_json(state_rows(&root, "services").iter().filter(|row| {
7233 row.get("business_id").and_then(Value::as_i64) == Some(id("business_id"))
7234 }).min_by_key(|row| row.get("sort_order").and_then(Value::as_i64).unwrap_or_else(|| row.get("id").and_then(Value::as_i64).unwrap_or(i64::MAX)))),
7235 "service-sort-order-for-business" => Ok(state_rows(&root, "services").iter().find(|row| {
7236 row.get("id").and_then(Value::as_i64) == Some(id("service_id"))
7237 && row.get("business_id").and_then(Value::as_i64) == Some(id("business_id"))
7238 }).and_then(|row| row.get("sort_order")).map(value_to_text).unwrap_or_else(|| id("service_id").max(1).to_string())),
7239 "business-availability-json-for-business" => output_json(state_rows(&root, "business_availability").iter().find(|row| {
7240 row.get("id").and_then(Value::as_i64) == Some(id("availability_id"))
7241 && row.get("business_id").and_then(Value::as_i64) == Some(id("business_id"))
7242 })),
7243 "technician-json-for-business" => output_json(state_rows(&root, "technicians").iter().find(|row| {
7244 row.get("id").and_then(Value::as_i64) == Some(id("technician_id"))
7245 && row.get("business_id").and_then(Value::as_i64) == Some(id("business_id"))
7246 })),
7247 "booking-json-for-user" => output_json(state_rows(&root, "bookings").iter().find(|row| {
7248 row.get("id").and_then(Value::as_i64) == Some(id("booking_id"))
7249 && row.get("user_id").and_then(Value::as_i64) == Some(id("user_id"))
7250 })),
7251 "service-booking-options-array-json" => Ok(state_row_by_id(&root, "services", id("service_id"))
7252 .and_then(|row| row.get("booking_options"))
7253 .and_then(|value| serde_json::to_string(value).ok())
7254 .unwrap_or_else(|| "[]".to_owned())),
7255 "service-booking-options-json" => {
7256 let service = state_row_by_id(&root, "services", id("service_id"));
7257 let mut rows = Vec::new();
7258 if let Some(service) = service {
7259 rows.push(serde_json::json!({
7260 "label": service.get("name").cloned().unwrap_or(Value::String(String::new())),
7261 "duration_minutes": service.get("duration_minutes").cloned().unwrap_or(Value::Number(45.into())),
7262 "cost": service.get("cost").cloned().unwrap_or(Value::String(String::new())),
7263 }));
7264 if let Some(options) = service.get("booking_options").and_then(Value::as_array) {
7265 rows.extend(options.iter().cloned());
7266 }
7267 }
7268 Ok(serde_json::to_string(&rows).unwrap_or_else(|_| "[]".to_owned()))
7269 }
7270 "service-selected-booking-option-json" => {
7271 let options = state_row_by_id(&root, "services", id("service_id"))
7272 .and_then(|row| row.get("booking_options"))
7273 .and_then(Value::as_array);
7274 let index = id("booking_option_index").max(0) as usize;
7275 output_json(options.and_then(|items| items.get(index).or_else(|| items.first())))
7276 }
7277 "active-email-verification-json" => output_json(state_active_token(&root, "email_verifications", text("token_hash"), text("now_iso"))),
7278 "active-password-reset-json" => output_json(state_active_token(&root, "password_resets", text("token_hash"), text("now_iso"))),
7279 "active-invite-json" => output_json(state_active_token(&root, "invites", text("token_hash"), text("now_iso"))),
7280 "active-session-json" => output_json(state_rows(&root, "sessions").iter().find(|row| {
7281 row.get("session_token_hash").and_then(Value::as_str) == Some(text("session_token_hash"))
7282 && row.get("revoked_at").is_none_or(|value| value.is_null() || value.as_str() == Some(""))
7283 && row.get("expires_at").and_then(Value::as_str).is_some_and(|expires| expires > text("now_iso"))
7284 })),
7285 "current-user-json" => {
7286 let user_id = state_rows(&root, "sessions").iter().find(|row| {
7287 row.get("session_token_hash").and_then(Value::as_str) == Some(text("session_token_hash"))
7288 && row.get("revoked_at").is_none_or(|value| value.is_null() || value.as_str() == Some(""))
7289 && row.get("expires_at").and_then(Value::as_str).is_some_and(|expires| expires > text("now_iso"))
7290 }).and_then(|row| row.get("user_id")).and_then(Value::as_i64).unwrap_or(0);
7291 output_json(state_row_by_id(&root, "users", user_id).filter(|row| row.get("is_active").and_then(Value::as_bool) != Some(false)))
7292 }
7293 "authenticate-user-json" => output_json(state_row_by_text(&root, "users", "username", text("username")).filter(|row| row.get("is_active").and_then(Value::as_bool) != Some(false))),
7294 "active-tenant-ids-lines" => Ok(state_lines(state_rows(&root, "tenants"), "id")),
7295 "user-membership-tenant-ids-json" => {
7296 let ids: Vec<Value> = state_rows(&root, "tenant_memberships").iter().filter(|row| {
7297 row.get("user_id").and_then(Value::as_i64) == Some(id("user_id"))
7298 && !matches!(row.get("membership_status").and_then(Value::as_str), Some("disabled" | "revoked"))
7299 }).filter_map(|row| row.get("tenant_id").cloned()).collect();
7300 Ok(serde_json::to_string(&ids).unwrap_or_else(|_| "[]".to_owned()))
7301 }
7302 "next-id" => Ok(state_next_id(&root, text("section")).to_string()),
7303 "append-record" => {
7304 let section = text("section");
7305 let record = options.get("record_json").cloned().ok_or(64)?;
7306 if !record.is_object() || section.is_empty() { return Err(64); }
7307 let root_map = root.as_object_mut().ok_or(66)?;
7308 let rows = root_map.entry(section.to_owned()).or_insert_with(|| Value::Array(Vec::new()));
7309 rows.as_array_mut().ok_or(69)?.push(record);
7310 state_write(&root, state_path)
7311 }
7312 "update-record-by-id" => {
7313 let section = text("section");
7314 let record_id = id("record_id");
7315 let patch = options.get("patch_json").and_then(Value::as_object).ok_or(64)?.clone();
7316 let row = root.get_mut(section).and_then(Value::as_array_mut).and_then(|rows| rows.iter_mut().find(|row| row.get("id").and_then(Value::as_i64) == Some(record_id))).ok_or(66)?;
7317 let row_map = row.as_object_mut().ok_or(69)?;
7318 for (key, value) in patch { row_map.insert(key, value); }
7319 state_write(&root, state_path)
7320 }
7321 "remove-record-by-id" => {
7322 let section = text("section");
7323 let record_id = id("record_id");
7324 let rows = root.get_mut(section).and_then(Value::as_array_mut).ok_or(66)?;
7325 let before = rows.len();
7326 rows.retain(|row| row.get("id").and_then(Value::as_i64) != Some(record_id));
7327 if rows.len() == before { return Err(66); }
7328 state_write(&root, state_path)
7329 }
7330 "rate-limit-event-count" => {
7331 let count = state_rows(&root, "rate_limit_events").iter().filter(|row| {
7332 row.get("policy_key").and_then(Value::as_str) == Some(text("policy_key"))
7333 && row.get("bucket_key").and_then(Value::as_str) == Some(text("bucket_key"))
7334 && row.get("created_at").and_then(Value::as_str).is_some_and(|created| created >= text("window_start"))
7335 && (text("ip_address").is_empty() || row.get("ip_address").and_then(Value::as_str) == Some(text("ip_address")))
7336 }).count();
7337 Ok(count.to_string())
7338 }
7339 "record-rate-limit-event" => {
7340 let event = serde_json::json!({
7341 "id": state_next_id(&root, "rate_limit_events"),
7342 "policy_key": text("policy_key"),
7343 "bucket_key": text("bucket_key"),
7344 "ip_address": if text("ip_address").is_empty() { Value::Null } else { Value::String(text("ip_address").to_owned()) },
7345 "outcome": text("outcome"),
7346 "created_at": text("created_at"),
7347 });
7348 root.as_object_mut().ok_or(66)?.entry("rate_limit_events".to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)?.push(event);
7349 state_write(&root, state_path)
7350 }
7351 "prune-rate-limit-events" => {
7352 let cutoff = text("cutoff_iso");
7353 root.as_object_mut().ok_or(66)?.entry("rate_limit_events".to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)?.retain(|row| row.get("created_at").and_then(Value::as_str).is_some_and(|created| created >= cutoff));
7354 state_write(&root, state_path)
7355 }
7356 "business-technician-ids-lines" => Ok(state_lines(&state_rows(&root, "technicians").iter().filter(|row| row.get("business_id").map(value_to_text).as_deref() == Some(id("business_id").to_string().as_str())).cloned().collect::<Vec<_>>(), "id")),
7357 "service-assigned-technician-ids-lines" => {
7358 let service = state_row_by_id(&root, "services", id("service_id"));
7359 let ids = service.and_then(|row| row.get("technician_ids")).and_then(Value::as_array).map(|items| items.iter().map(value_to_text).collect::<Vec<_>>().join("\n")).unwrap_or_default();
7360 Ok(ids)
7361 }
7362 "business-availability-lines" => Ok(state_rows(&root, "business_availability").iter().filter(|row| row.get("business_id").map(value_to_text).as_deref() == Some(id("business_id").to_string().as_str()) && (text("day_of_week").is_empty() || row.get("day_of_week").and_then(Value::as_str) == Some(text("day_of_week")))).map(|row| serde_json::to_string(row).unwrap_or_default()).collect::<Vec<_>>().join("\n")),
7363 "technician-availability-lines" => Ok(state_rows(&root, "technician_availability").iter().filter(|row| row.get("technician_id").map(value_to_text).as_deref() == Some(id("technician_id").to_string().as_str()) && (text("day_of_week").is_empty() || row.get("day_of_week").and_then(Value::as_str) == Some(text("day_of_week")))).map(|row| serde_json::to_string(row).unwrap_or_default()).collect::<Vec<_>>().join("\n")),
7364 "technician-appointments-lines" => Ok(state_rows(&root, "appointments").iter().filter(|row| row.get("technician_id").map(value_to_text).as_deref() == Some(id("technician_id").to_string().as_str()) && (text("date").is_empty() || row.get("date").and_then(Value::as_str) == Some(text("date")))).map(|row| serde_json::to_string(row).unwrap_or_default()).collect::<Vec<_>>().join("\n")),
7365 "public-business-cards-json" => Ok(serde_json::to_string(&state_rows(&root, "businesses").iter().map(|row| {
7366 serde_json::json!({ "id": row.get("id").cloned().unwrap_or(Value::Null), "slug": row.get("slug").cloned().unwrap_or(Value::String(String::new())), "name": row.get("name").cloned().unwrap_or(Value::String(String::new())), "image_url": row.get("image_url").cloned().unwrap_or(Value::String(String::new())) })
7367 }).collect::<Vec<_>>()).unwrap_or_else(|_| "[]".to_owned())),
7368 "manageable-business-cards-json" => {
7369 let tenant_ids = options.get("tenant_ids_json").and_then(Value::as_array).cloned().unwrap_or_default();
7370 let cards = state_rows(&root, "businesses").iter().filter(|business| {
7371 let business_id = business.get("id").map(value_to_text);
7372 let tenant_id = state_rows(&root, "tenants").iter().find(|tenant| tenant.get("source_business_id").map(value_to_text) == business_id).and_then(|tenant| tenant.get("id")).map(value_to_text);
7373 tenant_ids.iter().map(value_to_text).any(|wanted| tenant_id.as_deref() == Some(wanted.as_str())) || business.get("owner_id").map(value_to_text).as_deref() == Some(id("user_id").to_string().as_str())
7374 }).cloned().collect::<Vec<_>>();
7375 Ok(serde_json::to_string(&cards).unwrap_or_else(|_| "[]".to_owned()))
7376 }
7377 "managed-appointments-json" | "user-appointments-json" => {
7378 let rows = state_rows(&root, "appointments").iter().filter(|row| {
7379 if name == "user-appointments-json" { row.get("user_id").map(value_to_text).as_deref() == Some(id("user_id").to_string().as_str()) } else { true }
7380 }).cloned().collect::<Vec<_>>();
7381 Ok(serde_json::to_string(&rows).unwrap_or_else(|_| "[]".to_owned()))
7382 }
7383 "profile-page-json" => output_json(state_row_by_id(&root, "users", id("user_id"))),
7384 "appointments-page-json" => Ok(serde_json::to_string(&state_rows(&root, "appointments").iter().filter(|row| row.get("user_id").map(value_to_text).as_deref() == Some(id("user_id").to_string().as_str())).cloned().collect::<Vec<_>>()).unwrap_or_else(|_| "[]".to_owned())),
7385 "business-admin-service-rows-json" => Ok(serde_json::to_string(&state_rows(&root, "services").iter().filter(|row| row.get("business_id").map(value_to_text).as_deref() == Some(id("business_id").to_string().as_str())).cloned().collect::<Vec<_>>()).unwrap_or_else(|_| "[]".to_owned())),
7386 "business-admin-technician-rows-json" => Ok(serde_json::to_string(&state_rows(&root, "technicians").iter().filter(|row| row.get("business_id").map(value_to_text).as_deref() == Some(id("business_id").to_string().as_str())).cloned().collect::<Vec<_>>()).unwrap_or_else(|_| "[]".to_owned())),
7387 "event-stream-row-json-lines" => {
7388 let since_id = id("since_id");
7389 let limit = id("limit").clamp(1, 100) as usize;
7390 Ok(state_rows(&root, "event_stream").iter().filter(|row| row.get("id").map(value_to_text).and_then(|value| value.parse::<i64>().ok()).is_some_and(|value| value > since_id)).take(limit).map(|row| serde_json::to_string(row).unwrap_or_default()).collect::<Vec<_>>().join("\n"))
7391 }
7392 "append-audit-event" | "append-workflow-event" => {
7393 let section = if name == "append-audit-event" { "audit_log" } else { "workflow_execution_events" };
7394 let mut event = options.get("payload_json").cloned().filter(Value::is_object).unwrap_or_else(|| serde_json::json!({}));
7395 let object = event.as_object_mut().ok_or(69)?;
7396 if name == "append-audit-event" { object.insert("event".to_owned(), Value::String(text("event_kind").to_owned())); } else { object.insert("event_kind".to_owned(), Value::String(text("event_kind").to_owned())); object.insert("id".to_owned(), Value::Number(state_next_id(&root, section).into())); }
7397 object.insert("created_at".to_owned(), Value::String(text("created_at").to_owned()));
7398 root.as_object_mut().ok_or(66)?.entry(section.to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)?.push(event);
7399 state_write(&root, state_path)
7400 }
7401 "issue-browser-session" => {
7402 let event = serde_json::json!({ "id": state_next_id(&root, "sessions"), "user_id": id("user_id"), "session_token_hash": text("session_token_hash"), "session_kind": "browser_session", "current_tenant_id": Value::Null, "expires_at": text("expires_at"), "revoked_at": Value::Null, "created_at": text("created_at"), "user_agent": if text("user_agent").is_empty() { Value::Null } else { Value::String(text("user_agent").to_owned()) }, "ip_address": if text("ip_address").is_empty() { Value::Null } else { Value::String(text("ip_address").to_owned()) } });
7403 root.as_object_mut().ok_or(66)?.entry("sessions".to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)?.push(event);
7404 state_write(&root, state_path)
7405 }
7406 "revoke-session-hash" => {
7407 let hash = text("session_token_hash").to_owned();
7408 for row in root.as_object_mut().ok_or(66)?.entry("sessions".to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)? {
7409 if row.get("session_token_hash").and_then(Value::as_str) == Some(hash.as_str()) && row.get("revoked_at").is_none_or(|value| value.is_null() || value.as_str() == Some("")) { row.as_object_mut().ok_or(69)?.insert("revoked_at".to_owned(), Value::String(text("revoked_at").to_owned())); }
7410 }
7411 state_write(&root, state_path)
7412 }
7413 "issue-email-verification" | "issue-password-reset" => {
7414 let section = if name == "issue-email-verification" { "email_verifications" } else { "password_resets" };
7415 let user_id = id("user_id");
7416 let next_id = state_next_id(&root, section);
7417 let token_hash = text("token_hash").to_owned();
7418 let expires_at = text("expires_at").to_owned();
7419 let created_at = text("created_at").to_owned();
7420 let rows = root.as_object_mut().ok_or(66)?.entry(section.to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)?;
7421 rows.retain(|row| row.get("user_id").map(value_to_text).and_then(|value| value.parse::<i64>().ok()) != Some(user_id) || !row.get("claimed_at").is_none_or(|value| value.is_null() || value.as_str() == Some("")));
7422 rows.push(serde_json::json!({ "id": next_id, "user_id": user_id, "token_hash": token_hash, "expires_at": expires_at, "claimed_at": Value::Null, "created_at": created_at }));
7423 state_write(&root, state_path)
7424 }
7425 "claim-email-verification" => {
7426 let verification_id = id("verification_id");
7427 for row in root.as_object_mut().ok_or(66)?.entry("email_verifications".to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)? { if row.get("id").map(value_to_text).and_then(|value| value.parse::<i64>().ok()) == Some(verification_id) { row.as_object_mut().ok_or(69)?.insert("claimed_at".to_owned(), Value::String(text("claimed_at").to_owned())); } }
7428 if let Some(user) = state_row_by_id(&root, "users", id("user_id")).cloned() { let _ = user; }
7429 state_write(&root, state_path)
7430 }
7431 "issue-invite" => {
7432 let email = text("email").to_owned();
7433 let kind = text("invite_kind").to_owned();
7434 let next_id = state_next_id(&root, "invites");
7435 let account_type = text("account_type").to_owned();
7436 let token_hash = text("token_hash").to_owned();
7437 let expires_at = text("expires_at").to_owned();
7438 let created_at = text("created_at").to_owned();
7439 let rows = root.as_object_mut().ok_or(66)?.entry("invites".to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)?;
7440 rows.retain(|row| !(row.get("email").and_then(Value::as_str).is_some_and(|value| value.eq_ignore_ascii_case(&email)) && row.get("invite_kind").and_then(Value::as_str) == Some(kind.as_str()) && row.get("claimed_at").is_none_or(|value| value.is_null() || value.as_str() == Some(""))));
7441 rows.push(serde_json::json!({ "id": next_id, "email": email, "account_type": account_type, "invite_kind": kind, "token_hash": token_hash, "expires_at": expires_at, "claimed_at": Value::Null, "created_at": created_at, "target_tenant_id": if id("target_tenant_id") > 0 { Value::Number(id("target_tenant_id").into()) } else { Value::Null }, "role_hint": if text("role_hint").is_empty() { Value::Null } else { Value::String(text("role_hint").to_owned()) }, "invited_by_user_id": if id("invited_by_user_id") > 0 { Value::Number(id("invited_by_user_id").into()) } else { Value::Null }, "accepted_user_id": Value::Null }));
7442 state_write(&root, state_path)
7443 }
7444 "claim-invite" => {
7445 let invite_id = id("invite_id");
7446 for row in root.as_object_mut().ok_or(66)?.entry("invites".to_owned()).or_insert_with(|| Value::Array(Vec::new())).as_array_mut().ok_or(69)? { if row.get("id").map(value_to_text).and_then(|value| value.parse::<i64>().ok()) == Some(invite_id) { let object = row.as_object_mut().ok_or(69)?; object.insert("accepted_user_id".to_owned(), Value::Number(id("accepted_user_id").into())); object.insert("claimed_at".to_owned(), Value::String(text("claimed_at").to_owned())); } }
7447 state_write(&root, state_path)
7448 }
7449 "business-open-for-slot" => {
7450 let business_id = id("business_id");
7451 let day = text("date");
7452 let open = state_rows(&root, "business_availability").iter().any(|row| row.get("business_id").map(value_to_text).as_deref() == Some(business_id.to_string().as_str()) && (day.is_empty() || row.get("date").and_then(Value::as_str).is_none_or(|value| value == day)));
7453 Ok(if open { "1" } else { "0" }.to_owned())
7454 }
7455 "technician-booked-for-slot" => {
7456 let booked = state_rows(&root, "appointments").iter().any(|row| row.get("technician_id").map(value_to_text).as_deref() == Some(id("technician_id").to_string().as_str()) && row.get("date").and_then(Value::as_str) == Some(text("date")) && row.get("time").and_then(Value::as_str) == Some(text("time")));
7457 Ok(if booked { "1" } else { "0" }.to_owned())
7458 }
7459 "assign-technician-for-slot" => {
7460 let selected = state_rows(&root, "technicians").iter().find(|technician| {
7461 technician.get("business_id").map(value_to_text).as_deref() == Some(id("business_id").to_string().as_str()) && !state_rows(&root, "appointments").iter().any(|appointment| appointment.get("technician_id").map(value_to_text) == technician.get("id").map(value_to_text) && appointment.get("date").and_then(Value::as_str) == Some(text("date")) && appointment.get("time").and_then(Value::as_str) == Some(text("time")))
7462 });
7463 Ok(selected.and_then(|row| row.get("id")).map(value_to_text).unwrap_or_else(|| "0".to_owned()))
7464 }
7465 "google-calendar-connection-for-business" => output_json(state_rows(&root, "google_calendar_connections").iter().find(|row| row.get("business_id").map(value_to_text).as_deref() == Some(id("business_id").to_string().as_str()))),
7466 "business-dashboard-page-json" | "explore-page-json" | "business-admin-page-json" => {
7467 let slug = text("slug");
7468 let business = if slug.is_empty() { None } else { state_row_by_text(&root, "businesses", "slug", slug) };
7469 Ok(serde_json::to_string(&serde_json::json!({
7470 "business": business,
7471 "businesses": state_rows(&root, "businesses"),
7472 "services": business.map(|row| state_rows(&root, "services").iter().filter(|service| service.get("business_id").map(value_to_text) == row.get("id").map(value_to_text)).cloned().collect::<Vec<_>>()).unwrap_or_default(),
7473 "technicians": business.map(|row| state_rows(&root, "technicians").iter().filter(|technician| technician.get("business_id").map(value_to_text) == row.get("id").map(value_to_text)).cloned().collect::<Vec<_>>()).unwrap_or_default(),
7474 })).unwrap_or_else(|_| "{}".to_owned()))
7475 }
7476 "business-public-page-json" | "business-edit-page-json" | "business-public-render-page-json" => output_json(state_row_by_text(&root, "businesses", "slug", text("slug"))),
7477 "service-edit-page-json" | "service-book-form-page-json" => output_json(state_row_by_id(&root, "services", id("service_id"))),
7478 "business-calendar-page-json" | "business-calendar-render-page-json" => Ok(serde_json::to_string(&state_rows(&root, "appointments").iter().filter(|row| row.get("business_id").map(value_to_text).as_deref() == state_row_by_text(&root, "businesses", "slug", text("slug")).and_then(|business| business.get("id")).map(value_to_text).as_deref()).cloned().collect::<Vec<_>>()).unwrap_or_else(|_| "[]".to_owned())),
7479 "profile-render-page-json" => output_json(state_row_by_id(&root, "users", id("user_id"))),
7480 "service-visual-html" => service_visual_html(Path::new(text("app_root")), Some(text("image_url")), text("service_name"), Some(text("primary_color"))).ok_or(69),
7481 "service-sort-order-options-html" => service_sort_order_options_html(Path::new(text("app_root")), &root, id("business_id"), id("selected_order"), id("include_append") != 0).ok_or(69),
7482 "service-booking-options-editor-html" => service_booking_options_editor_html(Path::new(text("app_root")), &root, id("service_id")).ok_or(69),
7483 "service-technician-checkboxes-html" => technician_checkboxes_html(Path::new(text("app_root")), &root, id("business_id"), id("service_id"), text("empty_message"), false).ok_or(69),
7484 "business-technician-checkboxes-html" => technician_checkboxes_html(Path::new(text("app_root")), &root, id("business_id"), 0, text("empty_message"), true).ok_or(69),
7485 _ => Err(78),
7486 }
7487}
7488
7489/// Performs generated surface context value.
7490fn generated_surface_context_value(surface: &Value, params: &Value) -> Option<(String, String)> {
7491 let binding = surface.get("context_binding")?;
7492 let param = binding
7493 .get("param")
7494 .and_then(Value::as_str)
7495 .filter(|value| !value.is_empty())?;
7496 let value = params
7497 .get(param)
7498 .map(value_to_text)
7499 .filter(|value| !value.is_empty())?;
7500 Some((param.to_owned(), value))
7501}
7502
7503/// Resolves generated-surface context, tenant scope, and context record metadata.
7504///
7505/// # Parameters
7506///
7507/// - `state_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_context_json`].
7508/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_context_json`].
7509/// - `params_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_context_json`].
7510///
7511/// # Returns
7512///
7513/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_context_json`] can produce a value and [`None`] otherwise.
7514///
7515/// # Performance
7516///
7517/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_context_json`].
7518///
7519/// # Examples
7520///
7521/// ```rust,ignore
7522/// let _result = pharos_native::runtime::json_commands::generated_surface_context_json(todo!(), "{}", todo!());
7523/// ```
7524///
7525/// # Related
7526///
7527/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
7528pub fn generated_surface_context_json(
7529 state_path: &Path,
7530 surface_json: &str,
7531 params_json: &str,
7532) -> Option<String> {
7533 let state = read_json_file(state_path)?;
7534 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
7535 let params = serde_json::from_str::<Value>(params_json).ok()?;
7536 let binding = surface.get("context_binding");
7537 let Some((param, context_value)) = generated_surface_context_value(&surface, ¶ms) else {
7538 return serde_json::to_string(&serde_json::json!({
7539 "ok": false,
7540 "error_code": "missing_context_parameter",
7541 "message": "Missing route parameter."
7542 }))
7543 .ok();
7544 };
7545 let state_collection = binding
7546 .and_then(|value| value.get("state_collection"))
7547 .and_then(Value::as_str)
7548 .unwrap_or("businesses");
7549 let match_field = binding
7550 .and_then(|value| value.get("match_field"))
7551 .and_then(Value::as_str)
7552 .unwrap_or("slug");
7553 let record = state_row_by_text(&state, state_collection, match_field, &context_value)?;
7554 let context_id_field = binding
7555 .and_then(|value| value.get("context_id_field"))
7556 .and_then(Value::as_str)
7557 .unwrap_or("id");
7558 let label_field = binding
7559 .and_then(|value| value.get("context_label_field"))
7560 .and_then(Value::as_str)
7561 .unwrap_or("name");
7562 let context_id = record.get(context_id_field).cloned().unwrap_or(Value::Null);
7563 let tenant_collection = binding
7564 .and_then(|value| value.get("tenant_collection"))
7565 .and_then(Value::as_str)
7566 .unwrap_or("tenants");
7567 let tenant_match_field = binding
7568 .and_then(|value| value.get("tenant_match_field"))
7569 .and_then(Value::as_str)
7570 .unwrap_or("source_business_id");
7571 let tenant_match_from = binding
7572 .and_then(|value| value.get("tenant_match_from_context_field"))
7573 .and_then(Value::as_str)
7574 .unwrap_or("id");
7575 let tenant_value = record
7576 .get(tenant_match_from)
7577 .map(value_to_text)
7578 .unwrap_or_default();
7579 let tenant_id = state_rows(&state, tenant_collection)
7580 .iter()
7581 .find(|row| {
7582 row.get(tenant_match_field).map(value_to_text).as_deref() == Some(tenant_value.as_str())
7583 })
7584 .and_then(|row| row.get("id"))
7585 .cloned()
7586 .unwrap_or(Value::Number(0.into()));
7587 let tenant_id_text = value_to_text(&tenant_id);
7588 serde_json::to_string(&serde_json::json!({
7589 "ok": true,
7590 "tenant_id": tenant_id,
7591 "tenant_scope": if tenant_id_text == "0" || tenant_id_text.is_empty() { "global".to_owned() } else { format!("tenant:{tenant_id_text}") },
7592 "context_id": context_id,
7593 "context_param": param,
7594 "context_value": context_value,
7595 "context_label": record.get(label_field).map(value_to_text).unwrap_or_default(),
7596 "record": record,
7597 })).ok()
7598}
7599
7600/// Performs generated surface filter matches.
7601fn generated_surface_filter_matches(item: &Value, filter: &Value, raw_query: &str) -> bool {
7602 let query = filter
7603 .get("query")
7604 .and_then(Value::as_str)
7605 .unwrap_or_default();
7606 if query.is_empty() {
7607 return true;
7608 }
7609 let wanted =
7610 request_query_value(raw_query, query).or_else(|| filter.get("default").map(value_to_text));
7611 let Some(wanted) = wanted.filter(|value| !value.is_empty() && value != "all") else {
7612 return true;
7613 };
7614 let field = filter.get("field").and_then(Value::as_str).unwrap_or(query);
7615 let actual = item.get(field);
7616 match filter
7617 .get("match")
7618 .and_then(Value::as_str)
7619 .unwrap_or("exact")
7620 {
7621 "contains" => actual.map(value_to_text).is_some_and(|value| {
7622 value
7623 .to_ascii_lowercase()
7624 .contains(&wanted.to_ascii_lowercase())
7625 }),
7626 "boolean" => actual
7627 .map(value_to_text)
7628 .is_some_and(|value| value.eq_ignore_ascii_case(&wanted)),
7629 "array_contains_integer" => actual.and_then(Value::as_array).is_some_and(|values| {
7630 values
7631 .iter()
7632 .map(value_to_text)
7633 .any(|value| value == wanted)
7634 }),
7635 _ => actual
7636 .map(value_to_text)
7637 .is_some_and(|value| value == wanted),
7638 }
7639}
7640
7641/// Builds a filtered, sorted, paginated generated-surface list result.
7642///
7643/// # Parameters
7644///
7645/// - `state_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_list_result_json`].
7646/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_list_result_json`].
7647/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_list_result_json`].
7648/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::generated_surface_list_result_json`].
7649///
7650/// # Returns
7651///
7652/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_list_result_json`] can produce a value and [`None`] otherwise.
7653///
7654/// # Performance
7655///
7656/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_list_result_json`].
7657///
7658/// # Examples
7659///
7660/// ```rust,ignore
7661/// let _result = pharos_native::runtime::json_commands::generated_surface_list_result_json(todo!(), "{}", todo!(), todo!());
7662/// ```
7663///
7664/// # Related
7665///
7666/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
7667pub fn generated_surface_list_result_json(
7668 state_path: &Path,
7669 surface_json: &str,
7670 context_json: &str,
7671 raw_query: &str,
7672) -> Option<String> {
7673 let state = read_json_file(state_path)?;
7674 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
7675 let context = serde_json::from_str::<Value>(context_json).ok()?;
7676 let collection = surface.get("entity_collection").and_then(Value::as_str)?;
7677 let scope_field = surface.get("scope_field").and_then(Value::as_str);
7678 let context_id = context
7679 .get("context_id")
7680 .map(value_to_text)
7681 .unwrap_or_default();
7682 let mut rows: Vec<Value> = state_rows(&state, collection)
7683 .iter()
7684 .filter(|row| {
7685 let scope_matches = scope_field
7686 .map(|field| {
7687 row.get(field).map(value_to_text).as_deref() == Some(context_id.as_str())
7688 })
7689 .unwrap_or(true);
7690 scope_matches
7691 && surface
7692 .get("filters")
7693 .and_then(Value::as_array)
7694 .map(|filters| {
7695 filters
7696 .iter()
7697 .all(|filter| generated_surface_filter_matches(row, filter, raw_query))
7698 })
7699 .unwrap_or(true)
7700 })
7701 .cloned()
7702 .collect();
7703 let sort_key = request_query_value(raw_query, "sort").or_else(|| {
7704 surface
7705 .get("default_sort")
7706 .and_then(Value::as_str)
7707 .map(ToOwned::to_owned)
7708 });
7709 if let Some(sort_key) = sort_key.clone().filter(|value| !value.is_empty()) {
7710 let descending = sort_key.starts_with('-');
7711 let field = sort_key.trim_start_matches('-').to_owned();
7712 rows.sort_by(|left, right| {
7713 let ordering = value_to_text(left.get(&field).unwrap_or(&Value::Null))
7714 .cmp(&value_to_text(right.get(&field).unwrap_or(&Value::Null)));
7715 if descending {
7716 ordering.reverse()
7717 } else {
7718 ordering
7719 }
7720 });
7721 }
7722 let page = request_query_value(raw_query, "page")
7723 .and_then(|value| value.parse::<usize>().ok())
7724 .filter(|value| *value > 0)
7725 .unwrap_or(1);
7726 let page_default = surface
7727 .get("page_size_default")
7728 .and_then(Value::as_u64)
7729 .unwrap_or(25) as usize;
7730 let page_max = surface
7731 .get("page_size_max")
7732 .and_then(Value::as_u64)
7733 .unwrap_or(100) as usize;
7734 let page_size = request_query_value(raw_query, "page_size")
7735 .and_then(|value| value.parse::<usize>().ok())
7736 .filter(|value| *value > 0)
7737 .unwrap_or(page_default)
7738 .min(page_max);
7739 let total_items = rows.len();
7740 let start = page
7741 .saturating_sub(1)
7742 .saturating_mul(page_size)
7743 .min(total_items);
7744 let end = start.saturating_add(page_size).min(total_items);
7745 let response_fields = surface.get("response_fields").and_then(Value::as_array);
7746 let items: Vec<Value> = rows[start..end]
7747 .iter()
7748 .map(|row| {
7749 if let Some(fields) = response_fields {
7750 let mut object = serde_json::Map::new();
7751 for field in fields.iter().filter_map(Value::as_str) {
7752 object.insert(
7753 field.to_owned(),
7754 row.get(field).cloned().unwrap_or(Value::Null),
7755 );
7756 }
7757 Value::Object(object)
7758 } else {
7759 row.clone()
7760 }
7761 })
7762 .collect();
7763 serde_json::to_string(&serde_json::json!({
7764 "items": items,
7765 "pagination": { "page": page, "page_size": page_size, "total_items": total_items, "total_pages": if page_size == 0 { 1 } else { total_items.div_ceil(page_size) } },
7766 "sort": sort_key,
7767 })).ok()
7768}
7769
7770/// Builds a default generated-surface candidate from existing, declared, and submitted values.
7771///
7772/// # Parameters
7773///
7774/// - `state_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_default_candidate_json`].
7775/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_default_candidate_json`].
7776/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_default_candidate_json`].
7777/// - `existing_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_default_candidate_json`].
7778/// - `submitted_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_default_candidate_json`].
7779///
7780/// # Returns
7781///
7782/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_default_candidate_json`] can produce a value and [`None`] otherwise.
7783///
7784/// # Performance
7785///
7786/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_default_candidate_json`].
7787///
7788/// # Examples
7789///
7790/// ```rust,ignore
7791/// let _result = pharos_native::runtime::json_commands::generated_surface_default_candidate_json(todo!(), "{}", todo!(), todo!(), todo!());
7792/// ```
7793///
7794/// # Related
7795///
7796/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
7797pub fn generated_surface_default_candidate_json(
7798 state_path: &Path,
7799 surface_json: &str,
7800 context_json: &str,
7801 existing_json: Option<&str>,
7802 submitted_json: Option<&str>,
7803) -> Option<String> {
7804 let state = read_json_file(state_path)?;
7805 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
7806 let context = serde_json::from_str::<Value>(context_json).ok()?;
7807 let mut candidate = serde_json::Map::new();
7808 for source in [existing_json, None] {
7809 if let Some(text) = source.filter(|value| !value.is_empty())
7810 && let Some(object) = serde_json::from_str::<Value>(text).ok()?.as_object()
7811 {
7812 candidate.extend(object.clone());
7813 }
7814 }
7815 if let Some(defaults) = surface.get("defaults").and_then(Value::as_object) {
7816 for (key, value) in defaults {
7817 candidate
7818 .entry(key.clone())
7819 .or_insert_with(|| value.clone());
7820 }
7821 }
7822 if let Some(text) = submitted_json.filter(|value| !value.is_empty())
7823 && let Some(object) = serde_json::from_str::<Value>(text).ok()?.as_object()
7824 {
7825 candidate.extend(object.clone());
7826 }
7827 if let Some(scope_field) = surface
7828 .get("scope_field")
7829 .and_then(Value::as_str)
7830 .filter(|value| !value.is_empty())
7831 && let Some(context_id) = context.get("context_id")
7832 {
7833 candidate.insert(scope_field.to_owned(), context_id.clone());
7834 }
7835 if existing_json.is_none()
7836 && let (Some(sequence_field), Some(collection)) = (
7837 surface.get("auto_sequence_field").and_then(Value::as_str),
7838 surface.get("entity_collection").and_then(Value::as_str),
7839 )
7840 {
7841 let scope_field = surface.get("scope_field").and_then(Value::as_str);
7842 let context_id = context
7843 .get("context_id")
7844 .map(value_to_text)
7845 .unwrap_or_default();
7846 let next = state_rows(&state, collection)
7847 .iter()
7848 .filter(|row| {
7849 scope_field
7850 .map(|field| {
7851 row.get(field).map(value_to_text).as_deref() == Some(context_id.as_str())
7852 })
7853 .unwrap_or(true)
7854 })
7855 .filter_map(|row| {
7856 row.get(sequence_field)
7857 .map(value_to_text)
7858 .and_then(|value| value.parse::<i64>().ok())
7859 })
7860 .max()
7861 .unwrap_or(0)
7862 + 1;
7863 candidate.insert(sequence_field.to_owned(), Value::Number(next.into()));
7864 }
7865 generated::generated_surface_coerce_candidate_json(
7866 &surface,
7867 &serde_json::to_string(&candidate).ok()?,
7868 )
7869 .or_else(|| serde_json::to_string(&candidate).ok())
7870}
7871
7872/// Renders generated-surface form controls from the app-owned form declaration.
7873///
7874/// # Parameters
7875///
7876/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_form_fields_html`].
7877/// - `state_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_form_fields_html`].
7878/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_form_fields_html`].
7879/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_form_fields_html`].
7880/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_form_fields_html`].
7881/// - `item_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_form_fields_html`].
7882///
7883/// # Returns
7884///
7885/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_form_fields_html`] can produce a value and [`None`] otherwise.
7886///
7887/// # Performance
7888///
7889/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_form_fields_html`].
7890///
7891/// # Examples
7892///
7893/// ```rust,ignore
7894/// let _result = pharos_native::runtime::json_commands::generated_surface_form_fields_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), "{}", todo!(), "{}");
7895/// ```
7896///
7897/// # Related
7898///
7899/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
7900pub fn generated_surface_form_fields_html(
7901 app_root: &Path,
7902 state_path: &Path,
7903 fixture_path: &Path,
7904 surface_json: &str,
7905 context_json: &str,
7906 item_json: Option<&str>,
7907) -> Option<String> {
7908 let _state = read_json_file(state_path)?;
7909 let fixture = read_json_file(fixture_path)?;
7910 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
7911 let _context = serde_json::from_str::<Value>(context_json).ok()?;
7912 let item = item_json
7913 .filter(|value| !value.is_empty())
7914 .and_then(|value| serde_json::from_str::<Value>(value).ok())
7915 .unwrap_or_else(|| serde_json::json!({}));
7916 let form_id = surface.get("form_id").and_then(Value::as_str)?;
7917 let form = fixture
7918 .get("forms")
7919 .and_then(Value::as_array)?
7920 .iter()
7921 .find(|form| form.get("form_id").and_then(Value::as_str) == Some(form_id))?;
7922 let mut out = String::new();
7923 for field in form.get("fields").and_then(Value::as_array)? {
7924 let field_id = field
7925 .get("field_id")
7926 .and_then(Value::as_str)
7927 .filter(|value| !value.is_empty())?;
7928 let widget = field
7929 .get("widget")
7930 .and_then(Value::as_str)
7931 .unwrap_or("text");
7932 let label = field
7933 .get("label")
7934 .and_then(Value::as_str)
7935 .filter(|value| !value.is_empty())
7936 .map(ToOwned::to_owned)
7937 .unwrap_or_else(|| generated_surface_label(field_id));
7938 let value = item.get(field_id).map(value_to_text).unwrap_or_default();
7939 let options = if widget == "select" {
7940 generated_surface_select_options_html(
7941 app_root,
7942 &serde_json::to_string(field).ok()?,
7943 Some(&value),
7944 )?
7945 } else {
7946 String::new()
7947 };
7948 out.push_str(&render_ui_element_html(
7949 app_root,
7950 if widget == "select" {
7951 "select"
7952 } else if widget == "textarea" {
7953 "textarea"
7954 } else if widget == "checkbox" {
7955 "checkbox"
7956 } else {
7957 "input"
7958 },
7959 Some(widget),
7960 field_id,
7961 field_id,
7962 &label,
7963 &value,
7964 &options,
7965 )?);
7966 }
7967 Some(out)
7968}
7969
7970/// Renders relation checkbox options and the app-owned checkbox group fragment.
7971///
7972/// # Parameters
7973///
7974/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_relation_checkboxes_html`].
7975/// - `state_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_relation_checkboxes_html`].
7976/// - `relation_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_relation_checkboxes_html`].
7977/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_relation_checkboxes_html`].
7978/// - `selected_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_relation_checkboxes_html`].
7979///
7980/// # Returns
7981///
7982/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_relation_checkboxes_html`] can produce a value and [`None`] otherwise.
7983///
7984/// # Performance
7985///
7986/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_relation_checkboxes_html`].
7987///
7988/// # Examples
7989///
7990/// ```rust,ignore
7991/// let _result = pharos_native::runtime::json_commands::generated_surface_relation_checkboxes_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
7992/// ```
7993///
7994/// # Related
7995///
7996/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
7997pub fn generated_surface_relation_checkboxes_html(
7998 app_root: &Path,
7999 state_path: &Path,
8000 relation_json: &str,
8001 context_json: &str,
8002 selected_json: Option<&str>,
8003) -> Option<String> {
8004 let state = read_json_file(state_path)?;
8005 let relation = serde_json::from_str::<Value>(relation_json).ok()?;
8006 let context = serde_json::from_str::<Value>(context_json).ok()?;
8007 let selected = selected_json
8008 .and_then(|text| serde_json::from_str::<Value>(text).ok())
8009 .unwrap_or_else(|| Value::Array(Vec::new()));
8010 let relation_field = relation
8011 .get("field")
8012 .and_then(Value::as_str)
8013 .unwrap_or("relation");
8014 let collection = relation
8015 .get("related_collection")
8016 .and_then(Value::as_str)
8017 .unwrap_or_default();
8018 let scope_field = relation.get("related_scope_field").and_then(Value::as_str);
8019 let context_id = context
8020 .get("context_id")
8021 .map(value_to_text)
8022 .unwrap_or_default();
8023 let label_field = relation
8024 .get("label_field")
8025 .and_then(Value::as_str)
8026 .unwrap_or("name");
8027 let prefix = relation
8028 .get("form_checkbox_prefix")
8029 .and_then(Value::as_str)
8030 .filter(|value| !value.is_empty())
8031 .unwrap_or(relation_field);
8032 let mut options_html = String::new();
8033 for row in state_rows(&state, collection) {
8034 if !scope_field
8035 .map(|field| row.get(field).map(value_to_text).as_deref() == Some(context_id.as_str()))
8036 .unwrap_or(true)
8037 {
8038 continue;
8039 }
8040 let row_id = row
8041 .get("id")
8042 .map(value_to_text)
8043 .filter(|value| !value.is_empty())?;
8044 let row_label = row
8045 .get(label_field)
8046 .map(value_to_text)
8047 .filter(|value| !value.is_empty())
8048 .unwrap_or_else(|| row_id.clone());
8049 let selected = selected
8050 .as_array()
8051 .map(|items| items.iter().map(value_to_text).any(|value| value == row_id))
8052 .unwrap_or(false);
8053 let params = super::ui::ui_render_param_map(
8054 &format!("{prefix}{row_id}"),
8055 Some("checkbox"),
8056 Some("button"),
8057 &format!("{prefix}{row_id}"),
8058 &row_label,
8059 &row_label,
8060 &row_id,
8061 Some(""),
8062 Some(""),
8063 if selected { " checked" } else { "" },
8064 "",
8065 "",
8066 &row_label,
8067 "checkbox",
8068 )?;
8069 options_html.push_str(
8070 &generated::render_generated_admin_fragment_from_value_runtime(
8071 app_root,
8072 "templates/shared/fragments/ui_checkbox.html",
8073 ¶ms,
8074 )?,
8075 );
8076 }
8077 if options_html.is_empty() {
8078 options_html.push_str(&render_fragment_with_params(
8079 app_root,
8080 "templates/shared/fragments/muted_text.html",
8081 serde_json::Map::from_iter([(
8082 "message".to_owned(),
8083 Value::String(
8084 relation
8085 .get("empty_options_text")
8086 .and_then(Value::as_str)
8087 .unwrap_or("No related records available.")
8088 .to_owned(),
8089 ),
8090 )]),
8091 )?);
8092 }
8093 render_fragment_with_params(
8094 app_root,
8095 "templates/shared/fragments/ui_checkbox_group.html",
8096 serde_json::Map::from_iter([
8097 ("id".to_owned(), Value::String(relation_field.to_owned())),
8098 (
8099 "class_name".to_owned(),
8100 Value::String("form-group".to_owned()),
8101 ),
8102 (
8103 "label".to_owned(),
8104 Value::String(
8105 relation
8106 .get("label")
8107 .and_then(Value::as_str)
8108 .unwrap_or(relation_field)
8109 .to_owned(),
8110 ),
8111 ),
8112 ("help_html".to_owned(), Value::String(String::new())),
8113 ("options_html".to_owned(), Value::String(options_html)),
8114 ]),
8115 )
8116}
8117
8118/// Builds the generated-admin table fragments and column count.
8119///
8120/// # Parameters
8121///
8122/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_table_json`].
8123/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_table_json`].
8124/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_table_json`].
8125/// - `list_result_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_table_json`].
8126///
8127/// # Returns
8128///
8129/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_manage_table_json`] can produce a value and [`None`] otherwise.
8130///
8131/// # Performance
8132///
8133/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_manage_table_json`].
8134///
8135/// # Examples
8136///
8137/// ```rust,ignore
8138/// let _result = pharos_native::runtime::json_commands::generated_surface_manage_table_json(std::path::Path::new("apps/dev_docs"), "{}", todo!(), todo!());
8139/// ```
8140///
8141/// # Related
8142///
8143/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8144pub fn generated_surface_manage_table_json(
8145 app_root: &Path,
8146 surface_json: &str,
8147 context_json: &str,
8148 list_result_json: &str,
8149) -> Option<String> {
8150 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
8151 let list = serde_json::from_str::<Value>(list_result_json).ok()?;
8152 let columns = surface
8153 .get("admin")
8154 .and_then(|value| value.get("list_columns"))
8155 .cloned()
8156 .or_else(|| surface.get("response_fields").cloned())
8157 .unwrap_or_else(|| Value::Array(Vec::new()));
8158 let mut header = String::new();
8159 if !generated::runtime_generated_surface_append_table_header_cells_html(
8160 &mut header,
8161 &columns,
8162 app_root,
8163 ) {
8164 return None;
8165 }
8166 let fields: Vec<String> = columns
8167 .as_array()
8168 .map(|items| {
8169 items
8170 .iter()
8171 .filter_map(|item| {
8172 item.get("field")
8173 .and_then(Value::as_str)
8174 .or_else(|| item.as_str())
8175 })
8176 .map(ToOwned::to_owned)
8177 .collect()
8178 })
8179 .unwrap_or_default();
8180 let mut rows_html = String::new();
8181 for row in list.get("items").and_then(Value::as_array)? {
8182 let mut cells = String::new();
8183 for field in &fields {
8184 let field_value =
8185 html_escape_text(&value_to_text(row.get(field).unwrap_or(&Value::Null)));
8186 let cell = render_fragment_with_params(
8187 app_root,
8188 "templates/generated_admin/fragments/table_row_cell.html",
8189 serde_json::Map::from_iter([(
8190 "field_value".to_owned(),
8191 Value::String(field_value),
8192 )]),
8193 )?;
8194 cells.push_str(&cell);
8195 }
8196 rows_html.push_str(&render_fragment_with_params(
8197 app_root,
8198 "templates/generated_admin/fragments/table_row.html",
8199 serde_json::Map::from_iter([
8200 ("cells_html".to_owned(), Value::String(cells)),
8201 ("action_cell_html".to_owned(), Value::String(String::new())),
8202 ]),
8203 )?);
8204 }
8205 let _ = context_json;
8206 serde_json::to_string(&serde_json::json!({ "header_html": header, "row_html": rows_html, "column_count": fields.len() })).ok()
8207}
8208
8209/// Builds the generated-admin manage-page JSON contract.
8210///
8211/// # Parameters
8212///
8213/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_page_json`].
8214/// - `state_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_page_json`].
8215/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_page_json`].
8216/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_page_json`].
8217/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_page_json`].
8218/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::generated_surface_manage_page_json`].
8219///
8220/// # Returns
8221///
8222/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_manage_page_json`] can produce a value and [`None`] otherwise.
8223///
8224/// # Performance
8225///
8226/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_manage_page_json`].
8227///
8228/// # Examples
8229///
8230/// ```rust,ignore
8231/// let _result = pharos_native::runtime::json_commands::generated_surface_manage_page_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), "{}", todo!(), todo!());
8232/// ```
8233///
8234/// # Related
8235///
8236/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8237pub fn generated_surface_manage_page_json(
8238 app_root: &Path,
8239 state_path: &Path,
8240 fixture_path: &Path,
8241 surface_json: &str,
8242 context_json: &str,
8243 raw_query: &str,
8244) -> Option<String> {
8245 let list_result =
8246 generated_surface_list_result_json(state_path, surface_json, context_json, raw_query)?;
8247 let table =
8248 generated_surface_manage_table_json(app_root, surface_json, context_json, &list_result)?;
8249 let form_fields = generated_surface_form_fields_html(
8250 app_root,
8251 state_path,
8252 fixture_path,
8253 surface_json,
8254 context_json,
8255 None,
8256 )?;
8257 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
8258 let context = serde_json::from_str::<Value>(context_json).ok()?;
8259 let path_for = |operation: &str| {
8260 let context_value = surface
8261 .get("context_binding")
8262 .and_then(|value| value.get("param"))
8263 .and_then(Value::as_str)
8264 .and_then(|param| context.get(param))
8265 .map(value_to_text);
8266 generated::runtime_generated_surface_operation_path(
8267 &surface,
8268 context_value.as_deref(),
8269 operation,
8270 None,
8271 )
8272 .unwrap_or_default()
8273 };
8274 serde_json::to_string(&serde_json::json!({
8275 "title": surface.get("admin").and_then(|value| value.get("manage_title")).and_then(Value::as_str).unwrap_or("Manage records"),
8276 "manage_path": path_for("manage"),
8277 "create_path": path_for("create"),
8278 "back_path": path_for("manage"),
8279 "list_result": serde_json::from_str::<Value>(&list_result).ok()?,
8280 "manage_table": serde_json::from_str::<Value>(&table).ok()?,
8281 "form_fields_html": form_fields,
8282 "filter_controls_html": generated_surface_filter_controls_html(app_root, surface_json, raw_query).unwrap_or_default(),
8283 "sort_options_html": generated_surface_sort_options_html(app_root, surface_json, raw_query).unwrap_or_default(),
8284 })).ok()
8285}
8286
8287/// Renders the service visual using the app-owned image or badge fragment.
8288///
8289/// # Parameters
8290///
8291/// - `app_root`: Input consumed by [`crate::runtime::json_commands::service_visual_html`].
8292/// - `image_url`: Input consumed by [`crate::runtime::json_commands::service_visual_html`].
8293/// - `service_name`: Input consumed by [`crate::runtime::json_commands::service_visual_html`].
8294/// - `primary_color`: Input consumed by [`crate::runtime::json_commands::service_visual_html`].
8295///
8296/// # Returns
8297///
8298/// Returns [`Some`] when [`crate::runtime::json_commands::service_visual_html`] can produce a value and [`None`] otherwise.
8299///
8300/// # Performance
8301///
8302/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::service_visual_html`].
8303///
8304/// # Examples
8305///
8306/// ```rust,ignore
8307/// let _result = pharos_native::runtime::json_commands::service_visual_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
8308/// ```
8309///
8310/// # Related
8311///
8312/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8313pub fn service_visual_html(
8314 app_root: &Path,
8315 image_url: Option<&str>,
8316 service_name: &str,
8317 primary_color: Option<&str>,
8318) -> Option<String> {
8319 if let Some(image_url) = image_url.filter(|value| !value.is_empty() && *value != "null") {
8320 return render_fragment_with_params(
8321 app_root,
8322 "templates/shared/fragments/visual_image.html",
8323 serde_json::Map::from_iter([
8324 (
8325 "wrapper_class".to_owned(),
8326 Value::String("service-visual service-visual-image".to_owned()),
8327 ),
8328 ("image_url".to_owned(), Value::String(image_url.to_owned())),
8329 (
8330 "image_alt".to_owned(),
8331 Value::String(service_name.to_owned()),
8332 ),
8333 ]),
8334 );
8335 }
8336 let initials = dynamic_service_abbreviation(service_name);
8337 render_fragment_with_params(
8338 app_root,
8339 "templates/shared/fragments/visual_badge.html",
8340 serde_json::Map::from_iter([
8341 (
8342 "wrapper_class".to_owned(),
8343 Value::String("service-visual service-visual-badge".to_owned()),
8344 ),
8345 (
8346 "style_attr".to_owned(),
8347 Value::String(format!(
8348 "background:{};color:#fff;",
8349 primary_color
8350 .filter(|value| !value.is_empty())
8351 .unwrap_or("#2563eb")
8352 )),
8353 ),
8354 (
8355 "text_value".to_owned(),
8356 Value::String(if initials.is_empty() {
8357 "XX".to_owned()
8358 } else {
8359 initials
8360 }),
8361 ),
8362 ]),
8363 )
8364}
8365
8366/// Renders service sort-order options through the shared select-option fragment.
8367///
8368/// # Parameters
8369///
8370/// - `app_root`: Input consumed by [`crate::runtime::json_commands::service_sort_order_options_html`].
8371/// - `state_root`: Input consumed by [`crate::runtime::json_commands::service_sort_order_options_html`].
8372/// - `business_id`: Input consumed by [`crate::runtime::json_commands::service_sort_order_options_html`].
8373/// - `selected_order`: Input consumed by [`crate::runtime::json_commands::service_sort_order_options_html`].
8374/// - `include_append`: Input consumed by [`crate::runtime::json_commands::service_sort_order_options_html`].
8375///
8376/// # Returns
8377///
8378/// Returns [`Some`] when [`crate::runtime::json_commands::service_sort_order_options_html`] can produce a value and [`None`] otherwise.
8379///
8380/// # Performance
8381///
8382/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::service_sort_order_options_html`].
8383///
8384/// # Examples
8385///
8386/// ```rust,ignore
8387/// let _result = pharos_native::runtime::json_commands::service_sort_order_options_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
8388/// ```
8389///
8390/// # Related
8391///
8392/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8393pub fn service_sort_order_options_html(
8394 app_root: &Path,
8395 state_root: &Value,
8396 business_id: i64,
8397 selected_order: i64,
8398 include_append: bool,
8399) -> Option<String> {
8400 let count = state_rows(state_root, "services")
8401 .iter()
8402 .filter(|row| {
8403 row.get("business_id")
8404 .map(value_to_text)
8405 .and_then(|value| value.parse::<i64>().ok())
8406 == Some(business_id)
8407 && row
8408 .get("is_active")
8409 .and_then(Value::as_bool)
8410 .unwrap_or(true)
8411 })
8412 .count() as i64;
8413 let max_order = if include_append { count + 1 } else { count }.max(1);
8414 let selected = if selected_order > 0 && selected_order <= max_order {
8415 selected_order
8416 } else {
8417 max_order
8418 };
8419 let mut out = String::new();
8420 for order in 1..=max_order {
8421 out.push_str(&render_fragment_with_params(
8422 app_root,
8423 "templates/shared/fragments/select_option.html",
8424 serde_json::Map::from_iter([
8425 ("option_value".to_owned(), Value::String(order.to_string())),
8426 (
8427 "selected_attr".to_owned(),
8428 Value::String(if order == selected { " selected" } else { "" }.to_owned()),
8429 ),
8430 ("option_label".to_owned(), Value::String(order.to_string())),
8431 ]),
8432 )?);
8433 }
8434 Some(out)
8435}
8436
8437/// Renders the booking-option editor from one service state record.
8438///
8439/// # Parameters
8440///
8441/// - `app_root`: Input consumed by [`crate::runtime::json_commands::service_booking_options_editor_html`].
8442/// - `state_root`: Input consumed by [`crate::runtime::json_commands::service_booking_options_editor_html`].
8443/// - `service_id`: Input consumed by [`crate::runtime::json_commands::service_booking_options_editor_html`].
8444///
8445/// # Returns
8446///
8447/// Returns [`Some`] when [`crate::runtime::json_commands::service_booking_options_editor_html`] can produce a value and [`None`] otherwise.
8448///
8449/// # Performance
8450///
8451/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::service_booking_options_editor_html`].
8452///
8453/// # Examples
8454///
8455/// ```rust,ignore
8456/// let _result = pharos_native::runtime::json_commands::service_booking_options_editor_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
8457/// ```
8458///
8459/// # Related
8460///
8461/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8462pub fn service_booking_options_editor_html(
8463 app_root: &Path,
8464 state_root: &Value,
8465 service_id: i64,
8466) -> Option<String> {
8467 let options = state_row_by_id(state_root, "services", service_id)
8468 .and_then(|row| row.get("booking_options"))
8469 .and_then(Value::as_array)
8470 .cloned()
8471 .unwrap_or_default();
8472 let mut rows_html = String::new();
8473 for (index, option) in options.iter().enumerate() {
8474 rows_html.push_str(&render_fragment_with_params(
8475 app_root,
8476 "templates/businesses/fragments/service_booking_option_row.html",
8477 serde_json::Map::from_iter([
8478 (
8479 "option_index".to_owned(),
8480 Value::String((index + 1).to_string()),
8481 ),
8482 (
8483 "label_value".to_owned(),
8484 Value::String(option.get("label").map(value_to_text).unwrap_or_default()),
8485 ),
8486 (
8487 "duration_value".to_owned(),
8488 Value::String(
8489 option
8490 .get("duration_minutes")
8491 .map(value_to_text)
8492 .unwrap_or_else(|| "45".to_owned()),
8493 ),
8494 ),
8495 (
8496 "cost_value".to_owned(),
8497 Value::String(option.get("cost").map(value_to_text).unwrap_or_default()),
8498 ),
8499 ]),
8500 )?);
8501 }
8502 render_fragment_with_params(
8503 app_root,
8504 "templates/businesses/fragments/service_booking_options_editor.html",
8505 serde_json::Map::from_iter([
8506 (
8507 "option_count".to_owned(),
8508 Value::String(options.len().to_string()),
8509 ),
8510 (
8511 "option_count_text".to_owned(),
8512 Value::String(options.len().to_string()),
8513 ),
8514 ("option_rows_html".to_owned(), Value::String(rows_html)),
8515 ]),
8516 )
8517}
8518
8519/// Renders technician assignment checkbox fragments for one business/service.
8520///
8521/// # Parameters
8522///
8523/// - `app_root`: Input consumed by [`crate::runtime::json_commands::technician_checkboxes_html`].
8524/// - `state_root`: Input consumed by [`crate::runtime::json_commands::technician_checkboxes_html`].
8525/// - `business_id`: Input consumed by [`crate::runtime::json_commands::technician_checkboxes_html`].
8526/// - `service_id`: Input consumed by [`crate::runtime::json_commands::technician_checkboxes_html`].
8527/// - `empty_message`: Input consumed by [`crate::runtime::json_commands::technician_checkboxes_html`].
8528/// - `select_all`: Input consumed by [`crate::runtime::json_commands::technician_checkboxes_html`].
8529///
8530/// # Returns
8531///
8532/// Returns [`Some`] when [`crate::runtime::json_commands::technician_checkboxes_html`] can produce a value and [`None`] otherwise.
8533///
8534/// # Performance
8535///
8536/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::technician_checkboxes_html`].
8537///
8538/// # Examples
8539///
8540/// ```rust,ignore
8541/// let _result = pharos_native::runtime::json_commands::technician_checkboxes_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!(), todo!());
8542/// ```
8543///
8544/// # Related
8545///
8546/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8547pub fn technician_checkboxes_html(
8548 app_root: &Path,
8549 state_root: &Value,
8550 business_id: i64,
8551 service_id: i64,
8552 empty_message: &str,
8553 select_all: bool,
8554) -> Option<String> {
8555 let assigned = state_row_by_id(state_root, "services", service_id)
8556 .and_then(|row| row.get("technician_ids"))
8557 .and_then(Value::as_array)
8558 .cloned()
8559 .unwrap_or_default();
8560 let mut out = String::new();
8561 for technician in state_rows(state_root, "technicians").iter().filter(|row| {
8562 row.get("business_id")
8563 .map(value_to_text)
8564 .and_then(|value| value.parse::<i64>().ok())
8565 == Some(business_id)
8566 && row.get("is_active").and_then(Value::as_bool) != Some(false)
8567 }) {
8568 let id = technician.get("id").map(value_to_text).unwrap_or_default();
8569 let checked = select_all || assigned.iter().map(value_to_text).any(|value| value == id);
8570 let technician_name = technician
8571 .get("name")
8572 .map(value_to_text)
8573 .unwrap_or_default();
8574 let label_html = render_fragment_with_params(
8575 app_root,
8576 "templates/shared/fragments/ui_label_technician_assignment.html",
8577 serde_json::Map::from_iter([(
8578 "technician_name".to_owned(),
8579 Value::String(technician_name.clone()),
8580 )]),
8581 )?;
8582 let technician_assignment_checkbox_ui_html = render_fragment_with_params(
8583 app_root,
8584 "templates/shared/fragments/ui_checkbox.html",
8585 serde_json::Map::from_iter([
8586 ("id".to_owned(), Value::String(format!("technician-{id}"))),
8587 ("name".to_owned(), Value::String(format!("technician_{id}"))),
8588 ("class_name".to_owned(), Value::String(String::new())),
8589 ("value".to_owned(), Value::String(id)),
8590 ("label_class".to_owned(), Value::String(String::new())),
8591 (
8592 "attributes_html".to_owned(),
8593 Value::String(if checked { " checked" } else { "" }.to_owned()),
8594 ),
8595 ("label_html".to_owned(), Value::String(label_html)),
8596 ("help_html".to_owned(), Value::String(String::new())),
8597 ]),
8598 )?;
8599 out.push_str(&render_fragment_with_params(
8600 app_root,
8601 "templates/businesses/fragments/technician_checkbox.html",
8602 serde_json::Map::from_iter([(
8603 "technician_assignment_checkbox_ui_html".to_owned(),
8604 Value::String(technician_assignment_checkbox_ui_html),
8605 )]),
8606 )?);
8607 }
8608 if out.is_empty() {
8609 render_fragment_with_params(
8610 app_root,
8611 "templates/shared/fragments/muted_text.html",
8612 serde_json::Map::from_iter([(
8613 "message".to_owned(),
8614 Value::String(empty_message.to_owned()),
8615 )]),
8616 )
8617 } else {
8618 Some(out)
8619 }
8620}
8621
8622/// Builds the generated-admin edit-page JSON contract for one existing item.
8623///
8624/// # Parameters
8625///
8626/// - `app_root`: Input consumed by [`crate::runtime::json_commands::generated_surface_edit_page_json`].
8627/// - `state_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_edit_page_json`].
8628/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_edit_page_json`].
8629/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_edit_page_json`].
8630/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_edit_page_json`].
8631/// - `item_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_edit_page_json`].
8632///
8633/// # Returns
8634///
8635/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_edit_page_json`] can produce a value and [`None`] otherwise.
8636///
8637/// # Performance
8638///
8639/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_edit_page_json`].
8640///
8641/// # Examples
8642///
8643/// ```rust,ignore
8644/// let _result = pharos_native::runtime::json_commands::generated_surface_edit_page_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), "{}", todo!(), "{}");
8645/// ```
8646///
8647/// # Related
8648///
8649/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8650pub fn generated_surface_edit_page_json(
8651 app_root: &Path,
8652 state_path: &Path,
8653 fixture_path: &Path,
8654 surface_json: &str,
8655 context_json: &str,
8656 item_json: &str,
8657) -> Option<String> {
8658 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
8659 let context = serde_json::from_str::<Value>(context_json).ok()?;
8660 let item = serde_json::from_str::<Value>(item_json).ok()?;
8661 let identity_field = surface
8662 .get("identity_field")
8663 .and_then(Value::as_str)
8664 .unwrap_or("id");
8665 let identity = item
8666 .get(identity_field)
8667 .map(value_to_text)
8668 .unwrap_or_default();
8669 let context_value = surface
8670 .get("context_binding")
8671 .and_then(|value| value.get("param"))
8672 .and_then(Value::as_str)
8673 .and_then(|param| context.get(param))
8674 .map(value_to_text);
8675 let form_fields = generated_surface_form_fields_html(
8676 app_root,
8677 state_path,
8678 fixture_path,
8679 surface_json,
8680 context_json,
8681 Some(item_json),
8682 )?;
8683 let title_field = surface
8684 .get("admin")
8685 .and_then(|value| value.get("item_title_field"))
8686 .and_then(Value::as_str)
8687 .unwrap_or("name");
8688 let title_value = generated_surface_display_value(item_json, title_field)
8689 .unwrap_or_else(|| format!("Record {identity}"));
8690 serde_json::to_string(&serde_json::json!({
8691 "title": format!("Edit {title_value}"),
8692 "manage_path": generated::runtime_generated_surface_operation_path(&surface, context_value.as_deref(), "manage", None).unwrap_or_default(),
8693 "update_path": generated::runtime_generated_surface_operation_path(&surface, context_value.as_deref(), "update", Some(&identity)).unwrap_or_default(),
8694 "form_fields_html": form_fields,
8695 "save_button_label": surface.get("admin").and_then(|value| value.get("save_button_label")).and_then(Value::as_str).unwrap_or("Save"),
8696 "delete_form_html": "",
8697 })).ok()
8698}
8699
8700const MAX_TEXT_BYTES: usize = 2 * 1024 * 1024;
8701const MAX_STATE_ROWS_JSON: usize = 16 * 1024;
8702const MAX_OUTPUT_ROWS_JSON: usize = 4096;
8703
8704/// Performs compact.
8705fn compact(value: &Value) -> Option<String> {
8706 let text = serde_json::to_string(value).ok()?;
8707 (text.len() <= MAX_TEXT_BYTES).then_some(text)
8708}
8709
8710/// Returns compact JSON text for a JSON value.
8711///
8712/// # Parameters
8713///
8714/// - `value`: Input consumed by [`crate::runtime::json_commands::emit_compact_json_value`].
8715///
8716/// # Returns
8717///
8718/// Returns [`Some`] when [`crate::runtime::json_commands::emit_compact_json_value`] can produce a value and [`None`] otherwise.
8719///
8720/// # Examples
8721///
8722/// ```rust,ignore
8723/// let _result = pharos_native::runtime::json_commands::emit_compact_json_value("value");
8724/// ```
8725///
8726/// # Related
8727///
8728/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8729pub fn emit_compact_json_value(value: &Value) -> Option<String> {
8730 compact(value)
8731}
8732
8733/// Returns compact JSON text or an empty object for invalid input.
8734///
8735/// # Parameters
8736///
8737/// - `value`: Input consumed by [`crate::runtime::json_commands::emit_json_text`].
8738///
8739/// # Returns
8740///
8741/// Returns the computed value produced by [`crate::runtime::json_commands::emit_json_text`].
8742///
8743/// # Examples
8744///
8745/// ```rust,ignore
8746/// let _result = pharos_native::runtime::json_commands::emit_json_text("value");
8747/// ```
8748///
8749/// # Related
8750///
8751/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8752pub fn emit_json_text(value: &Value) -> String {
8753 compact(value).unwrap_or_else(|| "{}".to_owned())
8754}
8755
8756/// Returns compact JSON text or an empty object for invalid serialized input.
8757///
8758/// # Parameters
8759///
8760/// - `text`: Input consumed by [`crate::runtime::json_commands::emit_json_or_empty`].
8761///
8762/// # Returns
8763///
8764/// Returns the computed value produced by [`crate::runtime::json_commands::emit_json_or_empty`].
8765///
8766/// # Performance
8767///
8768/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_json_or_empty`].
8769///
8770/// # Examples
8771///
8772/// ```rust,ignore
8773/// let _result = pharos_native::runtime::json_commands::emit_json_or_empty("{}");
8774/// ```
8775///
8776/// # Related
8777///
8778/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8779pub fn emit_json_or_empty(text: &str) -> String {
8780 serde_json::from_str::<Value>(text)
8781 .ok()
8782 .and_then(|value| compact(&value))
8783 .unwrap_or_else(|| "{}".to_owned())
8784}
8785
8786/// Serializes one string as a JSON string.
8787///
8788/// # Parameters
8789///
8790/// - `text`: Input consumed by [`crate::runtime::json_commands::emit_json_string`].
8791///
8792/// # Returns
8793///
8794/// Returns [`Some`] when [`crate::runtime::json_commands::emit_json_string`] can produce a value and [`None`] otherwise.
8795///
8796/// # Examples
8797///
8798/// ```rust,ignore
8799/// let _result = pharos_native::runtime::json_commands::emit_json_string("{}");
8800/// ```
8801///
8802/// # Related
8803///
8804/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8805pub fn emit_json_string(text: &str) -> Option<String> {
8806 compact(&Value::String(text.to_owned()))
8807}
8808
8809/// Serializes one signed integer as JSON text.
8810///
8811/// # Parameters
8812///
8813/// - `value`: Input consumed by [`crate::runtime::json_commands::emit_long`].
8814///
8815/// # Returns
8816///
8817/// Returns the computed value produced by [`crate::runtime::json_commands::emit_long`].
8818///
8819/// # Examples
8820///
8821/// ```rust,ignore
8822/// let _result = pharos_native::runtime::json_commands::emit_long("value");
8823/// ```
8824///
8825/// # Related
8826///
8827/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8828pub fn emit_long(value: i64) -> String {
8829 value.to_string()
8830}
8831
8832/// Serializes a boolean as the native numeric flag representation.
8833///
8834/// # Parameters
8835///
8836/// - `value`: Input consumed by [`crate::runtime::json_commands::emit_digit_bool`].
8837///
8838/// # Returns
8839///
8840/// Returns the computed value produced by [`crate::runtime::json_commands::emit_digit_bool`].
8841///
8842/// # Examples
8843///
8844/// ```rust,ignore
8845/// let _result = pharos_native::runtime::json_commands::emit_digit_bool("value");
8846/// ```
8847///
8848/// # Related
8849///
8850/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8851pub fn emit_digit_bool(value: bool) -> &'static str {
8852 if value { "1" } else { "0" }
8853}
8854
8855/// Returns a bounded line with a trailing newline.
8856///
8857/// # Parameters
8858///
8859/// - `text`: Input consumed by [`crate::runtime::json_commands::emit_text_line`].
8860///
8861/// # Returns
8862///
8863/// Returns [`Some`] when [`crate::runtime::json_commands::emit_text_line`] can produce a value and [`None`] otherwise.
8864///
8865/// # Performance
8866///
8867/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_text_line`].
8868///
8869/// # Examples
8870///
8871/// ```rust,ignore
8872/// let _result = pharos_native::runtime::json_commands::emit_text_line("{}");
8873/// ```
8874///
8875/// # Related
8876///
8877/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8878pub fn emit_text_line(text: &str) -> Option<String> {
8879 if text.len() >= MAX_TEXT_BYTES || text.chars().any(char::is_control) {
8880 return None;
8881 }
8882 Some(format!("{text}\n"))
8883}
8884
8885/// Adds a nullable string field to an object.
8886///
8887/// # Parameters
8888///
8889/// - `object`: Input consumed by [`crate::runtime::json_commands::add_json_string_or_null`].
8890/// - `key`: Input consumed by [`crate::runtime::json_commands::add_json_string_or_null`].
8891/// - `value`: Input consumed by [`crate::runtime::json_commands::add_json_string_or_null`].
8892///
8893/// # Returns
8894///
8895/// Returns `true` when the condition checked by [`crate::runtime::json_commands::add_json_string_or_null`] holds and `false` otherwise.
8896///
8897/// # Examples
8898///
8899/// ```rust,ignore
8900/// let _result = pharos_native::runtime::json_commands::add_json_string_or_null(todo!(), "key", "value");
8901/// ```
8902///
8903/// # Related
8904///
8905/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8906pub fn add_json_string_or_null(
8907 object: &mut Map<String, Value>,
8908 key: &str,
8909 value: Option<&str>,
8910) -> bool {
8911 if key.is_empty() {
8912 return false;
8913 }
8914 object.insert(
8915 key.to_owned(),
8916 value.map_or(Value::Null, |text| Value::String(text.to_owned())),
8917 );
8918 true
8919}
8920
8921/// Returns an optional string field from JSON text.
8922///
8923/// # Parameters
8924///
8925/// - `text`: Input consumed by [`crate::runtime::json_commands::json_option_value`].
8926/// - `key`: Input consumed by [`crate::runtime::json_commands::json_option_value`].
8927///
8928/// # Returns
8929///
8930/// Returns [`Some`] when [`crate::runtime::json_commands::json_option_value`] can produce a value and [`None`] otherwise.
8931///
8932/// # Performance
8933///
8934/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_option_value`].
8935///
8936/// # Examples
8937///
8938/// ```rust,ignore
8939/// let _result = pharos_native::runtime::json_commands::json_option_value("{}", "key");
8940/// ```
8941///
8942/// # Related
8943///
8944/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8945pub fn json_option_value(text: &str, key: &str) -> Option<String> {
8946 if key.is_empty() {
8947 return None;
8948 }
8949 serde_json::from_str::<Value>(text)
8950 .ok()?
8951 .get(key)
8952 .and_then(Value::as_str)
8953 .map(ToOwned::to_owned)
8954}
8955
8956/// Compares one JSON field with exact text.
8957///
8958/// # Parameters
8959///
8960/// - `text`: Input consumed by [`crate::runtime::json_commands::json_value_equals`].
8961/// - `key`: Input consumed by [`crate::runtime::json_commands::json_value_equals`].
8962/// - `expected`: Input consumed by [`crate::runtime::json_commands::json_value_equals`].
8963///
8964/// # Returns
8965///
8966/// Returns `true` when the condition checked by [`crate::runtime::json_commands::json_value_equals`] holds and `false` otherwise.
8967///
8968/// # Examples
8969///
8970/// ```rust,ignore
8971/// let _result = pharos_native::runtime::json_commands::json_value_equals("{}", "key", "value");
8972/// ```
8973///
8974/// # Related
8975///
8976/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
8977pub fn json_value_equals(text: &str, key: &str, expected: &str) -> bool {
8978 json_option_value(text, key).as_deref() == Some(expected)
8979}
8980
8981/// Returns a JSON object field as text.
8982///
8983/// # Parameters
8984///
8985/// - `text`: Input consumed by [`crate::runtime::json_commands::emit_object_string_field`].
8986/// - `key`: Input consumed by [`crate::runtime::json_commands::emit_object_string_field`].
8987///
8988/// # Returns
8989///
8990/// Returns [`Some`] when [`crate::runtime::json_commands::emit_object_string_field`] can produce a value and [`None`] otherwise.
8991///
8992/// # Examples
8993///
8994/// ```rust,ignore
8995/// let _result = pharos_native::runtime::json_commands::emit_object_string_field("{}", "key");
8996/// ```
8997///
8998/// # Related
8999///
9000/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9001pub fn emit_object_string_field(text: &str, key: &str) -> Option<String> {
9002 json_option_value(text, key)
9003}
9004
9005/// Returns a JSON value at a dotted object path.
9006///
9007/// # Parameters
9008///
9009/// - `text`: Input consumed by [`crate::runtime::json_commands::object_value_at_path`].
9010/// - `path`: Input consumed by [`crate::runtime::json_commands::object_value_at_path`].
9011///
9012/// # Returns
9013///
9014/// Returns [`Some`] when [`crate::runtime::json_commands::object_value_at_path`] can produce a value and [`None`] otherwise.
9015///
9016/// # Performance
9017///
9018/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::object_value_at_path`].
9019///
9020/// # Examples
9021///
9022/// ```rust,ignore
9023/// let _result = pharos_native::runtime::json_commands::object_value_at_path("{}", std::path::Path::new("fixture.json"));
9024/// ```
9025///
9026/// # Related
9027///
9028/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9029pub fn object_value_at_path(text: &str, path: &str) -> Option<String> {
9030 if path.is_empty() {
9031 return None;
9032 }
9033 let mut value = serde_json::from_str::<Value>(text).ok()?;
9034 for segment in path.split('.') {
9035 if segment.is_empty() {
9036 return None;
9037 }
9038 value = value.get(segment)?.clone();
9039 }
9040 compact(&value)
9041}
9042
9043/// Finds an array item by a string field.
9044///
9045/// # Parameters
9046///
9047/// - `text`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_string_field`].
9048/// - `section`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_string_field`].
9049/// - `field`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_string_field`].
9050/// - `expected`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_string_field`].
9051///
9052/// # Returns
9053///
9054/// Returns [`Some`] when [`crate::runtime::json_commands::find_array_item_by_string_field`] can produce a value and [`None`] otherwise.
9055///
9056/// # Performance
9057///
9058/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_array_item_by_string_field`].
9059///
9060/// # Examples
9061///
9062/// ```rust,ignore
9063/// let _result = pharos_native::runtime::json_commands::find_array_item_by_string_field("{}", "items", "field", "value");
9064/// ```
9065///
9066/// # Related
9067///
9068/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9069pub fn find_array_item_by_string_field(
9070 text: &str,
9071 section: &str,
9072 field: &str,
9073 expected: &str,
9074) -> Option<String> {
9075 let root = serde_json::from_str::<Value>(text).ok()?;
9076 root.get(section)
9077 .and_then(Value::as_array)?
9078 .iter()
9079 .find(|item| item.get(field).and_then(Value::as_str) == Some(expected))
9080 .and_then(compact)
9081}
9082
9083/// Finds a state user by numeric id.
9084///
9085/// # Parameters
9086///
9087/// - `text`: Input consumed by [`crate::runtime::json_commands::state_find_user_by_id`].
9088/// - `user_id`: Input consumed by [`crate::runtime::json_commands::state_find_user_by_id`].
9089///
9090/// # Returns
9091///
9092/// Returns [`Some`] when [`crate::runtime::json_commands::state_find_user_by_id`] can produce a value and [`None`] otherwise.
9093///
9094/// # Performance
9095///
9096/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::state_find_user_by_id`].
9097///
9098/// # Examples
9099///
9100/// ```rust,ignore
9101/// let _result = pharos_native::runtime::json_commands::state_find_user_by_id("{}", 1);
9102/// ```
9103///
9104/// # Related
9105///
9106/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9107pub fn state_find_user_by_id(text: &str, user_id: i64) -> Option<String> {
9108 let root = serde_json::from_str::<Value>(text).ok()?;
9109 root.get("users")
9110 .and_then(Value::as_array)?
9111 .iter()
9112 .find(|row| row.get("id").and_then(Value::as_i64) == Some(user_id))
9113 .and_then(compact)
9114}
9115
9116/// Finds an appointment by numeric id.
9117///
9118/// # Parameters
9119///
9120/// - `text`: Input consumed by [`crate::runtime::json_commands::state_find_appointment_by_id`].
9121/// - `appointment_id`: Input consumed by [`crate::runtime::json_commands::state_find_appointment_by_id`].
9122///
9123/// # Returns
9124///
9125/// Returns [`Some`] when [`crate::runtime::json_commands::state_find_appointment_by_id`] can produce a value and [`None`] otherwise.
9126///
9127/// # Performance
9128///
9129/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::state_find_appointment_by_id`].
9130///
9131/// # Examples
9132///
9133/// ```rust,ignore
9134/// let _result = pharos_native::runtime::json_commands::state_find_appointment_by_id("{}", 1);
9135/// ```
9136///
9137/// # Related
9138///
9139/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9140pub fn state_find_appointment_by_id(text: &str, appointment_id: i64) -> Option<String> {
9141 let root = serde_json::from_str::<Value>(text).ok()?;
9142 root.get("appointments")
9143 .and_then(Value::as_array)?
9144 .iter()
9145 .find(|row| row.get("id").and_then(Value::as_i64) == Some(appointment_id))
9146 .and_then(compact)
9147}
9148
9149/// Finds a user by case-insensitive email.
9150///
9151/// # Parameters
9152///
9153/// - `text`: Input consumed by [`crate::runtime::json_commands::state_find_user_by_email`].
9154/// - `email`: Input consumed by [`crate::runtime::json_commands::state_find_user_by_email`].
9155///
9156/// # Returns
9157///
9158/// Returns [`Some`] when [`crate::runtime::json_commands::state_find_user_by_email`] can produce a value and [`None`] otherwise.
9159///
9160/// # Performance
9161///
9162/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::state_find_user_by_email`].
9163///
9164/// # Examples
9165///
9166/// ```rust,ignore
9167/// let _result = pharos_native::runtime::json_commands::state_find_user_by_email("{}", todo!());
9168/// ```
9169///
9170/// # Related
9171///
9172/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9173pub fn state_find_user_by_email(text: &str, email: &str) -> Option<String> {
9174 let root = serde_json::from_str::<Value>(text).ok()?;
9175 root.get("users")
9176 .and_then(Value::as_array)?
9177 .iter()
9178 .find(|row| {
9179 row.get("email")
9180 .and_then(Value::as_str)
9181 .is_some_and(|value| value.eq_ignore_ascii_case(email))
9182 })
9183 .and_then(compact)
9184}
9185
9186/// Finds a user by exact username.
9187///
9188/// # Parameters
9189///
9190/// - `text`: Input consumed by [`crate::runtime::json_commands::state_find_user_by_username`].
9191/// - `username`: Input consumed by [`crate::runtime::json_commands::state_find_user_by_username`].
9192///
9193/// # Returns
9194///
9195/// Returns [`Some`] when [`crate::runtime::json_commands::state_find_user_by_username`] can produce a value and [`None`] otherwise.
9196///
9197/// # Examples
9198///
9199/// ```rust,ignore
9200/// let _result = pharos_native::runtime::json_commands::state_find_user_by_username("{}", todo!());
9201/// ```
9202///
9203/// # Related
9204///
9205/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9206pub fn state_find_user_by_username(text: &str, username: &str) -> Option<String> {
9207 find_array_item_by_string_field(text, "users", "username", username)
9208}
9209
9210/// Adds an array field to an object if it is absent.
9211///
9212/// # Parameters
9213///
9214/// - `root`: Input consumed by [`crate::runtime::json_commands::ensure_mut_array_field`].
9215/// - `key`: Input consumed by [`crate::runtime::json_commands::ensure_mut_array_field`].
9216///
9217/// # Returns
9218///
9219/// Returns `true` when the condition checked by [`crate::runtime::json_commands::ensure_mut_array_field`] holds and `false` otherwise.
9220///
9221/// # Examples
9222///
9223/// ```rust,ignore
9224/// let _result = pharos_native::runtime::json_commands::ensure_mut_array_field(std::path::Path::new("."), "key");
9225/// ```
9226///
9227/// # Related
9228///
9229/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9230pub fn ensure_mut_array_field(root: &mut Map<String, Value>, key: &str) -> bool {
9231 if key.is_empty() {
9232 return false;
9233 }
9234 root.entry(key.to_owned())
9235 .or_insert_with(|| Value::Array(Vec::new()))
9236 .is_array()
9237}
9238
9239/// Finds a multipart text field using the existing bounded multipart parser.
9240///
9241/// # Parameters
9242///
9243/// - `content_type`: Input consumed by [`crate::runtime::json_commands::multipart_find_part`].
9244/// - `body_path`: Input consumed by [`crate::runtime::json_commands::multipart_find_part`].
9245/// - `field_name`: Input consumed by [`crate::runtime::json_commands::multipart_find_part`].
9246///
9247/// # Returns
9248///
9249/// Returns [`Some`] when [`crate::runtime::json_commands::multipart_find_part`] can produce a value and [`None`] otherwise.
9250///
9251/// # Examples
9252///
9253/// ```rust,ignore
9254/// let _result = pharos_native::runtime::json_commands::multipart_find_part(todo!(), todo!(), todo!());
9255/// ```
9256///
9257/// # Related
9258///
9259/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9260pub fn multipart_find_part(
9261 content_type: &str,
9262 body_path: &Path,
9263 field_name: &str,
9264) -> Option<String> {
9265 dynamic_multipart_extract_text(content_type, body_path, field_name)
9266}
9267
9268/// Exports relational runtime state through the configured native adapter.
9269///
9270/// # Parameters
9271///
9272/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_export_json_runtime`].
9273/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_export_json_runtime`].
9274/// - `app_id`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_export_json_runtime`].
9275///
9276/// # Returns
9277///
9278/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_runtime_state_export_json_runtime`] can produce a value and [`None`] otherwise.
9279///
9280/// # Examples
9281///
9282/// ```rust,ignore
9283/// let _result = pharos_native::runtime::json_commands::dynamic_runtime_state_export_json_runtime(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
9284/// ```
9285///
9286/// # Related
9287///
9288/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9289pub fn dynamic_runtime_state_export_json_runtime(
9290 app_root: &Path,
9291 runtime_conf_path: Option<&Path>,
9292 app_id: Option<&str>,
9293) -> Option<String> {
9294 dynamic_relational_runtime_export_json(app_root, runtime_conf_path, app_id)
9295}
9296
9297/// Imports relational runtime state through the configured native adapter.
9298///
9299/// # Parameters
9300///
9301/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_import_json_runtime`].
9302/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_import_json_runtime`].
9303/// - `app_id`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_import_json_runtime`].
9304/// - `state_path`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_import_json_runtime`].
9305/// - `expected_audit_count`: Input consumed by [`crate::runtime::json_commands::dynamic_runtime_state_import_json_runtime`].
9306///
9307/// # Returns
9308///
9309/// Returns the computed value produced by [`crate::runtime::json_commands::dynamic_runtime_state_import_json_runtime`].
9310///
9311/// # Examples
9312///
9313/// ```rust,ignore
9314/// let _result = pharos_native::runtime::json_commands::dynamic_runtime_state_import_json_runtime(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
9315/// ```
9316///
9317/// # Related
9318///
9319/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9320pub fn dynamic_runtime_state_import_json_runtime(
9321 app_root: &Path,
9322 runtime_conf_path: Option<&Path>,
9323 app_id: Option<&str>,
9324 state_path: &Path,
9325 expected_audit_count: Option<&str>,
9326) -> i32 {
9327 dynamic_relational_runtime_import_state(
9328 app_root,
9329 runtime_conf_path,
9330 app_id,
9331 state_path,
9332 expected_audit_count,
9333 )
9334}
9335
9336/// Dispatches a named JSON command against a persisted state file.
9337///
9338/// # Parameters
9339///
9340/// - `state_path`: Input consumed by [`crate::runtime::json_commands::dispatch_json_command_entries`].
9341/// - `command`: Input consumed by [`crate::runtime::json_commands::dispatch_json_command_entries`].
9342/// - `options_json`: Input consumed by [`crate::runtime::json_commands::dispatch_json_command_entries`].
9343///
9344/// # Returns
9345///
9346/// Returns the successful value produced by [`crate::runtime::json_commands::dispatch_json_command_entries`] or an error when the operation fails.
9347///
9348/// # Errors
9349///
9350/// Returns an error value when [`crate::runtime::json_commands::dispatch_json_command_entries`] cannot complete successfully.
9351///
9352/// # Examples
9353///
9354/// ```rust,ignore
9355/// let _result = pharos_native::runtime::json_commands::dispatch_json_command_entries(todo!(), todo!(), todo!());
9356/// ```
9357///
9358/// # Related
9359///
9360/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9361pub fn dispatch_json_command_entries(
9362 state_path: &Path,
9363 command: &str,
9364 options_json: &str,
9365) -> Result<String, i32> {
9366 state_named_command(state_path, command, options_json)
9367}
9368
9369/// Dispatches the native JSON command surface without a shell bridge.
9370///
9371/// # Parameters
9372///
9373/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_json_command`].
9374/// - `command`: Input consumed by [`crate::runtime::json_commands::handle_json_command`].
9375/// - `options_json`: Input consumed by [`crate::runtime::json_commands::handle_json_command`].
9376///
9377/// # Returns
9378///
9379/// Returns the successful value produced by [`crate::runtime::json_commands::handle_json_command`] or an error when the operation fails.
9380///
9381/// # Errors
9382///
9383/// Returns an error value when [`crate::runtime::json_commands::handle_json_command`] cannot complete successfully.
9384///
9385/// # Examples
9386///
9387/// ```rust,ignore
9388/// let _result = pharos_native::runtime::json_commands::handle_json_command(todo!(), todo!(), todo!());
9389/// ```
9390///
9391/// # Related
9392///
9393/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9394pub fn handle_json_command(
9395 state_path: &Path,
9396 command: &str,
9397 options_json: &str,
9398) -> Result<String, i32> {
9399 dispatch_json_command_entries(state_path, command, options_json)
9400}
9401
9402/// Returns generated-surface list-column metadata.
9403///
9404/// # Parameters
9405///
9406/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_list_columns_runtime`].
9407///
9408/// # Returns
9409///
9410/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_list_columns_runtime`] can produce a value and [`None`] otherwise.
9411///
9412/// # Examples
9413///
9414/// ```rust,ignore
9415/// let _result = pharos_native::runtime::json_commands::generated_surface_list_columns_runtime("{}");
9416/// ```
9417///
9418/// # Related
9419///
9420/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9421pub fn generated_surface_list_columns_runtime(surface_json: &str) -> Option<String> {
9422 generated_surface_list_columns(surface_json)
9423}
9424
9425/// Handles state named command.
9426///
9427/// # Parameters
9428///
9429/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_state_named_command`].
9430/// - `name`: Input consumed by [`crate::runtime::json_commands::handle_state_named_command`].
9431/// - `options_json`: Input consumed by [`crate::runtime::json_commands::handle_state_named_command`].
9432///
9433/// # Returns
9434///
9435/// Returns the successful value produced by [`crate::runtime::json_commands::handle_state_named_command`] or an error when the operation fails.
9436///
9437/// # Errors
9438///
9439/// Returns an error value when [`crate::runtime::json_commands::handle_state_named_command`] cannot complete successfully.
9440///
9441/// # Examples
9442///
9443/// ```rust,ignore
9444/// let _result = pharos_native::runtime::json_commands::handle_state_named_command(todo!(), "name", todo!());
9445/// ```
9446///
9447/// # Related
9448///
9449/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9450pub fn handle_state_named_command(
9451 state_path: &Path,
9452 name: &str,
9453 options_json: &str,
9454) -> Result<String, i32> {
9455 state_named_command(state_path, name, options_json)
9456}
9457
9458/// Handles file generated surface match command.
9459///
9460/// # Parameters
9461///
9462/// - `file_path`: Input consumed by [`crate::runtime::json_commands::handle_file_generated_surface_match_command`].
9463/// - `method`: Input consumed by [`crate::runtime::json_commands::handle_file_generated_surface_match_command`].
9464/// - `request_path`: Input consumed by [`crate::runtime::json_commands::handle_file_generated_surface_match_command`].
9465///
9466/// # Returns
9467///
9468/// Returns [`Some`] when [`crate::runtime::json_commands::handle_file_generated_surface_match_command`] can produce a value and [`None`] otherwise.
9469///
9470/// # Examples
9471///
9472/// ```rust,ignore
9473/// let _result = pharos_native::runtime::json_commands::handle_file_generated_surface_match_command(todo!(), todo!(), "/");
9474/// ```
9475///
9476/// # Related
9477///
9478/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9479pub fn handle_file_generated_surface_match_command(
9480 file_path: &Path,
9481 method: &str,
9482 request_path: &str,
9483) -> Option<String> {
9484 file_generated_surface_match(file_path, method, request_path)
9485}
9486
9487/// Handles generated surface list columns JSON command.
9488///
9489/// # Parameters
9490///
9491/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_list_columns_json_command`].
9492///
9493/// # Returns
9494///
9495/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_list_columns_json_command`] can produce a value and [`None`] otherwise.
9496///
9497/// # Examples
9498///
9499/// ```rust,ignore
9500/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_list_columns_json_command("{}");
9501/// ```
9502///
9503/// # Related
9504///
9505/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9506pub fn handle_generated_surface_list_columns_json_command(surface_json: &str) -> Option<String> {
9507 generated_surface_list_columns(surface_json)
9508}
9509
9510/// Returns generated-surface request parameters.
9511///
9512/// # Parameters
9513///
9514/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_params_runtime`].
9515/// - `context_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_params_runtime`].
9516/// - `identity_value`: Input consumed by [`crate::runtime::json_commands::generated_surface_params_runtime`].
9517///
9518/// # Returns
9519///
9520/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_params_runtime`] can produce a value and [`None`] otherwise.
9521///
9522/// # Examples
9523///
9524/// ```rust,ignore
9525/// let _result = pharos_native::runtime::json_commands::generated_surface_params_runtime("{}", todo!(), todo!());
9526/// ```
9527///
9528/// # Related
9529///
9530/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9531pub fn generated_surface_params_runtime(
9532 surface_json: &str,
9533 context_json: &str,
9534 identity_value: Option<&str>,
9535) -> Option<String> {
9536 generated_surface_params(surface_json, context_json, identity_value)
9537}
9538
9539/// Handles generated surface params JSON command.
9540///
9541/// # Parameters
9542///
9543/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_params_json_command`].
9544/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_params_json_command`].
9545/// - `identity_value`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_params_json_command`].
9546///
9547/// # Returns
9548///
9549/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_params_json_command`] can produce a value and [`None`] otherwise.
9550///
9551/// # Examples
9552///
9553/// ```rust,ignore
9554/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_params_json_command("{}", todo!(), todo!());
9555/// ```
9556///
9557/// # Related
9558///
9559/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9560pub fn handle_generated_surface_params_json_command(
9561 surface_json: &str,
9562 context_json: &str,
9563 identity_value: Option<&str>,
9564) -> Option<String> {
9565 generated_surface_params(surface_json, context_json, identity_value)
9566}
9567
9568/// Returns generated-surface display text for a field.
9569///
9570/// # Parameters
9571///
9572/// - `item_json`: Input consumed by [`crate::runtime::json_commands::generated_surface_display_value_runtime`].
9573/// - `field_name`: Input consumed by [`crate::runtime::json_commands::generated_surface_display_value_runtime`].
9574///
9575/// # Returns
9576///
9577/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_display_value_runtime`] can produce a value and [`None`] otherwise.
9578///
9579/// # Examples
9580///
9581/// ```rust,ignore
9582/// let _result = pharos_native::runtime::json_commands::generated_surface_display_value_runtime("{}", todo!());
9583/// ```
9584///
9585/// # Related
9586///
9587/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9588pub fn generated_surface_display_value_runtime(
9589 item_json: &str,
9590 field_name: &str,
9591) -> Option<String> {
9592 generated_surface_display_value(item_json, field_name)
9593}
9594
9595/// Handles generated surface display value command.
9596///
9597/// # Parameters
9598///
9599/// - `item_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_display_value_command`].
9600/// - `field_name`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_display_value_command`].
9601///
9602/// # Returns
9603///
9604/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_display_value_command`] can produce a value and [`None`] otherwise.
9605///
9606/// # Examples
9607///
9608/// ```rust,ignore
9609/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_display_value_command("{}", todo!());
9610/// ```
9611///
9612/// # Related
9613///
9614/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9615pub fn handle_generated_surface_display_value_command(
9616 item_json: &str,
9617 field_name: &str,
9618) -> Option<String> {
9619 generated_surface_display_value(item_json, field_name)
9620}
9621
9622/// Handles generated surface select options HTML command.
9623///
9624/// # Parameters
9625///
9626/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_select_options_html_command`].
9627/// - `field_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_select_options_html_command`].
9628/// - `selected_value`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_select_options_html_command`].
9629///
9630/// # Returns
9631///
9632/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_select_options_html_command`] can produce a value and [`None`] otherwise.
9633///
9634/// # Examples
9635///
9636/// ```rust,ignore
9637/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_select_options_html_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
9638/// ```
9639///
9640/// # Related
9641///
9642/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9643pub fn handle_generated_surface_select_options_html_command(
9644 app_root: &Path,
9645 field_json: &str,
9646 selected_value: Option<&str>,
9647) -> Option<String> {
9648 generated_surface_select_options_html(app_root, field_json, selected_value)
9649}
9650
9651/// Handles generated surface relation checkboxes HTML command.
9652///
9653/// # Parameters
9654///
9655/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_relation_checkboxes_html_command`].
9656/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_relation_checkboxes_html_command`].
9657/// - `relation_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_relation_checkboxes_html_command`].
9658/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_relation_checkboxes_html_command`].
9659/// - `selected_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_relation_checkboxes_html_command`].
9660///
9661/// # Returns
9662///
9663/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_relation_checkboxes_html_command`] can produce a value and [`None`] otherwise.
9664///
9665/// # Examples
9666///
9667/// ```rust,ignore
9668/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_relation_checkboxes_html_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
9669/// ```
9670///
9671/// # Related
9672///
9673/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9674pub fn handle_generated_surface_relation_checkboxes_html_command(
9675 app_root: &Path,
9676 state_path: &Path,
9677 relation_json: &str,
9678 context_json: &str,
9679 selected_json: Option<&str>,
9680) -> Option<String> {
9681 generated_surface_relation_checkboxes_html(
9682 app_root,
9683 state_path,
9684 relation_json,
9685 context_json,
9686 selected_json,
9687 )
9688}
9689
9690/// Handles generated surface manage table JSON command.
9691///
9692/// # Parameters
9693///
9694/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_table_json_command`].
9695/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_table_json_command`].
9696/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_table_json_command`].
9697/// - `list_result_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_table_json_command`].
9698///
9699/// # Returns
9700///
9701/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_manage_table_json_command`] can produce a value and [`None`] otherwise.
9702///
9703/// # Examples
9704///
9705/// ```rust,ignore
9706/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_manage_table_json_command(std::path::Path::new("apps/dev_docs"), "{}", todo!(), todo!());
9707/// ```
9708///
9709/// # Related
9710///
9711/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9712pub fn handle_generated_surface_manage_table_json_command(
9713 app_root: &Path,
9714 surface_json: &str,
9715 context_json: &str,
9716 list_result_json: &str,
9717) -> Option<String> {
9718 generated_surface_manage_table_json(app_root, surface_json, context_json, list_result_json)
9719}
9720
9721/// Handles generated surface form fields HTML command.
9722///
9723/// # Parameters
9724///
9725/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_form_fields_html_command`].
9726/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_form_fields_html_command`].
9727/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_form_fields_html_command`].
9728/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_form_fields_html_command`].
9729/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_form_fields_html_command`].
9730/// - `item_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_form_fields_html_command`].
9731///
9732/// # Returns
9733///
9734/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_form_fields_html_command`] can produce a value and [`None`] otherwise.
9735///
9736/// # Examples
9737///
9738/// ```rust,ignore
9739/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_form_fields_html_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), "{}", todo!(), "{}");
9740/// ```
9741///
9742/// # Related
9743///
9744/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9745pub fn handle_generated_surface_form_fields_html_command(
9746 app_root: &Path,
9747 state_path: &Path,
9748 fixture_path: &Path,
9749 surface_json: &str,
9750 context_json: &str,
9751 item_json: Option<&str>,
9752) -> Option<String> {
9753 generated_surface_form_fields_html(
9754 app_root,
9755 state_path,
9756 fixture_path,
9757 surface_json,
9758 context_json,
9759 item_json,
9760 )
9761}
9762
9763/// Handles generated surface filter controls HTML command.
9764///
9765/// # Parameters
9766///
9767/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_filter_controls_html_command`].
9768/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_filter_controls_html_command`].
9769/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_filter_controls_html_command`].
9770///
9771/// # Returns
9772///
9773/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_filter_controls_html_command`] can produce a value and [`None`] otherwise.
9774///
9775/// # Examples
9776///
9777/// ```rust,ignore
9778/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_filter_controls_html_command(std::path::Path::new("apps/dev_docs"), "{}", todo!());
9779/// ```
9780///
9781/// # Related
9782///
9783/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9784pub fn handle_generated_surface_filter_controls_html_command(
9785 app_root: &Path,
9786 surface_json: &str,
9787 raw_query: &str,
9788) -> Option<String> {
9789 generated_surface_filter_controls_html(app_root, surface_json, raw_query)
9790}
9791
9792/// Handles generated surface sort options HTML command.
9793///
9794/// # Parameters
9795///
9796/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_sort_options_html_command`].
9797/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_sort_options_html_command`].
9798/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_sort_options_html_command`].
9799///
9800/// # Returns
9801///
9802/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_sort_options_html_command`] can produce a value and [`None`] otherwise.
9803///
9804/// # Examples
9805///
9806/// ```rust,ignore
9807/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_sort_options_html_command(std::path::Path::new("apps/dev_docs"), "{}", todo!());
9808/// ```
9809///
9810/// # Related
9811///
9812/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9813pub fn handle_generated_surface_sort_options_html_command(
9814 app_root: &Path,
9815 surface_json: &str,
9816 raw_query: &str,
9817) -> Option<String> {
9818 generated_surface_sort_options_html(app_root, surface_json, raw_query)
9819}
9820
9821/// Handles generated surface context command.
9822///
9823/// # Parameters
9824///
9825/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_context_command`].
9826/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_context_command`].
9827/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_context_command`].
9828///
9829/// # Returns
9830///
9831/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_context_command`] can produce a value and [`None`] otherwise.
9832///
9833/// # Examples
9834///
9835/// ```rust,ignore
9836/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_context_command(todo!(), "{}", todo!());
9837/// ```
9838///
9839/// # Related
9840///
9841/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9842pub fn handle_generated_surface_context_command(
9843 state_path: &Path,
9844 surface_json: &str,
9845 params_json: &str,
9846) -> Option<String> {
9847 generated_surface_context_json(state_path, surface_json, params_json)
9848}
9849
9850/// Handles generated surface submitted command.
9851///
9852/// # Parameters
9853///
9854/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_submitted_command`].
9855/// - `request_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_submitted_command`].
9856///
9857/// # Returns
9858///
9859/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_submitted_command`] can produce a value and [`None`] otherwise.
9860///
9861/// # Performance
9862///
9863/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::handle_generated_surface_submitted_command`].
9864///
9865/// # Examples
9866///
9867/// ```rust,ignore
9868/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_submitted_command("{}", todo!());
9869/// ```
9870///
9871/// # Related
9872///
9873/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9874pub fn handle_generated_surface_submitted_command(
9875 surface_json: &str,
9876 request_json: &str,
9877) -> Option<String> {
9878 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
9879 let payload = serde_json::from_str::<Value>(request_json).ok()?;
9880 let form_root = serde_json::json!({
9881 "forms": [{
9882 "form_id": surface.get("form_id").and_then(Value::as_str).unwrap_or_default(),
9883 "fields": surface
9884 .get("fields")
9885 .and_then(Value::as_array)
9886 .cloned()
9887 .unwrap_or_default()
9888 }]
9889 });
9890 generated::generated_surface_submitted_json(&surface, &form_root, &payload)
9891 .or_else(|| compact(&payload))
9892}
9893
9894/// Handles generated surface list result command.
9895///
9896/// # Parameters
9897///
9898/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_list_result_command`].
9899/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_list_result_command`].
9900/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_list_result_command`].
9901/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_list_result_command`].
9902///
9903/// # Returns
9904///
9905/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_list_result_command`] can produce a value and [`None`] otherwise.
9906///
9907/// # Examples
9908///
9909/// ```rust,ignore
9910/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_list_result_command(todo!(), "{}", todo!(), todo!());
9911/// ```
9912///
9913/// # Related
9914///
9915/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9916pub fn handle_generated_surface_list_result_command(
9917 state_path: &Path,
9918 surface_json: &str,
9919 context_json: &str,
9920 raw_query: &str,
9921) -> Option<String> {
9922 generated_surface_list_result_json(state_path, surface_json, context_json, raw_query)
9923}
9924
9925/// Handles generated surface default candidate command.
9926///
9927/// # Parameters
9928///
9929/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_default_candidate_command`].
9930/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_default_candidate_command`].
9931/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_default_candidate_command`].
9932/// - `existing_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_default_candidate_command`].
9933/// - `submitted_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_default_candidate_command`].
9934///
9935/// # Returns
9936///
9937/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_default_candidate_command`] can produce a value and [`None`] otherwise.
9938///
9939/// # Examples
9940///
9941/// ```rust,ignore
9942/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_default_candidate_command(todo!(), "{}", todo!(), todo!(), todo!());
9943/// ```
9944///
9945/// # Related
9946///
9947/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9948pub fn handle_generated_surface_default_candidate_command(
9949 state_path: &Path,
9950 surface_json: &str,
9951 context_json: &str,
9952 existing_json: Option<&str>,
9953 submitted_json: Option<&str>,
9954) -> Option<String> {
9955 generated_surface_default_candidate_json(
9956 state_path,
9957 surface_json,
9958 context_json,
9959 existing_json,
9960 submitted_json,
9961 )
9962}
9963
9964/// Handles generated surface validation report command.
9965///
9966/// # Parameters
9967///
9968/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_validation_report_command`].
9969/// - `action_id`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_validation_report_command`].
9970/// - `actor_scope`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_validation_report_command`].
9971/// - `tenant_scope`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_validation_report_command`].
9972///
9973/// # Returns
9974///
9975/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_validation_report_command`] can produce a value and [`None`] otherwise.
9976///
9977/// # Performance
9978///
9979/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::handle_generated_surface_validation_report_command`].
9980///
9981/// # Examples
9982///
9983/// ```rust,ignore
9984/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_validation_report_command("{}", todo!(), todo!(), todo!());
9985/// ```
9986///
9987/// # Related
9988///
9989/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
9990pub fn handle_generated_surface_validation_report_command(
9991 surface_json: &str,
9992 action_id: Option<&str>,
9993 actor_scope: Option<&str>,
9994 tenant_scope: Option<&str>,
9995) -> Option<String> {
9996 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
9997 generated::generated_surface_validation_report_empty(
9998 surface.get("form_id").and_then(Value::as_str),
9999 action_id,
10000 actor_scope,
10001 tenant_scope,
10002 )
10003}
10004
10005/// Handles generated surface mutate command.
10006///
10007/// # Parameters
10008///
10009/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_mutate_command`].
10010/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_mutate_command`].
10011/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_mutate_command`].
10012/// - `candidate_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_mutate_command`].
10013///
10014/// # Returns
10015///
10016/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_mutate_command`] can produce a value and [`None`] otherwise.
10017///
10018/// # Examples
10019///
10020/// ```rust,ignore
10021/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_mutate_command(todo!(), "{}", todo!(), todo!());
10022/// ```
10023///
10024/// # Related
10025///
10026/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10027pub fn handle_generated_surface_mutate_command(
10028 state_path: &Path,
10029 surface_json: &str,
10030 context_json: &str,
10031 candidate_json: &str,
10032) -> Option<String> {
10033 generated_surface_default_candidate_json(
10034 state_path,
10035 surface_json,
10036 context_json,
10037 None,
10038 Some(candidate_json),
10039 )
10040}
10041
10042/// Validates a generated-surface candidate through the existing typed helper.
10043///
10044/// # Parameters
10045///
10046/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_coerce_candidate_command`].
10047/// - `candidate_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_coerce_candidate_command`].
10048///
10049/// # Returns
10050///
10051/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_coerce_candidate_command`] can produce a value and [`None`] otherwise.
10052///
10053/// # Performance
10054///
10055/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::handle_generated_surface_coerce_candidate_command`].
10056///
10057/// # Examples
10058///
10059/// ```rust,ignore
10060/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_coerce_candidate_command("{}", todo!());
10061/// ```
10062///
10063/// # Related
10064///
10065/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10066pub fn handle_generated_surface_coerce_candidate_command(
10067 surface_json: &str,
10068 candidate_json: &str,
10069) -> Option<String> {
10070 let surface_root = serde_json::from_str::<Value>(surface_json).ok()?;
10071 generated::generated_surface_coerce_candidate_json(&surface_root, candidate_json)
10072}
10073
10074/// Builds the generated-surface edit page JSON.
10075///
10076/// # Parameters
10077///
10078/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_edit_page_json_command`].
10079/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_edit_page_json_command`].
10080/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_edit_page_json_command`].
10081/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_edit_page_json_command`].
10082/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_edit_page_json_command`].
10083/// - `item_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_edit_page_json_command`].
10084///
10085/// # Returns
10086///
10087/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_edit_page_json_command`] can produce a value and [`None`] otherwise.
10088///
10089/// # Examples
10090///
10091/// ```rust,ignore
10092/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_edit_page_json_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), "{}", todo!(), "{}");
10093/// ```
10094///
10095/// # Related
10096///
10097/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10098pub fn handle_generated_surface_edit_page_json_command(
10099 app_root: &Path,
10100 state_path: &Path,
10101 fixture_path: &Path,
10102 surface_json: &str,
10103 context_json: &str,
10104 item_json: &str,
10105) -> Option<String> {
10106 generated_surface_edit_page_json(
10107 app_root,
10108 state_path,
10109 fixture_path,
10110 surface_json,
10111 context_json,
10112 item_json,
10113 )
10114}
10115
10116/// Builds the generated-surface management page JSON.
10117///
10118/// # Parameters
10119///
10120/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_page_json_command`].
10121/// - `state_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_page_json_command`].
10122/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_page_json_command`].
10123/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_page_json_command`].
10124/// - `context_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_page_json_command`].
10125/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_manage_page_json_command`].
10126///
10127/// # Returns
10128///
10129/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_manage_page_json_command`] can produce a value and [`None`] otherwise.
10130///
10131/// # Examples
10132///
10133/// ```rust,ignore
10134/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_manage_page_json_command(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), "{}", todo!(), todo!());
10135/// ```
10136///
10137/// # Related
10138///
10139/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10140pub fn handle_generated_surface_manage_page_json_command(
10141 app_root: &Path,
10142 state_path: &Path,
10143 fixture_path: &Path,
10144 surface_json: &str,
10145 context_json: &str,
10146 raw_query: &str,
10147) -> Option<String> {
10148 generated_surface_manage_page_json(
10149 app_root,
10150 state_path,
10151 fixture_path,
10152 surface_json,
10153 context_json,
10154 raw_query,
10155 )
10156}
10157
10158/// Performs object from text.
10159fn object_from_text(text: &str) -> Option<Value> {
10160 let value = serde_json::from_str::<Value>(text).ok()?;
10161 value.is_object().then_some(value)
10162}
10163
10164/// Performs bounded state array.
10165fn bounded_state_array<'a>(root: &'a Value, section: &str) -> &'a [Value] {
10166 root.get(section)
10167 .and_then(Value::as_array)
10168 .map(|items| &items[..items.len().min(MAX_STATE_ROWS_JSON)])
10169 .unwrap_or(&[])
10170}
10171
10172/// Appends an audit event to a mutable generated-surface state document.
10173///
10174/// # Parameters
10175///
10176/// - `root`: Input consumed by [`crate::runtime::json_commands::generated_surface_append_audit_event_mut`].
10177/// - `event`: Input consumed by [`crate::runtime::json_commands::generated_surface_append_audit_event_mut`].
10178///
10179/// # Returns
10180///
10181/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_append_audit_event_mut`] holds and `false` otherwise.
10182///
10183/// # Examples
10184///
10185/// ```rust,ignore
10186/// let _result = pharos_native::runtime::json_commands::generated_surface_append_audit_event_mut(std::path::Path::new("."), todo!());
10187/// ```
10188///
10189/// # Related
10190///
10191/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10192pub fn generated_surface_append_audit_event_mut(root: &mut Value, event: Value) -> bool {
10193 let Some(object) = root.as_object_mut() else {
10194 return false;
10195 };
10196 if !event.is_object() {
10197 return false;
10198 }
10199 let events = object
10200 .entry("audit_events".to_owned())
10201 .or_insert_with(|| Value::Array(Vec::new()));
10202 let Some(items) = events.as_array_mut() else {
10203 return false;
10204 };
10205 if items.len() >= MAX_STATE_ROWS_JSON {
10206 return false;
10207 }
10208 items.push(event);
10209 true
10210}
10211
10212/// Tests whether an array contains an exact string value.
10213///
10214/// # Parameters
10215///
10216/// - `values`: Input consumed by [`crate::runtime::json_commands::generated_surface_array_contains_string`].
10217/// - `expected`: Input consumed by [`crate::runtime::json_commands::generated_surface_array_contains_string`].
10218///
10219/// # Returns
10220///
10221/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_array_contains_string`] holds and `false` otherwise.
10222///
10223/// # Performance
10224///
10225/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_array_contains_string`].
10226///
10227/// # Examples
10228///
10229/// ```rust,ignore
10230/// let _result = pharos_native::runtime::json_commands::generated_surface_array_contains_string(todo!(), "value");
10231/// ```
10232///
10233/// # Related
10234///
10235/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10236pub fn generated_surface_array_contains_string(values: &Value, expected: &str) -> bool {
10237 !expected.is_empty()
10238 && values
10239 .as_array()
10240 .is_some_and(|items| items.iter().any(|item| item.as_str() == Some(expected)))
10241}
10242
10243/// Normalizes generated-surface order values to stable positive integers.
10244///
10245/// # Parameters
10246///
10247/// - `values`: Input consumed by [`crate::runtime::json_commands::generated_surface_normalize_order_mut`].
10248/// - `field`: Input consumed by [`crate::runtime::json_commands::generated_surface_normalize_order_mut`].
10249///
10250/// # Returns
10251///
10252/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_normalize_order_mut`] holds and `false` otherwise.
10253///
10254/// # Performance
10255///
10256/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_normalize_order_mut`].
10257///
10258/// # Examples
10259///
10260/// ```rust,ignore
10261/// let _result = pharos_native::runtime::json_commands::generated_surface_normalize_order_mut(todo!(), "field");
10262/// ```
10263///
10264/// # Related
10265///
10266/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10267pub fn generated_surface_normalize_order_mut(values: &mut Value, field: &str) -> bool {
10268 if field.is_empty() {
10269 return false;
10270 }
10271 let Some(items) = values.as_array_mut() else {
10272 return false;
10273 };
10274 for (index, item) in items.iter_mut().enumerate() {
10275 let Some(object) = item.as_object_mut() else {
10276 return false;
10277 };
10278 let value = object
10279 .get(field)
10280 .and_then(Value::as_i64)
10281 .filter(|value| *value > 0)
10282 .unwrap_or(index as i64 + 1);
10283 object.insert(field.to_owned(), Value::from(value));
10284 }
10285 true
10286}
10287
10288/// Validates that every relation value is an integer identifier.
10289///
10290/// # Parameters
10291///
10292/// - `values`: Input consumed by [`crate::runtime::json_commands::generated_surface_relation_array_all_integers`].
10293///
10294/// # Returns
10295///
10296/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_relation_array_all_integers`] holds and `false` otherwise.
10297///
10298/// # Performance
10299///
10300/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_relation_array_all_integers`].
10301///
10302/// # Examples
10303///
10304/// ```rust,ignore
10305/// let _result = pharos_native::runtime::json_commands::generated_surface_relation_array_all_integers(todo!());
10306/// ```
10307///
10308/// # Related
10309///
10310/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10311pub fn generated_surface_relation_array_all_integers(values: &Value) -> bool {
10312 values.as_array().is_some_and(|items| {
10313 items
10314 .iter()
10315 .all(|item| item.as_i64().is_some() || item.as_u64().is_some())
10316 })
10317}
10318
10319/// Adds one field error to a mutable validation report.
10320///
10321/// # Parameters
10322///
10323/// - `report`: Input consumed by [`crate::runtime::json_commands::generated_surface_report_add_field_error`].
10324/// - `field`: Input consumed by [`crate::runtime::json_commands::generated_surface_report_add_field_error`].
10325/// - `message`: Input consumed by [`crate::runtime::json_commands::generated_surface_report_add_field_error`].
10326///
10327/// # Returns
10328///
10329/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_report_add_field_error`] holds and `false` otherwise.
10330///
10331/// # Examples
10332///
10333/// ```rust,ignore
10334/// let _result = pharos_native::runtime::json_commands::generated_surface_report_add_field_error(todo!(), "field", "example");
10335/// ```
10336///
10337/// # Related
10338///
10339/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10340pub fn generated_surface_report_add_field_error(
10341 report: &mut Value,
10342 field: &str,
10343 message: &str,
10344) -> bool {
10345 if field.is_empty() || message.is_empty() {
10346 return false;
10347 }
10348 let Some(object) = report.as_object_mut() else {
10349 return false;
10350 };
10351 let errors = object
10352 .entry("field_errors".to_owned())
10353 .or_insert_with(|| Value::Object(Map::new()));
10354 let Some(error_map) = errors.as_object_mut() else {
10355 return false;
10356 };
10357 error_map.insert(field.to_owned(), Value::String(message.to_owned()));
10358 true
10359}
10360
10361/// Performs valid date text.
10362fn valid_date_text(text: &str) -> bool {
10363 let parts: Vec<_> = text.split('-').collect();
10364 parts.len() == 3
10365 && parts[0].len() == 4
10366 && parts[1].len() == 2
10367 && parts[2].len() == 2
10368 && parts
10369 .iter()
10370 .all(|part| part.bytes().all(|byte| byte.is_ascii_digit()))
10371 && (1..=12).contains(&parts[1].parse::<u8>().unwrap_or(0))
10372 && (1..=31).contains(&parts[2].parse::<u8>().unwrap_or(0))
10373}
10374
10375/// Validates the date text accepted by generated-surface filters.
10376///
10377/// # Parameters
10378///
10379/// - `text`: Input consumed by [`crate::runtime::json_commands::generated_surface_text_is_date`].
10380///
10381/// # Returns
10382///
10383/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_text_is_date`] holds and `false` otherwise.
10384///
10385/// # Examples
10386///
10387/// ```rust,ignore
10388/// let _result = pharos_native::runtime::json_commands::generated_surface_text_is_date("{}");
10389/// ```
10390///
10391/// # Related
10392///
10393/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10394pub fn generated_surface_text_is_date(text: &str) -> bool {
10395 valid_date_text(text)
10396}
10397
10398/// Validates a bounded decimal amount without accepting exponent syntax.
10399///
10400/// # Parameters
10401///
10402/// - `text`: Input consumed by [`crate::runtime::json_commands::generated_surface_text_is_decimal`].
10403///
10404/// # Returns
10405///
10406/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_text_is_decimal`] holds and `false` otherwise.
10407///
10408/// # Performance
10409///
10410/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_text_is_decimal`].
10411///
10412/// # Examples
10413///
10414/// ```rust,ignore
10415/// let _result = pharos_native::runtime::json_commands::generated_surface_text_is_decimal("{}");
10416/// ```
10417///
10418/// # Related
10419///
10420/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10421pub fn generated_surface_text_is_decimal(text: &str) -> bool {
10422 if text.is_empty() || text.len() > 64 {
10423 return false;
10424 }
10425 let mut separator_seen = false;
10426 let digits = text.strip_prefix('-').unwrap_or(text);
10427 !digits.is_empty()
10428 && digits.bytes().all(|byte| match byte {
10429 b'0'..=b'9' => true,
10430 b'.' if !separator_seen => {
10431 separator_seen = true;
10432 true
10433 }
10434 _ => false,
10435 })
10436}
10437
10438/// Validates a basic user-facing email value.
10439///
10440/// # Parameters
10441///
10442/// - `text`: Input consumed by [`crate::runtime::json_commands::generated_surface_text_is_email`].
10443///
10444/// # Returns
10445///
10446/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_text_is_email`] holds and `false` otherwise.
10447///
10448/// # Performance
10449///
10450/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_text_is_email`].
10451///
10452/// # Examples
10453///
10454/// ```rust,ignore
10455/// let _result = pharos_native::runtime::json_commands::generated_surface_text_is_email("{}");
10456/// ```
10457///
10458/// # Related
10459///
10460/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10461pub fn generated_surface_text_is_email(text: &str) -> bool {
10462 let Some((local, domain)) = text.split_once('@') else {
10463 return false;
10464 };
10465 !local.is_empty()
10466 && local.len() <= 128
10467 && !local.contains(char::is_whitespace)
10468 && domain.len() <= 253
10469 && domain.contains('.')
10470 && domain.split('.').all(|part| {
10471 !part.is_empty()
10472 && !part.starts_with('-')
10473 && !part.ends_with('-')
10474 && part
10475 .bytes()
10476 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
10477 })
10478}
10479
10480/// Validates an HH:MM or HH:MM:SS time value.
10481///
10482/// # Parameters
10483///
10484/// - `text`: Input consumed by [`crate::runtime::json_commands::generated_surface_text_is_time`].
10485///
10486/// # Returns
10487///
10488/// Returns `true` when the condition checked by [`crate::runtime::json_commands::generated_surface_text_is_time`] holds and `false` otherwise.
10489///
10490/// # Performance
10491///
10492/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::generated_surface_text_is_time`].
10493///
10494/// # Examples
10495///
10496/// ```rust,ignore
10497/// let _result = pharos_native::runtime::json_commands::generated_surface_text_is_time("{}");
10498/// ```
10499///
10500/// # Related
10501///
10502/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10503pub fn generated_surface_text_is_time(text: &str) -> bool {
10504 let parts: Vec<_> = text.split(':').collect();
10505 (parts.len() == 2 || parts.len() == 3)
10506 && parts
10507 .iter()
10508 .all(|part| part.len() == 2 && part.bytes().all(|byte| byte.is_ascii_digit()))
10509 && parts[0].parse::<u8>().is_ok_and(|value| value < 24)
10510 && parts[1].parse::<u8>().is_ok_and(|value| value < 60)
10511 && parts
10512 .get(2)
10513 .is_none_or(|part| part.parse::<u8>().is_ok_and(|value| value < 60))
10514}
10515
10516/// Returns the greatest positive id in one state section.
10517///
10518/// # Parameters
10519///
10520/// - `root`: Input consumed by [`crate::runtime::json_commands::max_id_in_state_section`].
10521/// - `section`: Input consumed by [`crate::runtime::json_commands::max_id_in_state_section`].
10522///
10523/// # Returns
10524///
10525/// Returns the computed value produced by [`crate::runtime::json_commands::max_id_in_state_section`].
10526///
10527/// # Performance
10528///
10529/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::max_id_in_state_section`].
10530///
10531/// # Examples
10532///
10533/// ```rust,ignore
10534/// let _result = pharos_native::runtime::json_commands::max_id_in_state_section(std::path::Path::new("."), "items");
10535/// ```
10536///
10537/// # Related
10538///
10539/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10540pub fn max_id_in_state_section(root: &Value, section: &str) -> i64 {
10541 bounded_state_array(root, section)
10542 .iter()
10543 .filter_map(|row| row.get("id").and_then(Value::as_i64))
10544 .filter(|id| *id > 0)
10545 .max()
10546 .unwrap_or(0)
10547}
10548
10549/// Merges an object patch into a mutable object document.
10550///
10551/// # Parameters
10552///
10553/// - `target`: Input consumed by [`crate::runtime::json_commands::merge_patch_into_mut_object`].
10554/// - `patch`: Input consumed by [`crate::runtime::json_commands::merge_patch_into_mut_object`].
10555///
10556/// # Returns
10557///
10558/// Returns `true` when the condition checked by [`crate::runtime::json_commands::merge_patch_into_mut_object`] holds and `false` otherwise.
10559///
10560/// # Performance
10561///
10562/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::merge_patch_into_mut_object`].
10563///
10564/// # Examples
10565///
10566/// ```rust,ignore
10567/// let _result = pharos_native::runtime::json_commands::merge_patch_into_mut_object("/", todo!());
10568/// ```
10569///
10570/// # Related
10571///
10572/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10573pub fn merge_patch_into_mut_object(target: &mut Value, patch: &Value) -> bool {
10574 let (Some(target), Some(patch)) = (target.as_object_mut(), patch.as_object()) else {
10575 return false;
10576 };
10577 for (key, value) in patch {
10578 if value.is_null() {
10579 target.remove(key);
10580 } else {
10581 target.insert(key.clone(), value.clone());
10582 }
10583 }
10584 true
10585}
10586
10587/// Returns a mutable normalized payload object, rejecting non-object input.
10588///
10589/// # Parameters
10590///
10591/// - `value`: Input consumed by [`crate::runtime::json_commands::normalized_payload_object_mut`].
10592///
10593/// # Returns
10594///
10595/// Returns [`Some`] when [`crate::runtime::json_commands::normalized_payload_object_mut`] can produce a value and [`None`] otherwise.
10596///
10597/// # Examples
10598///
10599/// ```rust,ignore
10600/// let _result = pharos_native::runtime::json_commands::normalized_payload_object_mut("value");
10601/// ```
10602///
10603/// # Related
10604///
10605/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10606pub fn normalized_payload_object_mut(value: &mut Value) -> Option<&mut Map<String, Value>> {
10607 value.as_object_mut()
10608}
10609
10610/// Returns the JSON text for a user's booking selected by user id.
10611///
10612/// # Parameters
10613///
10614/// - `root`: Input consumed by [`crate::runtime::json_commands::state_find_booking_for_user`].
10615/// - `user_id`: Input consumed by [`crate::runtime::json_commands::state_find_booking_for_user`].
10616///
10617/// # Returns
10618///
10619/// Returns [`Some`] when [`crate::runtime::json_commands::state_find_booking_for_user`] can produce a value and [`None`] otherwise.
10620///
10621/// # Performance
10622///
10623/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::state_find_booking_for_user`].
10624///
10625/// # Examples
10626///
10627/// ```rust,ignore
10628/// let _result = pharos_native::runtime::json_commands::state_find_booking_for_user(std::path::Path::new("."), 1);
10629/// ```
10630///
10631/// # Related
10632///
10633/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10634pub fn state_find_booking_for_user(root: &Value, user_id: i64) -> Option<String> {
10635 bounded_state_array(root, "bookings")
10636 .iter()
10637 .find(|row| row.get("user_id").and_then(Value::as_i64) == Some(user_id))
10638 .and_then(compact)
10639}
10640
10641/// Returns the JSON text for a record selected by a string field.
10642///
10643/// # Parameters
10644///
10645/// - `root`: Input consumed by [`crate::runtime::json_commands::surface_find_record_by_string_field`].
10646/// - `section`: Input consumed by [`crate::runtime::json_commands::surface_find_record_by_string_field`].
10647/// - `field`: Input consumed by [`crate::runtime::json_commands::surface_find_record_by_string_field`].
10648/// - `expected`: Input consumed by [`crate::runtime::json_commands::surface_find_record_by_string_field`].
10649///
10650/// # Returns
10651///
10652/// Returns [`Some`] when [`crate::runtime::json_commands::surface_find_record_by_string_field`] can produce a value and [`None`] otherwise.
10653///
10654/// # Performance
10655///
10656/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::surface_find_record_by_string_field`].
10657///
10658/// # Examples
10659///
10660/// ```rust,ignore
10661/// let _result = pharos_native::runtime::json_commands::surface_find_record_by_string_field(std::path::Path::new("."), "items", "field", "value");
10662/// ```
10663///
10664/// # Related
10665///
10666/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10667pub fn surface_find_record_by_string_field(
10668 root: &Value,
10669 section: &str,
10670 field: &str,
10671 expected: &str,
10672) -> Option<String> {
10673 bounded_state_array(root, section)
10674 .iter()
10675 .find(|row| row.get(field).and_then(Value::as_str) == Some(expected))
10676 .and_then(compact)
10677}
10678
10679/// Emits active tenant identifiers for one user as newline-delimited text.
10680///
10681/// # Parameters
10682///
10683/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_active_tenant_ids_lines`].
10684/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_active_tenant_ids_lines`].
10685///
10686/// # Returns
10687///
10688/// Returns the computed value produced by [`crate::runtime::json_commands::emit_active_tenant_ids_lines`].
10689///
10690/// # Performance
10691///
10692/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_active_tenant_ids_lines`].
10693///
10694/// # Examples
10695///
10696/// ```rust,ignore
10697/// let _result = pharos_native::runtime::json_commands::emit_active_tenant_ids_lines(std::path::Path::new("."), 1);
10698/// ```
10699///
10700/// # Related
10701///
10702/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10703pub fn emit_active_tenant_ids_lines(root: &Value, user_id: i64) -> String {
10704 bounded_state_array(root, "tenant_memberships")
10705 .iter()
10706 .filter(|row| row.get("user_id").and_then(Value::as_i64) == Some(user_id))
10707 .filter(|row| {
10708 row.get("membership_status")
10709 .and_then(Value::as_str)
10710 .is_none_or(|status| status.is_empty() || status == "active")
10711 })
10712 .filter_map(|row| row.get("tenant_id").and_then(Value::as_i64))
10713 .take(MAX_OUTPUT_ROWS_JSON)
10714 .map(|id| id.to_string())
10715 .collect::<Vec<_>>()
10716 .join("\n")
10717}
10718
10719/// Performs rows as JSON.
10720fn rows_as_json(root: &Value, section: &str, predicate: impl Fn(&Value) -> bool) -> Option<String> {
10721 let rows: Vec<Value> = bounded_state_array(root, section)
10722 .iter()
10723 .filter(|row| predicate(row))
10724 .take(MAX_OUTPUT_ROWS_JSON)
10725 .cloned()
10726 .collect();
10727 compact(&Value::Array(rows))
10728}
10729
10730/// Emits appointments visible to one user.
10731///
10732/// # Parameters
10733///
10734/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_user_appointments`].
10735/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_user_appointments`].
10736///
10737/// # Returns
10738///
10739/// Returns [`Some`] when [`crate::runtime::json_commands::emit_user_appointments`] can produce a value and [`None`] otherwise.
10740///
10741/// # Examples
10742///
10743/// ```rust,ignore
10744/// let _result = pharos_native::runtime::json_commands::emit_user_appointments(std::path::Path::new("."), 1);
10745/// ```
10746///
10747/// # Related
10748///
10749/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10750pub fn emit_user_appointments(root: &Value, user_id: i64) -> Option<String> {
10751 rows_as_json(root, "appointments", |row| {
10752 row.get("user_id").and_then(Value::as_i64) == Some(user_id)
10753 })
10754}
10755
10756/// Emits appointments visible to a managed user, preserving state order.
10757///
10758/// # Parameters
10759///
10760/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_managed_appointments`].
10761/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_managed_appointments`].
10762///
10763/// # Returns
10764///
10765/// Returns [`Some`] when [`crate::runtime::json_commands::emit_managed_appointments`] can produce a value and [`None`] otherwise.
10766///
10767/// # Performance
10768///
10769/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_managed_appointments`].
10770///
10771/// # Examples
10772///
10773/// ```rust,ignore
10774/// let _result = pharos_native::runtime::json_commands::emit_managed_appointments(std::path::Path::new("."), 1);
10775/// ```
10776///
10777/// # Related
10778///
10779/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10780pub fn emit_managed_appointments(root: &Value, user_id: i64) -> Option<String> {
10781 let business_ids: Vec<i64> = bounded_state_array(root, "businesses")
10782 .iter()
10783 .filter(|row| row.get("owner_id").and_then(Value::as_i64) == Some(user_id))
10784 .filter_map(|row| row.get("id").and_then(Value::as_i64))
10785 .collect();
10786 rows_as_json(root, "appointments", |row| {
10787 row.get("business_id")
10788 .and_then(Value::as_i64)
10789 .is_some_and(|id| business_ids.contains(&id))
10790 })
10791}
10792
10793/// Emits the tenant ids associated with one user's active memberships as JSON.
10794///
10795/// # Parameters
10796///
10797/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_user_membership_tenant_ids`].
10798/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_user_membership_tenant_ids`].
10799///
10800/// # Returns
10801///
10802/// Returns [`Some`] when [`crate::runtime::json_commands::emit_user_membership_tenant_ids`] can produce a value and [`None`] otherwise.
10803///
10804/// # Performance
10805///
10806/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_user_membership_tenant_ids`].
10807///
10808/// # Examples
10809///
10810/// ```rust,ignore
10811/// let _result = pharos_native::runtime::json_commands::emit_user_membership_tenant_ids(std::path::Path::new("."), 1);
10812/// ```
10813///
10814/// # Related
10815///
10816/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10817pub fn emit_user_membership_tenant_ids(root: &Value, user_id: i64) -> Option<String> {
10818 let ids: Vec<Value> = bounded_state_array(root, "tenant_memberships")
10819 .iter()
10820 .filter(|row| row.get("user_id").and_then(Value::as_i64) == Some(user_id))
10821 .filter(|row| {
10822 row.get("membership_status")
10823 .and_then(Value::as_str)
10824 .is_none_or(|status| status.is_empty() || status == "active")
10825 })
10826 .filter_map(|row| row.get("tenant_id").cloned())
10827 .take(MAX_OUTPUT_ROWS_JSON)
10828 .collect();
10829 compact(&Value::Array(ids))
10830}
10831
10832/// Emits public business cards from state.
10833///
10834/// # Parameters
10835///
10836/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_public_business_cards`].
10837///
10838/// # Returns
10839///
10840/// Returns [`Some`] when [`crate::runtime::json_commands::emit_public_business_cards`] can produce a value and [`None`] otherwise.
10841///
10842/// # Performance
10843///
10844/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_public_business_cards`].
10845///
10846/// # Examples
10847///
10848/// ```rust,ignore
10849/// let _result = pharos_native::runtime::json_commands::emit_public_business_cards(std::path::Path::new("."));
10850/// ```
10851///
10852/// # Related
10853///
10854/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10855pub fn emit_public_business_cards(root: &Value) -> Option<String> {
10856 let cards: Vec<Value> = bounded_state_array(root, "businesses")
10857 .iter()
10858 .map(|row| serde_json::json!({"id": row.get("id").cloned().unwrap_or(Value::Null), "slug": row.get("slug").cloned().unwrap_or(Value::Null), "name": row.get("name").cloned().unwrap_or(Value::Null), "description": row.get("description").cloned().unwrap_or(Value::Null), "image_url": row.get("image_url").cloned().unwrap_or(Value::Null)}))
10859 .take(MAX_OUTPUT_ROWS_JSON)
10860 .collect();
10861 compact(&Value::Array(cards))
10862}
10863
10864/// Emits business cards managed by one user.
10865///
10866/// # Parameters
10867///
10868/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_manageable_business_cards`].
10869/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_manageable_business_cards`].
10870///
10871/// # Returns
10872///
10873/// Returns [`Some`] when [`crate::runtime::json_commands::emit_manageable_business_cards`] can produce a value and [`None`] otherwise.
10874///
10875/// # Performance
10876///
10877/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_manageable_business_cards`].
10878///
10879/// # Examples
10880///
10881/// ```rust,ignore
10882/// let _result = pharos_native::runtime::json_commands::emit_manageable_business_cards(std::path::Path::new("."), 1);
10883/// ```
10884///
10885/// # Related
10886///
10887/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10888pub fn emit_manageable_business_cards(root: &Value, user_id: i64) -> Option<String> {
10889 let cards: Vec<Value> = bounded_state_array(root, "businesses")
10890 .iter()
10891 .filter(|row| {
10892 row.get("owner_id").and_then(Value::as_i64) == Some(user_id)
10893 || row.get("owner_user_id").and_then(Value::as_i64) == Some(user_id)
10894 })
10895 .take(MAX_OUTPUT_ROWS_JSON)
10896 .cloned()
10897 .collect();
10898 compact(&Value::Array(cards))
10899}
10900
10901/// Writes a bounded mutable JSON document through the native filesystem surface.
10902///
10903/// # Parameters
10904///
10905/// - `path`: Input consumed by [`crate::runtime::json_commands::write_mutable_json_doc`].
10906/// - `value`: Input consumed by [`crate::runtime::json_commands::write_mutable_json_doc`].
10907///
10908/// # Returns
10909///
10910/// Returns the computed value produced by [`crate::runtime::json_commands::write_mutable_json_doc`].
10911///
10912/// # Examples
10913///
10914/// ```rust,ignore
10915/// let _result = pharos_native::runtime::json_commands::write_mutable_json_doc(std::path::Path::new("fixture.json"), "value");
10916/// ```
10917///
10918/// # Related
10919///
10920/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10921pub fn write_mutable_json_doc(path: &Path, value: &Value) -> i32 {
10922 let Some(text) = compact(value) else {
10923 return 75;
10924 };
10925 super::fs::write_text_file(path, &text)
10926}
10927
10928/// Compacts a mutable JSON document read from disk.
10929///
10930/// # Parameters
10931///
10932/// - `path`: Input consumed by [`crate::runtime::json_commands::emit_compact_mutable_json_doc`].
10933///
10934/// # Returns
10935///
10936/// Returns [`Some`] when [`crate::runtime::json_commands::emit_compact_mutable_json_doc`] can produce a value and [`None`] otherwise.
10937///
10938/// # Examples
10939///
10940/// ```rust,ignore
10941/// let _result = pharos_native::runtime::json_commands::emit_compact_mutable_json_doc(std::path::Path::new("fixture.json"));
10942/// ```
10943///
10944/// # Related
10945///
10946/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10947pub fn emit_compact_mutable_json_doc(path: &Path) -> Option<String> {
10948 compact(&read_json_file(path)?)
10949}
10950
10951/// Captures a stable checksum string using the configured native checksum tool.
10952///
10953/// # Parameters
10954///
10955/// - `value`: Input consumed by [`crate::runtime::json_commands::capture_checksum_output`].
10956///
10957/// # Returns
10958///
10959/// Returns [`Some`] when [`crate::runtime::json_commands::capture_checksum_output`] can produce a value and [`None`] otherwise.
10960///
10961/// # Examples
10962///
10963/// ```rust,ignore
10964/// let _result = pharos_native::runtime::json_commands::capture_checksum_output("value");
10965/// ```
10966///
10967/// # Related
10968///
10969/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10970pub fn capture_checksum_output(value: &str) -> Option<String> {
10971 checksum_string(value)
10972}
10973
10974/// Returns a JSON value with its template text rendered from bounded parameters.
10975///
10976/// # Parameters
10977///
10978/// - `template_path`: Input consumed by [`crate::runtime::json_commands::emit_template`].
10979/// - `params_json`: Input consumed by [`crate::runtime::json_commands::emit_template`].
10980///
10981/// # Returns
10982///
10983/// Returns [`Some`] when [`crate::runtime::json_commands::emit_template`] can produce a value and [`None`] otherwise.
10984///
10985/// # Examples
10986///
10987/// ```rust,ignore
10988/// let _result = pharos_native::runtime::json_commands::emit_template("template.html", todo!());
10989/// ```
10990///
10991/// # Related
10992///
10993/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
10994pub fn emit_template(template_path: &Path, params_json: &str) -> Option<String> {
10995 let params = object_from_text(params_json)?;
10996 render_file_template(template_path, ¶ms)
10997}
10998
10999/// Emits an escaped text line without writing to a process-global stream.
11000///
11001/// # Parameters
11002///
11003/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_sorted_section_lines`].
11004/// - `section`: Input consumed by [`crate::runtime::json_commands::emit_sorted_section_lines`].
11005/// - `field`: Input consumed by [`crate::runtime::json_commands::emit_sorted_section_lines`].
11006///
11007/// # Returns
11008///
11009/// Returns [`Some`] when [`crate::runtime::json_commands::emit_sorted_section_lines`] can produce a value and [`None`] otherwise.
11010///
11011/// # Performance
11012///
11013/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_sorted_section_lines`].
11014///
11015/// # Examples
11016///
11017/// ```rust,ignore
11018/// let _result = pharos_native::runtime::json_commands::emit_sorted_section_lines(std::path::Path::new("."), "items", "field");
11019/// ```
11020///
11021/// # Related
11022///
11023/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11024pub fn emit_sorted_section_lines(root: &Value, section: &str, field: &str) -> Option<String> {
11025 let mut values: Vec<String> = bounded_state_array(root, section)
11026 .iter()
11027 .filter_map(|row| row.get(field).map(value_to_text))
11028 .filter(|value| !value.is_empty())
11029 .collect();
11030 values.sort();
11031 values.dedup();
11032 Some(values.join("\n"))
11033}
11034
11035/// Emits template page parameters as compact JSON.
11036///
11037/// # Parameters
11038///
11039/// - `params`: Input consumed by [`crate::runtime::json_commands::emit_template_page_params`].
11040///
11041/// # Returns
11042///
11043/// Returns [`Some`] when [`crate::runtime::json_commands::emit_template_page_params`] can produce a value and [`None`] otherwise.
11044///
11045/// # Examples
11046///
11047/// ```rust,ignore
11048/// let _result = pharos_native::runtime::json_commands::emit_template_page_params(todo!());
11049/// ```
11050///
11051/// # Related
11052///
11053/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11054pub fn emit_template_page_params(params: &Value) -> Option<String> {
11055 compact(params)
11056}
11057
11058/// Appends a text line to a bounded output buffer.
11059///
11060/// # Parameters
11061///
11062/// - `output`: Input consumed by [`crate::runtime::json_commands::emit_text_line_to_buffer`].
11063/// - `text`: Input consumed by [`crate::runtime::json_commands::emit_text_line_to_buffer`].
11064///
11065/// # Returns
11066///
11067/// Returns `true` when the condition checked by [`crate::runtime::json_commands::emit_text_line_to_buffer`] holds and `false` otherwise.
11068///
11069/// # Examples
11070///
11071/// ```rust,ignore
11072/// let _result = pharos_native::runtime::json_commands::emit_text_line_to_buffer(todo!(), "{}");
11073/// ```
11074///
11075/// # Related
11076///
11077/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11078pub fn emit_text_line_to_buffer(output: &mut String, text: &str) -> bool {
11079 let Some(line) = emit_text_line(text) else {
11080 return false;
11081 };
11082 if output.len().saturating_add(line.len()) > MAX_TEXT_BYTES {
11083 return false;
11084 }
11085 output.push_str(&line);
11086 true
11087}
11088
11089/// Returns a mutable state row selected by a positive integer field.
11090///
11091/// # Parameters
11092///
11093/// - `root`: Input consumed by [`crate::runtime::json_commands::find_mut_array_item_by_long_field`].
11094/// - `section`: Input consumed by [`crate::runtime::json_commands::find_mut_array_item_by_long_field`].
11095/// - `field`: Input consumed by [`crate::runtime::json_commands::find_mut_array_item_by_long_field`].
11096/// - `wanted`: Input consumed by [`crate::runtime::json_commands::find_mut_array_item_by_long_field`].
11097///
11098/// # Returns
11099///
11100/// Returns [`Some`] when [`crate::runtime::json_commands::find_mut_array_item_by_long_field`] can produce a value and [`None`] otherwise.
11101///
11102/// # Performance
11103///
11104/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_mut_array_item_by_long_field`].
11105///
11106/// # Examples
11107///
11108/// ```rust,ignore
11109/// let _result = pharos_native::runtime::json_commands::find_mut_array_item_by_long_field(std::path::Path::new("."), "items", "field", todo!());
11110/// ```
11111///
11112/// # Related
11113///
11114/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11115pub fn find_mut_array_item_by_long_field<'a>(
11116 root: &'a mut Value,
11117 section: &str,
11118 field: &str,
11119 wanted: i64,
11120) -> Option<&'a mut Value> {
11121 if section.is_empty() || field.is_empty() || wanted <= 0 {
11122 return None;
11123 }
11124 root.get_mut(section)?
11125 .as_array_mut()?
11126 .iter_mut()
11127 .find(|item| item.get(field).and_then(Value::as_i64) == Some(wanted))
11128}
11129
11130/// Finds an active token record for one user and token value.
11131///
11132/// # Parameters
11133///
11134/// - `root`: Input consumed by [`crate::runtime::json_commands::find_active_token_record`].
11135/// - `user_id`: Input consumed by [`crate::runtime::json_commands::find_active_token_record`].
11136/// - `token`: Input consumed by [`crate::runtime::json_commands::find_active_token_record`].
11137///
11138/// # Returns
11139///
11140/// Returns [`Some`] when [`crate::runtime::json_commands::find_active_token_record`] can produce a value and [`None`] otherwise.
11141///
11142/// # Performance
11143///
11144/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_active_token_record`].
11145///
11146/// # Examples
11147///
11148/// ```rust,ignore
11149/// let _result = pharos_native::runtime::json_commands::find_active_token_record(std::path::Path::new("."), 1, todo!());
11150/// ```
11151///
11152/// # Related
11153///
11154/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11155pub fn find_active_token_record(root: &Value, user_id: i64, token: &str) -> Option<String> {
11156 if user_id <= 0 || token.is_empty() {
11157 return None;
11158 }
11159 bounded_state_array(root, "tokens")
11160 .iter()
11161 .find(|row| {
11162 row.get("user_id").and_then(Value::as_i64) == Some(user_id)
11163 && row.get("token").and_then(Value::as_str) == Some(token)
11164 && row.get("revoked").and_then(Value::as_bool) != Some(true)
11165 })
11166 .and_then(compact)
11167}
11168
11169/// Normalizes one persisted runtime state file and returns its compact text.
11170///
11171/// # Parameters
11172///
11173/// - `path`: Input consumed by [`crate::runtime::json_commands::handle_file_normalize_runtime_state_command`].
11174///
11175/// # Returns
11176///
11177/// Returns the successful value produced by [`crate::runtime::json_commands::handle_file_normalize_runtime_state_command`] or an error when the operation fails.
11178///
11179/// # Errors
11180///
11181/// Returns an error value when [`crate::runtime::json_commands::handle_file_normalize_runtime_state_command`] cannot complete successfully.
11182///
11183/// # Examples
11184///
11185/// ```rust,ignore
11186/// let _result = pharos_native::runtime::json_commands::handle_file_normalize_runtime_state_command(std::path::Path::new("fixture.json"));
11187/// ```
11188///
11189/// # Related
11190///
11191/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11192pub fn handle_file_normalize_runtime_state_command(path: &Path) -> Result<String, i32> {
11193 let root = read_json_file(path).ok_or(66)?;
11194 let text = compact(&root).ok_or(75)?;
11195 if super::fs::write_text_file(path, &text) != 0 {
11196 return Err(73);
11197 }
11198 Ok(text)
11199}
11200
11201/// Matches a route template and stores its path parameters in an object.
11202///
11203/// # Parameters
11204///
11205/// - `output`: Input consumed by [`crate::runtime::json_commands::route_template_match_into_mut_obj`].
11206/// - `template_path`: Input consumed by [`crate::runtime::json_commands::route_template_match_into_mut_obj`].
11207/// - `request_path`: Input consumed by [`crate::runtime::json_commands::route_template_match_into_mut_obj`].
11208///
11209/// # Returns
11210///
11211/// Returns `true` when the condition checked by [`crate::runtime::json_commands::route_template_match_into_mut_obj`] holds and `false` otherwise.
11212///
11213/// # Examples
11214///
11215/// ```rust,ignore
11216/// let _result = pharos_native::runtime::json_commands::route_template_match_into_mut_obj(todo!(), "template.html", "/");
11217/// ```
11218///
11219/// # Related
11220///
11221/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11222pub fn route_template_match_into_mut_obj(
11223 output: &mut Map<String, Value>,
11224 template_path: &str,
11225 request_path: &str,
11226) -> bool {
11227 let Some(params) = route_template_params(template_path, request_path) else {
11228 return false;
11229 };
11230 output.extend(params);
11231 true
11232}
11233
11234/// Handles the state event-row JSON command from an already-loaded row.
11235///
11236/// # Parameters
11237///
11238/// - `row`: Input consumed by [`crate::runtime::json_commands::handle_state_event_stream_row_json_lines_command`].
11239///
11240/// # Returns
11241///
11242/// Returns [`Some`] when [`crate::runtime::json_commands::handle_state_event_stream_row_json_lines_command`] can produce a value and [`None`] otherwise.
11243///
11244/// # Examples
11245///
11246/// ```rust,ignore
11247/// let _result = pharos_native::runtime::json_commands::handle_state_event_stream_row_json_lines_command(todo!());
11248/// ```
11249///
11250/// # Related
11251///
11252/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11253pub fn handle_state_event_stream_row_json_lines_command(row: &Value) -> Option<String> {
11254 compact(row)
11255}
11256
11257/// Handles importing one bounded JSON row into a mutable state document.
11258///
11259/// # Parameters
11260///
11261/// - `root`: Input consumed by [`crate::runtime::json_commands::handle_state_import_row_json_lines_command`].
11262/// - `row_text`: Input consumed by [`crate::runtime::json_commands::handle_state_import_row_json_lines_command`].
11263///
11264/// # Returns
11265///
11266/// Returns the successful value produced by [`crate::runtime::json_commands::handle_state_import_row_json_lines_command`] or an error when the operation fails.
11267///
11268/// # Errors
11269///
11270/// Returns an error value when [`crate::runtime::json_commands::handle_state_import_row_json_lines_command`] cannot complete successfully.
11271///
11272/// # Performance
11273///
11274/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::handle_state_import_row_json_lines_command`].
11275///
11276/// # Examples
11277///
11278/// ```rust,ignore
11279/// let _result = pharos_native::runtime::json_commands::handle_state_import_row_json_lines_command(std::path::Path::new("."), todo!());
11280/// ```
11281///
11282/// # Related
11283///
11284/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11285pub fn handle_state_import_row_json_lines_command(
11286 root: &mut Value,
11287 row_text: &str,
11288) -> Result<String, i32> {
11289 let row = serde_json::from_str::<Value>(row_text).map_err(|_| 65)?;
11290 if !row.is_object() || !generated_surface_append_audit_event_mut(root, row.clone()) {
11291 return Err(65);
11292 }
11293 compact(&row).ok_or(75)
11294}
11295
11296/// Applies a template base path to URL-bearing attributes.
11297///
11298/// # Parameters
11299///
11300/// - `html`: Input consumed by [`crate::runtime::json_commands::handle_template_apply_base_path_command`].
11301/// - `base_path`: Input consumed by [`crate::runtime::json_commands::handle_template_apply_base_path_command`].
11302///
11303/// # Returns
11304///
11305/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_apply_base_path_command`] can produce a value and [`None`] otherwise.
11306///
11307/// # Examples
11308///
11309/// ```rust,ignore
11310/// let _result = pharos_native::runtime::json_commands::handle_template_apply_base_path_command(todo!(), "/app");
11311/// ```
11312///
11313/// # Related
11314///
11315/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11316pub fn handle_template_apply_base_path_command(
11317 html: &str,
11318 base_path: Option<&str>,
11319) -> Option<String> {
11320 template_apply_base_path(html, base_path)
11321}
11322
11323/// Resolves a declared fragment path for a command caller.
11324///
11325/// # Parameters
11326///
11327/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_template_declared_fragment_path_command`].
11328/// - `fragment_id`: Input consumed by [`crate::runtime::json_commands::handle_template_declared_fragment_path_command`].
11329///
11330/// # Returns
11331///
11332/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_declared_fragment_path_command`] can produce a value and [`None`] otherwise.
11333///
11334/// # Examples
11335///
11336/// ```rust,ignore
11337/// let _result = pharos_native::runtime::json_commands::handle_template_declared_fragment_path_command(std::path::Path::new("apps/dev_docs"), "header");
11338/// ```
11339///
11340/// # Related
11341///
11342/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11343pub fn handle_template_declared_fragment_path_command(
11344 app_root: &Path,
11345 fragment_id: &str,
11346) -> Option<String> {
11347 template_declared_fragment_path(app_root, fragment_id)
11348}
11349
11350/// Resolves a declared page contract for a command caller.
11351///
11352/// # Parameters
11353///
11354/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_template_declared_page_contract_command`].
11355/// - `page_id`: Input consumed by [`crate::runtime::json_commands::handle_template_declared_page_contract_command`].
11356///
11357/// # Returns
11358///
11359/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_declared_page_contract_command`] can produce a value and [`None`] otherwise.
11360///
11361/// # Examples
11362///
11363/// ```rust,ignore
11364/// let _result = pharos_native::runtime::json_commands::handle_template_declared_page_contract_command(std::path::Path::new("apps/dev_docs"), "home");
11365/// ```
11366///
11367/// # Related
11368///
11369/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11370pub fn handle_template_declared_page_contract_command(
11371 app_root: &Path,
11372 page_id: &str,
11373) -> Option<String> {
11374 template_declared_page_contract(app_root, page_id)
11375}
11376
11377/// Renders one named page JSON contract from a parameter document.
11378///
11379/// # Parameters
11380///
11381/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_template_layout_page_json_command`].
11382/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_layout_page_json_command`].
11383///
11384/// # Returns
11385///
11386/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_layout_page_json_command`] can produce a value and [`None`] otherwise.
11387///
11388/// # Examples
11389///
11390/// ```rust,ignore
11391/// let _result = pharos_native::runtime::json_commands::handle_template_layout_page_json_command(std::path::Path::new("apps/dev_docs"), todo!());
11392/// ```
11393///
11394/// # Related
11395///
11396/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11397pub fn handle_template_layout_page_json_command(
11398 app_root: &Path,
11399 params_json: &str,
11400) -> Option<String> {
11401 let params = object_from_text(params_json)?;
11402 template_layout_page_json(
11403 app_root,
11404 params.get("layout_id").and_then(Value::as_str),
11405 params.get("current_lang").and_then(Value::as_str),
11406 params
11407 .get("title")
11408 .and_then(Value::as_str)
11409 .unwrap_or_default(),
11410 params
11411 .get("body")
11412 .and_then(Value::as_str)
11413 .unwrap_or_default(),
11414 params.get("current_request_target").and_then(Value::as_str),
11415 params
11416 .get("has_user")
11417 .and_then(Value::as_bool)
11418 .unwrap_or(false),
11419 params
11420 .get("managed_business_count")
11421 .and_then(Value::as_i64)
11422 .unwrap_or(0),
11423 )
11424}
11425
11426/// Renders one named layout page as HTML from a parameter document.
11427///
11428/// # Parameters
11429///
11430/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_template_layout_page_html_command`].
11431/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_layout_page_html_command`].
11432///
11433/// # Returns
11434///
11435/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_layout_page_html_command`] can produce a value and [`None`] otherwise.
11436///
11437/// # Examples
11438///
11439/// ```rust,ignore
11440/// let _result = pharos_native::runtime::json_commands::handle_template_layout_page_html_command(std::path::Path::new("apps/dev_docs"), todo!());
11441/// ```
11442///
11443/// # Related
11444///
11445/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11446pub fn handle_template_layout_page_html_command(
11447 app_root: &Path,
11448 params_json: &str,
11449) -> Option<String> {
11450 let params = object_from_text(params_json)?;
11451 template_layout_page_html(
11452 app_root,
11453 params.get("base_path").and_then(Value::as_str),
11454 params.get("layout_id").and_then(Value::as_str),
11455 params.get("current_lang").and_then(Value::as_str),
11456 params
11457 .get("title")
11458 .and_then(Value::as_str)
11459 .unwrap_or_default(),
11460 params
11461 .get("body")
11462 .and_then(Value::as_str)
11463 .unwrap_or_default(),
11464 params.get("current_request_target").and_then(Value::as_str),
11465 params
11466 .get("has_user")
11467 .and_then(Value::as_bool)
11468 .unwrap_or(false),
11469 params
11470 .get("managed_business_count")
11471 .and_then(Value::as_i64)
11472 .unwrap_or(0),
11473 )
11474}
11475
11476/// Performs template command params.
11477fn template_command_params(params_json: &str) -> Option<Value> {
11478 let params = serde_json::from_str::<Value>(params_json).ok()?;
11479 params.is_object().then_some(params)
11480}
11481
11482/// Builds the login page parameter JSON for a command caller.
11483///
11484/// # Parameters
11485///
11486/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_login_page_json_command`].
11487///
11488/// # Returns
11489///
11490/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_login_page_json_command`] can produce a value and [`None`] otherwise.
11491///
11492/// # Examples
11493///
11494/// ```rust,ignore
11495/// let _result = pharos_native::runtime::json_commands::handle_template_login_page_json_command(todo!());
11496/// ```
11497///
11498/// # Related
11499///
11500/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11501pub fn handle_template_login_page_json_command(params_json: &str) -> Option<String> {
11502 let params = template_command_params(params_json)?;
11503 compact(¶ms)
11504}
11505
11506/// Builds the registration page parameter JSON for a command caller.
11507///
11508/// # Parameters
11509///
11510/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_register_page_json_command`].
11511///
11512/// # Returns
11513///
11514/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_register_page_json_command`] can produce a value and [`None`] otherwise.
11515///
11516/// # Examples
11517///
11518/// ```rust,ignore
11519/// let _result = pharos_native::runtime::json_commands::handle_template_register_page_json_command(todo!());
11520/// ```
11521///
11522/// # Related
11523///
11524/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11525pub fn handle_template_register_page_json_command(params_json: &str) -> Option<String> {
11526 let params = template_command_params(params_json)?;
11527 compact(¶ms)
11528}
11529
11530/// Builds the password-reset request page parameter JSON.
11531///
11532/// # Parameters
11533///
11534/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_password_reset_request_page_json_command`].
11535///
11536/// # Returns
11537///
11538/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_password_reset_request_page_json_command`] can produce a value and [`None`] otherwise.
11539///
11540/// # Examples
11541///
11542/// ```rust,ignore
11543/// let _result = pharos_native::runtime::json_commands::handle_template_password_reset_request_page_json_command(todo!());
11544/// ```
11545///
11546/// # Related
11547///
11548/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11549pub fn handle_template_password_reset_request_page_json_command(
11550 params_json: &str,
11551) -> Option<String> {
11552 let params = template_command_params(params_json)?;
11553 compact(¶ms)
11554}
11555
11556/// Builds the password-reset sent page parameter JSON.
11557///
11558/// # Parameters
11559///
11560/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_password_reset_sent_page_json_command`].
11561///
11562/// # Returns
11563///
11564/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_password_reset_sent_page_json_command`] can produce a value and [`None`] otherwise.
11565///
11566/// # Examples
11567///
11568/// ```rust,ignore
11569/// let _result = pharos_native::runtime::json_commands::handle_template_password_reset_sent_page_json_command(todo!());
11570/// ```
11571///
11572/// # Related
11573///
11574/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11575pub fn handle_template_password_reset_sent_page_json_command(params_json: &str) -> Option<String> {
11576 let params = template_command_params(params_json)?;
11577 compact(¶ms)
11578}
11579
11580/// Builds the password-reset complete page parameter JSON.
11581///
11582/// # Parameters
11583///
11584/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_password_reset_complete_page_json_command`].
11585///
11586/// # Returns
11587///
11588/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_password_reset_complete_page_json_command`] can produce a value and [`None`] otherwise.
11589///
11590/// # Examples
11591///
11592/// ```rust,ignore
11593/// let _result = pharos_native::runtime::json_commands::handle_template_password_reset_complete_page_json_command(todo!());
11594/// ```
11595///
11596/// # Related
11597///
11598/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11599pub fn handle_template_password_reset_complete_page_json_command(
11600 params_json: &str,
11601) -> Option<String> {
11602 let params = template_command_params(params_json)?;
11603 compact(¶ms)
11604}
11605
11606/// Builds the invite-created page parameter JSON.
11607///
11608/// # Parameters
11609///
11610/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_invite_created_page_json_command`].
11611///
11612/// # Returns
11613///
11614/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_invite_created_page_json_command`] can produce a value and [`None`] otherwise.
11615///
11616/// # Examples
11617///
11618/// ```rust,ignore
11619/// let _result = pharos_native::runtime::json_commands::handle_template_invite_created_page_json_command(todo!());
11620/// ```
11621///
11622/// # Related
11623///
11624/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11625pub fn handle_template_invite_created_page_json_command(params_json: &str) -> Option<String> {
11626 let params = template_command_params(params_json)?;
11627 compact(¶ms)
11628}
11629
11630/// Builds the invite-accept page parameter JSON.
11631///
11632/// # Parameters
11633///
11634/// - `params_json`: Input consumed by [`crate::runtime::json_commands::handle_template_invite_accept_page_json_command`].
11635///
11636/// # Returns
11637///
11638/// Returns [`Some`] when [`crate::runtime::json_commands::handle_template_invite_accept_page_json_command`] can produce a value and [`None`] otherwise.
11639///
11640/// # Examples
11641///
11642/// ```rust,ignore
11643/// let _result = pharos_native::runtime::json_commands::handle_template_invite_accept_page_json_command(todo!());
11644/// ```
11645///
11646/// # Related
11647///
11648/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11649pub fn handle_template_invite_accept_page_json_command(params_json: &str) -> Option<String> {
11650 let params = template_command_params(params_json)?;
11651 compact(¶ms)
11652}
11653
11654/// Renders a shared empty fragment through the declared application template.
11655///
11656/// # Parameters
11657///
11658/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_app_empty_fragment`].
11659/// - `message`: Input consumed by [`crate::runtime::json_commands::render_app_empty_fragment`].
11660///
11661/// # Returns
11662///
11663/// Returns [`Some`] when [`crate::runtime::json_commands::render_app_empty_fragment`] can produce a value and [`None`] otherwise.
11664///
11665/// # Examples
11666///
11667/// ```rust,ignore
11668/// let _result = pharos_native::runtime::json_commands::render_app_empty_fragment(std::path::Path::new("apps/dev_docs"), "example");
11669/// ```
11670///
11671/// # Related
11672///
11673/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11674pub fn render_app_empty_fragment(app_root: &Path, message: &str) -> Option<String> {
11675 let params = Map::from_iter([("message".to_owned(), Value::String(message.to_owned()))]);
11676 generated::render_generated_admin_fragment_from_value_runtime(
11677 app_root,
11678 "templates/shared/fragments/empty.html",
11679 ¶ms,
11680 )
11681}
11682
11683/// Returns the declared empty dashboard copy without embedding a page shell.
11684///
11685/// # Parameters
11686///
11687/// - `message`: Input consumed by [`crate::runtime::json_commands::render_business_dashboard_empty_html`].
11688///
11689/// # Returns
11690///
11691/// Returns the computed value produced by [`crate::runtime::json_commands::render_business_dashboard_empty_html`].
11692///
11693/// # Examples
11694///
11695/// ```rust,ignore
11696/// let _result = pharos_native::runtime::json_commands::render_business_dashboard_empty_html("example");
11697/// ```
11698///
11699/// # Related
11700///
11701/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11702pub fn render_business_dashboard_empty_html(message: &str) -> String {
11703 html_escape_text(message)
11704}
11705
11706/// Builds a generated-surface filter control fragment from contract metadata.
11707///
11708/// # Parameters
11709///
11710/// - `app_root`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_filter_controls_html`].
11711/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_filter_controls_html`].
11712/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_filter_controls_html`].
11713///
11714/// # Returns
11715///
11716/// Returns [`Some`] when [`crate::runtime::json_commands::build_generated_surface_filter_controls_html`] can produce a value and [`None`] otherwise.
11717///
11718/// # Examples
11719///
11720/// ```rust,ignore
11721/// let _result = pharos_native::runtime::json_commands::build_generated_surface_filter_controls_html(std::path::Path::new("apps/dev_docs"), "{}", todo!());
11722/// ```
11723///
11724/// # Related
11725///
11726/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11727pub fn build_generated_surface_filter_controls_html(
11728 app_root: &Path,
11729 surface_json: &str,
11730 raw_query: &str,
11731) -> Option<String> {
11732 generated_surface_filter_controls_html(app_root, surface_json, raw_query)
11733}
11734
11735/// Builds generated-surface form fields from contract metadata.
11736///
11737/// # Parameters
11738///
11739/// - `app_root`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_form_fields_html`].
11740/// - `state_path`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_form_fields_html`].
11741/// - `fixture_path`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_form_fields_html`].
11742/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_form_fields_html`].
11743/// - `context_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_form_fields_html`].
11744/// - `item_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_form_fields_html`].
11745///
11746/// # Returns
11747///
11748/// Returns [`Some`] when [`crate::runtime::json_commands::build_generated_surface_form_fields_html`] can produce a value and [`None`] otherwise.
11749///
11750/// # Examples
11751///
11752/// ```rust,ignore
11753/// let _result = pharos_native::runtime::json_commands::build_generated_surface_form_fields_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), "{}", todo!(), "{}");
11754/// ```
11755///
11756/// # Related
11757///
11758/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11759pub fn build_generated_surface_form_fields_html(
11760 app_root: &Path,
11761 state_path: &Path,
11762 fixture_path: &Path,
11763 surface_json: &str,
11764 context_json: &str,
11765 item_json: Option<&str>,
11766) -> Option<String> {
11767 generated_surface_form_fields_html(
11768 app_root,
11769 state_path,
11770 fixture_path,
11771 surface_json,
11772 context_json,
11773 item_json,
11774 )
11775}
11776
11777/// Builds generated-surface sort options from contract metadata.
11778///
11779/// # Parameters
11780///
11781/// - `app_root`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_sort_options_html`].
11782/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_sort_options_html`].
11783/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_sort_options_html`].
11784///
11785/// # Returns
11786///
11787/// Returns [`Some`] when [`crate::runtime::json_commands::build_generated_surface_sort_options_html`] can produce a value and [`None`] otherwise.
11788///
11789/// # Examples
11790///
11791/// ```rust,ignore
11792/// let _result = pharos_native::runtime::json_commands::build_generated_surface_sort_options_html(std::path::Path::new("apps/dev_docs"), "{}", todo!());
11793/// ```
11794///
11795/// # Related
11796///
11797/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11798pub fn build_generated_surface_sort_options_html(
11799 app_root: &Path,
11800 surface_json: &str,
11801 raw_query: &str,
11802) -> Option<String> {
11803 generated_surface_sort_options_html(app_root, surface_json, raw_query)
11804}
11805
11806/// Builds generated-surface management table parts from a bounded JSON value.
11807///
11808/// # Parameters
11809///
11810/// - `app_root`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_manage_table_parts`].
11811/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_manage_table_parts`].
11812/// - `context_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_manage_table_parts`].
11813/// - `list_result_json`: Input consumed by [`crate::runtime::json_commands::build_generated_surface_manage_table_parts`].
11814///
11815/// # Returns
11816///
11817/// Returns [`Some`] when [`crate::runtime::json_commands::build_generated_surface_manage_table_parts`] can produce a value and [`None`] otherwise.
11818///
11819/// # Examples
11820///
11821/// ```rust,ignore
11822/// let _result = pharos_native::runtime::json_commands::build_generated_surface_manage_table_parts(std::path::Path::new("apps/dev_docs"), "{}", todo!(), todo!());
11823/// ```
11824///
11825/// # Related
11826///
11827/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11828pub fn build_generated_surface_manage_table_parts(
11829 app_root: &Path,
11830 surface_json: &str,
11831 context_json: &str,
11832 list_result_json: &str,
11833) -> Option<String> {
11834 generated_surface_manage_table_json(app_root, surface_json, context_json, list_result_json)
11835}
11836
11837/// Applies one generated-surface filter item to a record.
11838///
11839/// # Parameters
11840///
11841/// - `item`: Input consumed by [`crate::runtime::json_commands::surface_filter_item`].
11842/// - `filter`: Input consumed by [`crate::runtime::json_commands::surface_filter_item`].
11843/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::surface_filter_item`].
11844///
11845/// # Returns
11846///
11847/// Returns `true` when the condition checked by [`crate::runtime::json_commands::surface_filter_item`] holds and `false` otherwise.
11848///
11849/// # Examples
11850///
11851/// ```rust,ignore
11852/// let _result = pharos_native::runtime::json_commands::surface_filter_item(todo!(), todo!(), todo!());
11853/// ```
11854///
11855/// # Related
11856///
11857/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11858pub fn surface_filter_item(item: &Value, filter: &Value, raw_query: &str) -> bool {
11859 generated_surface_filter_matches(item, filter, raw_query)
11860}
11861
11862/// Builds active filter state from a raw query string.
11863///
11864/// # Parameters
11865///
11866/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::surface_build_active_filters`].
11867/// - `filters`: Input consumed by [`crate::runtime::json_commands::surface_build_active_filters`].
11868///
11869/// # Returns
11870///
11871/// Returns [`Some`] when [`crate::runtime::json_commands::surface_build_active_filters`] can produce a value and [`None`] otherwise.
11872///
11873/// # Performance
11874///
11875/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::surface_build_active_filters`].
11876///
11877/// # Examples
11878///
11879/// ```rust,ignore
11880/// let _result = pharos_native::runtime::json_commands::surface_build_active_filters(todo!(), todo!());
11881/// ```
11882///
11883/// # Related
11884///
11885/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11886pub fn surface_build_active_filters(raw_query: &str, filters: &Value) -> Option<String> {
11887 let mut active = Map::new();
11888 for filter in filters.as_array()? {
11889 let key = filter.get("field").and_then(Value::as_str)?;
11890 if let Some(value) = request_query_value(raw_query, key) {
11891 active.insert(key.to_owned(), Value::String(value));
11892 }
11893 }
11894 compact(&Value::Object(active))
11895}
11896
11897/// Builds stable sort configuration from a JSON contract and query string.
11898///
11899/// # Parameters
11900///
11901/// - `options`: Input consumed by [`crate::runtime::json_commands::surface_build_sort_config`].
11902/// - `raw_query`: Input consumed by [`crate::runtime::json_commands::surface_build_sort_config`].
11903///
11904/// # Returns
11905///
11906/// Returns [`Some`] when [`crate::runtime::json_commands::surface_build_sort_config`] can produce a value and [`None`] otherwise.
11907///
11908/// # Examples
11909///
11910/// ```rust,ignore
11911/// let _result = pharos_native::runtime::json_commands::surface_build_sort_config(todo!(), todo!());
11912/// ```
11913///
11914/// # Related
11915///
11916/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11917pub fn surface_build_sort_config(options: &Value, raw_query: &str) -> Option<String> {
11918 let field = request_query_value(raw_query, "sort").or_else(|| {
11919 options
11920 .get("default")
11921 .and_then(Value::as_str)
11922 .map(ToOwned::to_owned)
11923 });
11924 compact(
11925 &serde_json::json!({"field": field, "direction": request_query_value(raw_query, "direction").unwrap_or_else(|| "asc".to_owned())}),
11926 )
11927}
11928
11929/// Sorts a bounded list using a declared string or numeric field.
11930///
11931/// # Parameters
11932///
11933/// - `items`: Input consumed by [`crate::runtime::json_commands::surface_sort_items`].
11934/// - `field`: Input consumed by [`crate::runtime::json_commands::surface_sort_items`].
11935/// - `descending`: Input consumed by [`crate::runtime::json_commands::surface_sort_items`].
11936///
11937/// # Returns
11938///
11939/// Returns `true` when the condition checked by [`crate::runtime::json_commands::surface_sort_items`] holds and `false` otherwise.
11940///
11941/// # Examples
11942///
11943/// ```rust,ignore
11944/// let _result = pharos_native::runtime::json_commands::surface_sort_items(todo!(), "field", todo!());
11945/// ```
11946///
11947/// # Related
11948///
11949/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11950pub fn surface_sort_items(items: &mut Value, field: &str, descending: bool) -> bool {
11951 let Some(values) = items.as_array_mut() else {
11952 return false;
11953 };
11954 values.sort_by(|left, right| {
11955 let ordering = match (left.get(field), right.get(field)) {
11956 (Some(Value::Number(left)), Some(Value::Number(right))) => left
11957 .as_f64()
11958 .partial_cmp(&right.as_f64())
11959 .unwrap_or(std::cmp::Ordering::Equal),
11960 _ => value_to_text(left.get(field).unwrap_or(&Value::Null))
11961 .cmp(&value_to_text(right.get(field).unwrap_or(&Value::Null))),
11962 };
11963 if descending {
11964 ordering.reverse()
11965 } else {
11966 ordering
11967 }
11968 });
11969 true
11970}
11971
11972/// Finds an element in a UI ledger by its stable element id.
11973///
11974/// # Parameters
11975///
11976/// - `root`: Input consumed by [`crate::runtime::json_commands::find_ui_ledger_element`].
11977/// - `element_id`: Input consumed by [`crate::runtime::json_commands::find_ui_ledger_element`].
11978///
11979/// # Returns
11980///
11981/// Returns [`Some`] when [`crate::runtime::json_commands::find_ui_ledger_element`] can produce a value and [`None`] otherwise.
11982///
11983/// # Performance
11984///
11985/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_ui_ledger_element`].
11986///
11987/// # Examples
11988///
11989/// ```rust,ignore
11990/// let _result = pharos_native::runtime::json_commands::find_ui_ledger_element(std::path::Path::new("."), todo!());
11991/// ```
11992///
11993/// # Related
11994///
11995/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
11996pub fn find_ui_ledger_element(root: &Value, element_id: &str) -> Option<String> {
11997 let elements = root.get("elements").and_then(Value::as_array)?;
11998 elements
11999 .iter()
12000 .find(|item| item.get("id").and_then(Value::as_str) == Some(element_id))
12001 .and_then(compact)
12002}
12003
12004/// Performs business by slug.
12005fn business_by_slug<'a>(root: &'a Value, slug: &str) -> Option<&'a Value> {
12006 bounded_state_array(root, "businesses")
12007 .iter()
12008 .find(|business| business.get("slug").and_then(Value::as_str) == Some(slug))
12009}
12010
12011/// Emits the business edit-page JSON contract.
12012///
12013/// # Parameters
12014///
12015/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_edit_page`].
12016/// - `slug`: Input consumed by [`crate::runtime::json_commands::emit_business_edit_page`].
12017/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_business_edit_page`].
12018///
12019/// # Returns
12020///
12021/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_edit_page`] can produce a value and [`None`] otherwise.
12022///
12023/// # Examples
12024///
12025/// ```rust,ignore
12026/// let _result = pharos_native::runtime::json_commands::emit_business_edit_page(std::path::Path::new("."), todo!(), std::path::Path::new("apps/dev_docs"));
12027/// ```
12028///
12029/// # Related
12030///
12031/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12032pub fn emit_business_edit_page(root: &Value, slug: &str, app_root: &Path) -> Option<String> {
12033 let _ = app_root;
12034 let business = business_by_slug(root, slug)?;
12035 compact(&serde_json::json!({"business": business, "mode": "edit"}))
12036}
12037
12038/// Emits the public business page JSON contract.
12039///
12040/// # Parameters
12041///
12042/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_public_page`].
12043/// - `slug`: Input consumed by [`crate::runtime::json_commands::emit_business_public_page`].
12044/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_business_public_page`].
12045///
12046/// # Returns
12047///
12048/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_public_page`] can produce a value and [`None`] otherwise.
12049///
12050/// # Performance
12051///
12052/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_business_public_page`].
12053///
12054/// # Examples
12055///
12056/// ```rust,ignore
12057/// let _result = pharos_native::runtime::json_commands::emit_business_public_page(std::path::Path::new("."), todo!(), 1);
12058/// ```
12059///
12060/// # Related
12061///
12062/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12063pub fn emit_business_public_page(root: &Value, slug: &str, user_id: i64) -> Option<String> {
12064 let business = business_by_slug(root, slug)?;
12065 let business_id = business.get("id").and_then(Value::as_i64).unwrap_or(0);
12066 let services: Vec<Value> = bounded_state_array(root, "services")
12067 .iter()
12068 .filter(|service| service.get("business_id").and_then(Value::as_i64) == Some(business_id))
12069 .take(MAX_OUTPUT_ROWS_JSON)
12070 .cloned()
12071 .collect();
12072 let subscribed = bounded_state_array(root, "subscriptions")
12073 .iter()
12074 .any(|row| {
12075 row.get("business_id").and_then(Value::as_i64) == Some(business_id)
12076 && row.get("user_id").and_then(Value::as_i64) == Some(user_id)
12077 });
12078 compact(
12079 &serde_json::json!({"business": business, "services": services, "subscribed": subscribed}),
12080 )
12081}
12082
12083/// Emits service rows for a business administration page.
12084///
12085/// # Parameters
12086///
12087/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_service_rows`].
12088/// - `business_id`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_service_rows`].
12089///
12090/// # Returns
12091///
12092/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_admin_service_rows`] can produce a value and [`None`] otherwise.
12093///
12094/// # Examples
12095///
12096/// ```rust,ignore
12097/// let _result = pharos_native::runtime::json_commands::emit_business_admin_service_rows(std::path::Path::new("."), todo!());
12098/// ```
12099///
12100/// # Related
12101///
12102/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12103pub fn emit_business_admin_service_rows(root: &Value, business_id: i64) -> Option<String> {
12104 rows_as_json(root, "services", |row| {
12105 row.get("business_id").and_then(Value::as_i64) == Some(business_id)
12106 })
12107}
12108
12109/// Emits technician rows for a business administration page.
12110///
12111/// # Parameters
12112///
12113/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_technician_rows`].
12114/// - `business_id`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_technician_rows`].
12115///
12116/// # Returns
12117///
12118/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_admin_technician_rows`] can produce a value and [`None`] otherwise.
12119///
12120/// # Examples
12121///
12122/// ```rust,ignore
12123/// let _result = pharos_native::runtime::json_commands::emit_business_admin_technician_rows(std::path::Path::new("."), todo!());
12124/// ```
12125///
12126/// # Related
12127///
12128/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12129pub fn emit_business_admin_technician_rows(root: &Value, business_id: i64) -> Option<String> {
12130 rows_as_json(root, "technicians", |row| {
12131 row.get("business_id").and_then(Value::as_i64) == Some(business_id)
12132 })
12133}
12134
12135/// Emits the business calendar page contract.
12136///
12137/// # Parameters
12138///
12139/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_page`].
12140/// - `slug`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_page`].
12141///
12142/// # Returns
12143///
12144/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_calendar_page`] can produce a value and [`None`] otherwise.
12145///
12146/// # Performance
12147///
12148/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_business_calendar_page`].
12149///
12150/// # Examples
12151///
12152/// ```rust,ignore
12153/// let _result = pharos_native::runtime::json_commands::emit_business_calendar_page(std::path::Path::new("."), todo!());
12154/// ```
12155///
12156/// # Related
12157///
12158/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12159pub fn emit_business_calendar_page(root: &Value, slug: &str) -> Option<String> {
12160 let business = business_by_slug(root, slug)?;
12161 let business_id = business.get("id").and_then(Value::as_i64).unwrap_or(0);
12162 let availability: Vec<Value> = bounded_state_array(root, "business_availability")
12163 .iter()
12164 .filter(|row| row.get("business_id").and_then(Value::as_i64) == Some(business_id))
12165 .take(MAX_OUTPUT_ROWS_JSON)
12166 .cloned()
12167 .collect();
12168 compact(&serde_json::json!({"business": business, "availability": availability}))
12169}
12170
12171/// Emits the rendered business-calendar page contract.
12172///
12173/// # Parameters
12174///
12175/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_render_page`].
12176/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_render_page`].
12177/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_render_page`].
12178/// - `selected_year`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_render_page`].
12179/// - `selected_month`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_render_page`].
12180/// - `selected_date`: Input consumed by [`crate::runtime::json_commands::emit_business_calendar_render_page`].
12181///
12182/// # Returns
12183///
12184/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_calendar_render_page`] can produce a value and [`None`] otherwise.
12185///
12186/// # Performance
12187///
12188/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_business_calendar_render_page`].
12189///
12190/// # Examples
12191///
12192/// ```rust,ignore
12193/// let _result = pharos_native::runtime::json_commands::emit_business_calendar_render_page(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
12194/// ```
12195///
12196/// # Related
12197///
12198/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12199pub fn emit_business_calendar_render_page(
12200 root: &Value,
12201 business_slug: &str,
12202 app_root: &Path,
12203 selected_year: Option<&str>,
12204 selected_month: Option<&str>,
12205 selected_date: Option<&str>,
12206) -> Option<String> {
12207 let _ = app_root;
12208 let page = emit_business_calendar_page(root, business_slug)?;
12209 let page = serde_json::from_str::<Value>(&page).ok()?;
12210 compact(&serde_json::json!({
12211 "page": page,
12212 "selected_year": selected_year,
12213 "selected_month": selected_month,
12214 "selected_date": selected_date,
12215 }))
12216}
12217
12218/// Emits the business public page with its render context.
12219///
12220/// # Parameters
12221///
12222/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_public_render_page`].
12223/// - `slug`: Input consumed by [`crate::runtime::json_commands::emit_business_public_render_page`].
12224/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_business_public_render_page`].
12225/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_business_public_render_page`].
12226///
12227/// # Returns
12228///
12229/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_public_render_page`] can produce a value and [`None`] otherwise.
12230///
12231/// # Performance
12232///
12233/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_business_public_render_page`].
12234///
12235/// # Examples
12236///
12237/// ```rust,ignore
12238/// let _result = pharos_native::runtime::json_commands::emit_business_public_render_page(std::path::Path::new("."), todo!(), 1, std::path::Path::new("apps/dev_docs"));
12239/// ```
12240///
12241/// # Related
12242///
12243/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12244pub fn emit_business_public_render_page(
12245 root: &Value,
12246 slug: &str,
12247 user_id: i64,
12248 app_root: &Path,
12249) -> Option<String> {
12250 let _ = app_root;
12251 let page = emit_business_public_page(root, slug, user_id)?;
12252 compact(&serde_json::json!({"page": serde_json::from_str::<Value>(&page).ok()?}))
12253}
12254
12255/// Emits a business dashboard page scoped to tenant identifiers.
12256///
12257/// # Parameters
12258///
12259/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_dashboard_page`].
12260/// - `tenant_ids`: Input consumed by [`crate::runtime::json_commands::emit_business_dashboard_page`].
12261/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_business_dashboard_page`].
12262///
12263/// # Returns
12264///
12265/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_dashboard_page`] can produce a value and [`None`] otherwise.
12266///
12267/// # Performance
12268///
12269/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_business_dashboard_page`].
12270///
12271/// # Examples
12272///
12273/// ```rust,ignore
12274/// let _result = pharos_native::runtime::json_commands::emit_business_dashboard_page(std::path::Path::new("."), todo!(), std::path::Path::new("apps/dev_docs"));
12275/// ```
12276///
12277/// # Related
12278///
12279/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12280pub fn emit_business_dashboard_page(
12281 root: &Value,
12282 tenant_ids: &Value,
12283 app_root: &Path,
12284) -> Option<String> {
12285 let _ = app_root;
12286 let businesses: Vec<Value> = bounded_state_array(root, "businesses")
12287 .iter()
12288 .filter(|business| {
12289 let id = business.get("id").and_then(Value::as_i64).unwrap_or(0);
12290 tenant_ids
12291 .as_array()
12292 .is_none_or(|ids| ids.iter().any(|item| item.as_i64() == Some(id)))
12293 })
12294 .take(MAX_OUTPUT_ROWS_JSON)
12295 .cloned()
12296 .collect();
12297 compact(&serde_json::json!({"businesses": businesses}))
12298}
12299
12300/// Emits the business administration page contract.
12301///
12302/// # Parameters
12303///
12304/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_page`].
12305/// - `slug`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_page`].
12306/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_page`].
12307/// - `invite_account_type_options_html`: Input consumed by [`crate::runtime::json_commands::emit_business_admin_page`].
12308///
12309/// # Returns
12310///
12311/// Returns [`Some`] when [`crate::runtime::json_commands::emit_business_admin_page`] can produce a value and [`None`] otherwise.
12312///
12313/// # Examples
12314///
12315/// ```rust,ignore
12316/// let _result = pharos_native::runtime::json_commands::emit_business_admin_page(std::path::Path::new("."), todo!(), std::path::Path::new("apps/dev_docs"), todo!());
12317/// ```
12318///
12319/// # Related
12320///
12321/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12322pub fn emit_business_admin_page(
12323 root: &Value,
12324 slug: &str,
12325 app_root: &Path,
12326 invite_account_type_options_html: Option<&str>,
12327) -> Option<String> {
12328 let _ = app_root;
12329 let business = business_by_slug(root, slug)?;
12330 compact(
12331 &serde_json::json!({"business": business, "invite_account_type_options_html": invite_account_type_options_html.unwrap_or_default()}),
12332 )
12333}
12334
12335/// Builds explore-page parameters from the public business collection.
12336///
12337/// # Parameters
12338///
12339/// - `root`: Input consumed by [`crate::runtime::json_commands::build_explore_page_params`].
12340/// - `app_root`: Input consumed by [`crate::runtime::json_commands::build_explore_page_params`].
12341///
12342/// # Returns
12343///
12344/// Returns [`Some`] when [`crate::runtime::json_commands::build_explore_page_params`] can produce a value and [`None`] otherwise.
12345///
12346/// # Examples
12347///
12348/// ```rust,ignore
12349/// let _result = pharos_native::runtime::json_commands::build_explore_page_params(std::path::Path::new("."), std::path::Path::new("apps/dev_docs"));
12350/// ```
12351///
12352/// # Related
12353///
12354/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12355pub fn build_explore_page_params(root: &Value, app_root: &Path) -> Option<String> {
12356 let _ = app_root;
12357 emit_public_business_cards(root)
12358}
12359
12360/// Emits the explore page contract.
12361///
12362/// # Parameters
12363///
12364/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_explore_page`].
12365/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_explore_page`].
12366///
12367/// # Returns
12368///
12369/// Returns [`Some`] when [`crate::runtime::json_commands::emit_explore_page`] can produce a value and [`None`] otherwise.
12370///
12371/// # Examples
12372///
12373/// ```rust,ignore
12374/// let _result = pharos_native::runtime::json_commands::emit_explore_page(std::path::Path::new("."), std::path::Path::new("apps/dev_docs"));
12375/// ```
12376///
12377/// # Related
12378///
12379/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12380pub fn emit_explore_page(root: &Value, app_root: &Path) -> Option<String> {
12381 build_explore_page_params(root, app_root)
12382}
12383
12384/// Emits a rendered profile page for one user.
12385///
12386/// # Parameters
12387///
12388/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_profile_page`].
12389/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_profile_page`].
12390///
12391/// # Returns
12392///
12393/// Returns [`Some`] when [`crate::runtime::json_commands::emit_profile_page`] can produce a value and [`None`] otherwise.
12394///
12395/// # Performance
12396///
12397/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_profile_page`].
12398///
12399/// # Examples
12400///
12401/// ```rust,ignore
12402/// let _result = pharos_native::runtime::json_commands::emit_profile_page(std::path::Path::new("."), 1);
12403/// ```
12404///
12405/// # Related
12406///
12407/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12408pub fn emit_profile_page(root: &Value, user_id: i64) -> Option<String> {
12409 let user = bounded_state_array(root, "users")
12410 .iter()
12411 .find(|row| row.get("id").and_then(Value::as_i64) == Some(user_id))?;
12412 compact(&serde_json::json!({"user": user}))
12413}
12414
12415/// Emits profile-page render context.
12416///
12417/// # Parameters
12418///
12419/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_profile_render_page`].
12420/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_profile_render_page`].
12421/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_profile_render_page`].
12422///
12423/// # Returns
12424///
12425/// Returns [`Some`] when [`crate::runtime::json_commands::emit_profile_render_page`] can produce a value and [`None`] otherwise.
12426///
12427/// # Examples
12428///
12429/// ```rust,ignore
12430/// let _result = pharos_native::runtime::json_commands::emit_profile_render_page(std::path::Path::new("."), 1, std::path::Path::new("apps/dev_docs"));
12431/// ```
12432///
12433/// # Related
12434///
12435/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12436pub fn emit_profile_render_page(root: &Value, user_id: i64, app_root: &Path) -> Option<String> {
12437 let _ = app_root;
12438 emit_profile_page(root, user_id)
12439}
12440
12441/// Emits appointments visible to a user and selected tenant ids.
12442///
12443/// # Parameters
12444///
12445/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_appointments_page`].
12446/// - `user_id`: Input consumed by [`crate::runtime::json_commands::emit_appointments_page`].
12447/// - `tenant_ids`: Input consumed by [`crate::runtime::json_commands::emit_appointments_page`].
12448/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_appointments_page`].
12449///
12450/// # Returns
12451///
12452/// Returns [`Some`] when [`crate::runtime::json_commands::emit_appointments_page`] can produce a value and [`None`] otherwise.
12453///
12454/// # Performance
12455///
12456/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_appointments_page`].
12457///
12458/// # Examples
12459///
12460/// ```rust,ignore
12461/// let _result = pharos_native::runtime::json_commands::emit_appointments_page(std::path::Path::new("."), 1, todo!(), std::path::Path::new("apps/dev_docs"));
12462/// ```
12463///
12464/// # Related
12465///
12466/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12467pub fn emit_appointments_page(
12468 root: &Value,
12469 user_id: i64,
12470 tenant_ids: &Value,
12471 app_root: &Path,
12472) -> Option<String> {
12473 let _ = app_root;
12474 let appointments: Vec<Value> = bounded_state_array(root, "appointments")
12475 .iter()
12476 .filter(|row| row.get("user_id").and_then(Value::as_i64) == Some(user_id))
12477 .filter(|row| {
12478 tenant_ids.as_array().is_none_or(|ids| {
12479 ids.iter()
12480 .any(|item| item.as_i64() == row.get("tenant_id").and_then(Value::as_i64))
12481 })
12482 })
12483 .take(MAX_OUTPUT_ROWS_JSON)
12484 .cloned()
12485 .collect();
12486 compact(&Value::Array(appointments))
12487}
12488
12489/// Emits the service booking form contract.
12490///
12491/// # Parameters
12492///
12493/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_service_book_form_page`].
12494/// - `slug`: Input consumed by [`crate::runtime::json_commands::emit_service_book_form_page`].
12495/// - `service_id`: Input consumed by [`crate::runtime::json_commands::emit_service_book_form_page`].
12496/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_service_book_form_page`].
12497/// - `error_message`: Input consumed by [`crate::runtime::json_commands::emit_service_book_form_page`].
12498///
12499/// # Returns
12500///
12501/// Returns [`Some`] when [`crate::runtime::json_commands::emit_service_book_form_page`] can produce a value and [`None`] otherwise.
12502///
12503/// # Performance
12504///
12505/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_service_book_form_page`].
12506///
12507/// # Examples
12508///
12509/// ```rust,ignore
12510/// let _result = pharos_native::runtime::json_commands::emit_service_book_form_page(std::path::Path::new("."), todo!(), todo!(), std::path::Path::new("apps/dev_docs"), todo!());
12511/// ```
12512///
12513/// # Related
12514///
12515/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12516pub fn emit_service_book_form_page(
12517 root: &Value,
12518 slug: &str,
12519 service_id: i64,
12520 app_root: &Path,
12521 error_message: Option<&str>,
12522) -> Option<String> {
12523 let _ = app_root;
12524 let service = bounded_state_array(root, "services").iter().find(|row| {
12525 row.get("id").and_then(Value::as_i64) == Some(service_id)
12526 && business_by_slug(root, slug).is_some()
12527 })?;
12528 compact(
12529 &serde_json::json!({"service": service, "error_message": error_message.unwrap_or_default()}),
12530 )
12531}
12532
12533/// Emits the service-edit page contract.
12534///
12535/// # Parameters
12536///
12537/// - `root`: Input consumed by [`crate::runtime::json_commands::emit_service_edit_page`].
12538/// - `service_id`: Input consumed by [`crate::runtime::json_commands::emit_service_edit_page`].
12539/// - `app_root`: Input consumed by [`crate::runtime::json_commands::emit_service_edit_page`].
12540///
12541/// # Returns
12542///
12543/// Returns [`Some`] when [`crate::runtime::json_commands::emit_service_edit_page`] can produce a value and [`None`] otherwise.
12544///
12545/// # Performance
12546///
12547/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::emit_service_edit_page`].
12548///
12549/// # Examples
12550///
12551/// ```rust,ignore
12552/// let _result = pharos_native::runtime::json_commands::emit_service_edit_page(std::path::Path::new("."), todo!(), std::path::Path::new("apps/dev_docs"));
12553/// ```
12554///
12555/// # Related
12556///
12557/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12558pub fn emit_service_edit_page(root: &Value, service_id: i64, app_root: &Path) -> Option<String> {
12559 let _ = app_root;
12560 let service = bounded_state_array(root, "services")
12561 .iter()
12562 .find(|row| row.get("id").and_then(Value::as_i64) == Some(service_id))?;
12563 compact(&serde_json::json!({"service": service, "mode": "edit"}))
12564}
12565
12566/// Records one pair in a bounded seen-pair set and reports whether it was new.
12567///
12568/// # Parameters
12569///
12570/// - `seen`: Input consumed by [`crate::runtime::json_commands::seen_long_pair`].
12571/// - `left`: Input consumed by [`crate::runtime::json_commands::seen_long_pair`].
12572/// - `right`: Input consumed by [`crate::runtime::json_commands::seen_long_pair`].
12573///
12574/// # Returns
12575///
12576/// Returns `true` when the condition checked by [`crate::runtime::json_commands::seen_long_pair`] holds and `false` otherwise.
12577///
12578/// # Examples
12579///
12580/// ```rust,ignore
12581/// let _result = pharos_native::runtime::json_commands::seen_long_pair(todo!(), todo!(), todo!());
12582/// ```
12583///
12584/// # Related
12585///
12586/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12587pub fn seen_long_pair(seen: &mut Vec<(i64, i64)>, left: i64, right: i64) -> bool {
12588 if seen.contains(&(left, right)) {
12589 return false;
12590 }
12591 if seen.len() >= MAX_OUTPUT_ROWS_JSON {
12592 return false;
12593 }
12594 seen.push((left, right));
12595 true
12596}
12597
12598/// Converts one event row into the safe Rust event-stream representation.
12599///
12600/// # Parameters
12601///
12602/// - `row`: Input consumed by [`crate::runtime::json_commands::collect_event_stream_row`].
12603/// - `native_order`: Input consumed by [`crate::runtime::json_commands::collect_event_stream_row`].
12604///
12605/// # Returns
12606///
12607/// Returns [`Some`] when [`crate::runtime::json_commands::collect_event_stream_row`] can produce a value and [`None`] otherwise.
12608///
12609/// # Examples
12610///
12611/// ```rust,ignore
12612/// let _result = pharos_native::runtime::json_commands::collect_event_stream_row(todo!(), todo!());
12613/// ```
12614///
12615/// # Related
12616///
12617/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12618pub fn collect_event_stream_row(
12619 row: &Value,
12620 native_order: i64,
12621) -> Option<super::json_ops::EventStreamRowRuntime> {
12622 row.is_object()
12623 .then(|| super::json_ops::EventStreamRowRuntime {
12624 created_at: row
12625 .get("created_at")
12626 .and_then(Value::as_str)
12627 .map(ToOwned::to_owned),
12628 source: row
12629 .get("source")
12630 .and_then(Value::as_str)
12631 .map(ToOwned::to_owned),
12632 native_order,
12633 })
12634}
12635
12636/// Releases all event rows owned by a caller.
12637///
12638/// # Parameters
12639///
12640/// - `rows`: Input consumed by [`crate::runtime::json_commands::free_event_stream_row_list`].
12641///
12642/// # Returns
12643///
12644/// Returns after [`crate::runtime::json_commands::free_event_stream_row_list`] applies its in-place effect.
12645///
12646/// # Examples
12647///
12648/// ```rust,ignore
12649/// let _result = pharos_native::runtime::json_commands::free_event_stream_row_list(todo!());
12650/// ```
12651///
12652/// # Related
12653///
12654/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12655pub fn free_event_stream_row_list(rows: &mut Vec<super::json_ops::EventStreamRowRuntime>) {
12656 rows.clear();
12657}
12658
12659/// Appends one event-stream row to a bounded JSON array.
12660///
12661/// # Parameters
12662///
12663/// - `output`: Input consumed by [`crate::runtime::json_commands::append_event_stream_row`].
12664/// - `row`: Input consumed by [`crate::runtime::json_commands::append_event_stream_row`].
12665///
12666/// # Returns
12667///
12668/// Returns `true` when the condition checked by [`crate::runtime::json_commands::append_event_stream_row`] holds and `false` otherwise.
12669///
12670/// # Examples
12671///
12672/// ```rust,ignore
12673/// let _result = pharos_native::runtime::json_commands::append_event_stream_row(todo!(), todo!());
12674/// ```
12675///
12676/// # Related
12677///
12678/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12679pub fn append_event_stream_row(output: &mut Vec<Value>, row: &Value) -> bool {
12680 if output.len() >= MAX_OUTPUT_ROWS_JSON || !row.is_object() {
12681 return false;
12682 }
12683 output.push(row.clone());
12684 true
12685}
12686
12687/// Appends relation checkbox HTML using the app-owned generated fragment contract.
12688///
12689/// # Parameters
12690///
12691/// - `output`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_relation_checkboxes_html`].
12692/// - `app_root`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_relation_checkboxes_html`].
12693/// - `state_path`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_relation_checkboxes_html`].
12694/// - `relation_json`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_relation_checkboxes_html`].
12695/// - `context_json`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_relation_checkboxes_html`].
12696/// - `selected_json`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_relation_checkboxes_html`].
12697///
12698/// # Returns
12699///
12700/// Returns `true` when the condition checked by [`crate::runtime::json_commands::append_generated_surface_relation_checkboxes_html`] holds and `false` otherwise.
12701///
12702/// # Examples
12703///
12704/// ```rust,ignore
12705/// let _result = pharos_native::runtime::json_commands::append_generated_surface_relation_checkboxes_html(todo!(), std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
12706/// ```
12707///
12708/// # Related
12709///
12710/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12711pub fn append_generated_surface_relation_checkboxes_html(
12712 output: &mut String,
12713 app_root: &Path,
12714 state_path: &Path,
12715 relation_json: &str,
12716 context_json: &str,
12717 selected_json: Option<&str>,
12718) -> bool {
12719 let Some(fragment) = generated_surface_relation_checkboxes_html(
12720 app_root,
12721 state_path,
12722 relation_json,
12723 context_json,
12724 selected_json,
12725 ) else {
12726 return false;
12727 };
12728 if output.len().saturating_add(fragment.len()) > MAX_TEXT_BYTES {
12729 return false;
12730 }
12731 output.push_str(&fragment);
12732 true
12733}
12734
12735/// Appends generated select options using the app-owned fragment contract.
12736///
12737/// # Parameters
12738///
12739/// - `output`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_select_options_html`].
12740/// - `app_root`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_select_options_html`].
12741/// - `field_json`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_select_options_html`].
12742/// - `selected`: Input consumed by [`crate::runtime::json_commands::append_generated_surface_select_options_html`].
12743///
12744/// # Returns
12745///
12746/// Returns `true` when the condition checked by [`crate::runtime::json_commands::append_generated_surface_select_options_html`] holds and `false` otherwise.
12747///
12748/// # Examples
12749///
12750/// ```rust,ignore
12751/// let _result = pharos_native::runtime::json_commands::append_generated_surface_select_options_html(todo!(), std::path::Path::new("apps/dev_docs"), todo!(), todo!());
12752/// ```
12753///
12754/// # Related
12755///
12756/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12757pub fn append_generated_surface_select_options_html(
12758 output: &mut String,
12759 app_root: &Path,
12760 field_json: &str,
12761 selected: Option<&str>,
12762) -> bool {
12763 let Some(fragment) = generated_surface_select_options_html(app_root, field_json, selected)
12764 else {
12765 return false;
12766 };
12767 if output.len().saturating_add(fragment.len()) > MAX_TEXT_BYTES {
12768 return false;
12769 }
12770 output.push_str(&fragment);
12771 true
12772}
12773
12774/// Performs a bounded case-insensitive substring test.
12775///
12776/// # Parameters
12777///
12778/// - `haystack`: Input consumed by [`crate::runtime::json_commands::contains_casefold`].
12779/// - `needle`: Input consumed by [`crate::runtime::json_commands::contains_casefold`].
12780///
12781/// # Returns
12782///
12783/// Returns `true` when the condition checked by [`crate::runtime::json_commands::contains_casefold`] holds and `false` otherwise.
12784///
12785/// # Examples
12786///
12787/// ```rust,ignore
12788/// let _result = pharos_native::runtime::json_commands::contains_casefold(todo!(), todo!());
12789/// ```
12790///
12791/// # Related
12792///
12793/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12794pub fn contains_casefold(haystack: &str, needle: &str) -> bool {
12795 !needle.is_empty()
12796 && haystack
12797 .to_ascii_lowercase()
12798 .contains(&needle.to_ascii_lowercase())
12799}
12800
12801/// Returns whether text is non-empty ASCII decimal digits.
12802///
12803/// # Parameters
12804///
12805/// - `text`: Input consumed by [`crate::runtime::json_commands::is_nonempty_ascii_digits`].
12806///
12807/// # Returns
12808///
12809/// Returns `true` when the condition checked by [`crate::runtime::json_commands::is_nonempty_ascii_digits`] holds and `false` otherwise.
12810///
12811/// # Performance
12812///
12813/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::is_nonempty_ascii_digits`].
12814///
12815/// # Examples
12816///
12817/// ```rust,ignore
12818/// let _result = pharos_native::runtime::json_commands::is_nonempty_ascii_digits("{}");
12819/// ```
12820///
12821/// # Related
12822///
12823/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12824pub fn is_nonempty_ascii_digits(text: &str) -> bool {
12825 !text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit())
12826}
12827
12828/// Returns an owned non-empty value or the supplied default.
12829///
12830/// # Parameters
12831///
12832/// - `value`: Input consumed by [`crate::runtime::json_commands::dup_or_default`].
12833/// - `default_value`: Input consumed by [`crate::runtime::json_commands::dup_or_default`].
12834///
12835/// # Returns
12836///
12837/// Returns the computed value produced by [`crate::runtime::json_commands::dup_or_default`].
12838///
12839/// # Examples
12840///
12841/// ```rust,ignore
12842/// let _result = pharos_native::runtime::json_commands::dup_or_default("value", todo!());
12843/// ```
12844///
12845/// # Related
12846///
12847/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12848pub fn dup_or_default(value: Option<&str>, default_value: &str) -> String {
12849 value
12850 .filter(|value| !value.is_empty())
12851 .unwrap_or(default_value)
12852 .to_owned()
12853}
12854
12855/// Emits a compact mutable JSON value and retains the caller's mutation ownership.
12856///
12857/// # Parameters
12858///
12859/// - `value`: Input consumed by [`crate::runtime::json_commands::emit_compact_json_mut_value`].
12860///
12861/// # Returns
12862///
12863/// Returns [`Some`] when [`crate::runtime::json_commands::emit_compact_json_mut_value`] can produce a value and [`None`] otherwise.
12864///
12865/// # Examples
12866///
12867/// ```rust,ignore
12868/// let _result = pharos_native::runtime::json_commands::emit_compact_json_mut_value("value");
12869/// ```
12870///
12871/// # Related
12872///
12873/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12874pub fn emit_compact_json_mut_value(value: &mut Value) -> Option<String> {
12875 compact(value)
12876}
12877
12878/// Emits one compact JSON value followed by a newline.
12879///
12880/// # Parameters
12881///
12882/// - `value`: Input consumed by [`crate::runtime::json_commands::emit_json_line`].
12883///
12884/// # Returns
12885///
12886/// Returns [`Some`] when [`crate::runtime::json_commands::emit_json_line`] can produce a value and [`None`] otherwise.
12887///
12888/// # Examples
12889///
12890/// ```rust,ignore
12891/// let _result = pharos_native::runtime::json_commands::emit_json_line("value");
12892/// ```
12893///
12894/// # Related
12895///
12896/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12897pub fn emit_json_line(value: &Value) -> Option<String> {
12898 Some(format!("{}\n", compact(value)?))
12899}
12900
12901/// Checks whether one event row is visible within a user and tenant scope.
12902///
12903/// # Parameters
12904///
12905/// - `row`: Input consumed by [`crate::runtime::json_commands::event_stream_allowed_by_scope`].
12906/// - `user_id`: Input consumed by [`crate::runtime::json_commands::event_stream_allowed_by_scope`].
12907/// - `tenant_id`: Input consumed by [`crate::runtime::json_commands::event_stream_allowed_by_scope`].
12908///
12909/// # Returns
12910///
12911/// Returns `true` when the condition checked by [`crate::runtime::json_commands::event_stream_allowed_by_scope`] holds and `false` otherwise.
12912///
12913/// # Examples
12914///
12915/// ```rust,ignore
12916/// let _result = pharos_native::runtime::json_commands::event_stream_allowed_by_scope(todo!(), 1, todo!());
12917/// ```
12918///
12919/// # Related
12920///
12921/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12922pub fn event_stream_allowed_by_scope(row: &Value, user_id: i64, tenant_id: i64) -> bool {
12923 let row_user = row.get("user_id").and_then(Value::as_i64);
12924 let row_tenant = row.get("tenant_id").and_then(Value::as_i64);
12925 (row_user.is_none() || row_user == Some(user_id))
12926 && (row_tenant.is_none() || row_tenant == Some(tenant_id))
12927}
12928
12929/// Matches event-stream filters declared as a JSON object.
12930///
12931/// # Parameters
12932///
12933/// - `row`: Input consumed by [`crate::runtime::json_commands::event_stream_matches_filters`].
12934/// - `filters`: Input consumed by [`crate::runtime::json_commands::event_stream_matches_filters`].
12935///
12936/// # Returns
12937///
12938/// Returns `true` when the condition checked by [`crate::runtime::json_commands::event_stream_matches_filters`] holds and `false` otherwise.
12939///
12940/// # Performance
12941///
12942/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::event_stream_matches_filters`].
12943///
12944/// # Examples
12945///
12946/// ```rust,ignore
12947/// let _result = pharos_native::runtime::json_commands::event_stream_matches_filters(todo!(), todo!());
12948/// ```
12949///
12950/// # Related
12951///
12952/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12953pub fn event_stream_matches_filters(row: &Value, filters: &Value) -> bool {
12954 filters.as_object().is_none_or(|entries| {
12955 entries
12956 .iter()
12957 .all(|(key, expected)| row.get(key).is_some_and(|actual| actual == expected))
12958 })
12959}
12960
12961/// Returns a bounded event resource identifier.
12962///
12963/// # Parameters
12964///
12965/// - `row`: Input consumed by [`crate::runtime::json_commands::event_stream_resource_id`].
12966///
12967/// # Returns
12968///
12969/// Returns [`Some`] when [`crate::runtime::json_commands::event_stream_resource_id`] can produce a value and [`None`] otherwise.
12970///
12971/// # Examples
12972///
12973/// ```rust,ignore
12974/// let _result = pharos_native::runtime::json_commands::event_stream_resource_id(todo!());
12975/// ```
12976///
12977/// # Related
12978///
12979/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
12980pub fn event_stream_resource_id(row: &Value) -> Option<String> {
12981 row.get("resource_id")
12982 .or_else(|| row.get("id"))
12983 .map(value_to_text)
12984 .filter(|value| !value.is_empty())
12985}
12986
12987/// Returns a positive event tenant identifier.
12988///
12989/// # Parameters
12990///
12991/// - `row`: Input consumed by [`crate::runtime::json_commands::event_stream_tenant_id`].
12992///
12993/// # Returns
12994///
12995/// Returns the computed value produced by [`crate::runtime::json_commands::event_stream_tenant_id`].
12996///
12997/// # Examples
12998///
12999/// ```rust,ignore
13000/// let _result = pharos_native::runtime::json_commands::event_stream_tenant_id(todo!());
13001/// ```
13002///
13003/// # Related
13004///
13005/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13006pub fn event_stream_tenant_id(row: &Value) -> i64 {
13007 row.get("tenant_id")
13008 .and_then(Value::as_i64)
13009 .unwrap_or(0)
13010 .max(0)
13011}
13012
13013/// Finds an array row by a positive integer field.
13014///
13015/// # Parameters
13016///
13017/// - `root`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_long_field`].
13018/// - `section`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_long_field`].
13019/// - `field`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_long_field`].
13020/// - `wanted`: Input consumed by [`crate::runtime::json_commands::find_array_item_by_long_field`].
13021///
13022/// # Returns
13023///
13024/// Returns [`Some`] when [`crate::runtime::json_commands::find_array_item_by_long_field`] can produce a value and [`None`] otherwise.
13025///
13026/// # Performance
13027///
13028/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_array_item_by_long_field`].
13029///
13030/// # Examples
13031///
13032/// ```rust,ignore
13033/// let _result = pharos_native::runtime::json_commands::find_array_item_by_long_field(std::path::Path::new("."), "items", "field", todo!());
13034/// ```
13035///
13036/// # Related
13037///
13038/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13039pub fn find_array_item_by_long_field(
13040 root: &Value,
13041 section: &str,
13042 field: &str,
13043 wanted: i64,
13044) -> Option<String> {
13045 bounded_state_array(root, section)
13046 .iter()
13047 .find(|item| item.get(field).and_then(Value::as_i64) == Some(wanted))
13048 .and_then(compact)
13049}
13050
13051/// Releases one optional event-stream row.
13052///
13053/// # Parameters
13054///
13055/// - `row`: Input consumed by [`crate::runtime::json_commands::free_event_stream_row`].
13056///
13057/// # Returns
13058///
13059/// Returns after [`crate::runtime::json_commands::free_event_stream_row`] applies its in-place effect.
13060///
13061/// # Examples
13062///
13063/// ```rust,ignore
13064/// let _result = pharos_native::runtime::json_commands::free_event_stream_row(todo!());
13065/// ```
13066///
13067/// # Related
13068///
13069/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13070pub fn free_event_stream_row(row: &mut Option<super::json_ops::EventStreamRowRuntime>) {
13071 *row = None;
13072}
13073
13074/// Produces the canonical generated-surface label for one field.
13075///
13076/// # Parameters
13077///
13078/// - `field_name`: Input consumed by [`crate::runtime::json_commands::generated_surface_label_from_field`].
13079///
13080/// # Returns
13081///
13082/// Returns the computed value produced by [`crate::runtime::json_commands::generated_surface_label_from_field`].
13083///
13084/// # Examples
13085///
13086/// ```rust,ignore
13087/// let _result = pharos_native::runtime::json_commands::generated_surface_label_from_field(todo!());
13088/// ```
13089///
13090/// # Related
13091///
13092/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13093pub fn generated_surface_label_from_field(field_name: &str) -> String {
13094 super::dynamic::generated_surface_label(Some(field_name)).unwrap_or_default()
13095}
13096
13097/// Appends one JSON value to an owned list.
13098///
13099/// # Parameters
13100///
13101/// - `list`: Input consumed by [`crate::runtime::json_commands::json_value_list_append`].
13102/// - `value`: Input consumed by [`crate::runtime::json_commands::json_value_list_append`].
13103///
13104/// # Returns
13105///
13106/// Returns `true` when the condition checked by [`crate::runtime::json_commands::json_value_list_append`] holds and `false` otherwise.
13107///
13108/// # Examples
13109///
13110/// ```rust,ignore
13111/// let _result = pharos_native::runtime::json_commands::json_value_list_append(todo!(), "value");
13112/// ```
13113///
13114/// # Related
13115///
13116/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13117pub fn json_value_list_append(
13118 list: &mut super::dynamic::RuntimeJsonValueList,
13119 value: &Value,
13120) -> bool {
13121 super::dynamic::runtime_json_value_list_append(list, value)
13122}
13123
13124/// Frees all values from an owned JSON list.
13125///
13126/// # Parameters
13127///
13128/// - `list`: Input consumed by [`crate::runtime::json_commands::json_value_list_free`].
13129///
13130/// # Returns
13131///
13132/// Returns after [`crate::runtime::json_commands::json_value_list_free`] applies its in-place effect.
13133///
13134/// # Examples
13135///
13136/// ```rust,ignore
13137/// let _result = pharos_native::runtime::json_commands::json_value_list_free(todo!());
13138/// ```
13139///
13140/// # Related
13141///
13142/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13143pub fn json_value_list_free(list: &mut super::dynamic::RuntimeJsonValueList) {
13144 super::dynamic::runtime_json_value_list_free(list);
13145}
13146
13147/// Applies the native truthiness contract to a JSON value.
13148///
13149/// # Parameters
13150///
13151/// - `value`: Input consumed by [`crate::runtime::json_commands::json_value_truthy`].
13152///
13153/// # Returns
13154///
13155/// Returns `true` when the condition checked by [`crate::runtime::json_commands::json_value_truthy`] holds and `false` otherwise.
13156///
13157/// # Examples
13158///
13159/// ```rust,ignore
13160/// let _result = pharos_native::runtime::json_commands::json_value_truthy("value");
13161/// ```
13162///
13163/// # Related
13164///
13165/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13166pub fn json_value_truthy(value: &Value) -> bool {
13167 match value {
13168 Value::Null => false,
13169 Value::Bool(value) => *value,
13170 Value::Number(value) => {
13171 value.as_i64().is_some_and(|value| value != 0)
13172 || value.as_f64().is_some_and(|value| value != 0.0)
13173 }
13174 Value::String(value) => {
13175 !value.is_empty() && !matches!(value.as_str(), "0" | "false" | "off" | "no")
13176 }
13177 Value::Array(value) => !value.is_empty(),
13178 Value::Object(value) => !value.is_empty(),
13179 }
13180}
13181
13182/// Returns the first string value from a payload object or array.
13183///
13184/// # Parameters
13185///
13186/// - `value`: Input consumed by [`crate::runtime::json_commands::payload_first_string`].
13187///
13188/// # Returns
13189///
13190/// Returns [`Some`] when [`crate::runtime::json_commands::payload_first_string`] can produce a value and [`None`] otherwise.
13191///
13192/// # Performance
13193///
13194/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::payload_first_string`].
13195///
13196/// # Examples
13197///
13198/// ```rust,ignore
13199/// let _result = pharos_native::runtime::json_commands::payload_first_string("value");
13200/// ```
13201///
13202/// # Related
13203///
13204/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13205pub fn payload_first_string(value: &Value) -> Option<String> {
13206 match value {
13207 Value::String(value) if !value.is_empty() => Some(value.clone()),
13208 Value::Array(items) => items.iter().find_map(payload_first_string),
13209 Value::Object(object) => object.values().find_map(payload_first_string),
13210 _ => None,
13211 }
13212}
13213
13214/// Reads a content-disposition parameter using strict quoted-field parsing.
13215///
13216/// # Parameters
13217///
13218/// - `headers`: Input consumed by [`crate::runtime::json_commands::content_disposition_param`].
13219/// - `key`: Input consumed by [`crate::runtime::json_commands::content_disposition_param`].
13220///
13221/// # Returns
13222///
13223/// Returns [`Some`] when [`crate::runtime::json_commands::content_disposition_param`] can produce a value and [`None`] otherwise.
13224///
13225/// # Examples
13226///
13227/// ```rust,ignore
13228/// let _result = pharos_native::runtime::json_commands::content_disposition_param("host: example", "key");
13229/// ```
13230///
13231/// # Related
13232///
13233/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13234pub fn content_disposition_param(headers: &str, key: &str) -> Option<String> {
13235 multipart_header_param(headers, key)
13236}
13237
13238/// Returns the decoded value for one query key.
13239///
13240/// # Parameters
13241///
13242/// - `query`: Input consumed by [`crate::runtime::json_commands::query_value_decoded`].
13243/// - `key`: Input consumed by [`crate::runtime::json_commands::query_value_decoded`].
13244///
13245/// # Returns
13246///
13247/// Returns [`Some`] when [`crate::runtime::json_commands::query_value_decoded`] can produce a value and [`None`] otherwise.
13248///
13249/// # Examples
13250///
13251/// ```rust,ignore
13252/// let _result = pharos_native::runtime::json_commands::query_value_decoded("page=1", "key");
13253/// ```
13254///
13255/// # Related
13256///
13257/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13258pub fn query_value_decoded(query: &str, key: &str) -> Option<String> {
13259 request_query_value(query, key)
13260}
13261
13262/// Classifies a resource identifier by its stable prefix.
13263///
13264/// # Parameters
13265///
13266/// - `resource_id`: Input consumed by [`crate::runtime::json_commands::resource_kind_from_resource_id`].
13267///
13268/// # Returns
13269///
13270/// Returns [`Some`] when [`crate::runtime::json_commands::resource_kind_from_resource_id`] can produce a value and [`None`] otherwise.
13271///
13272/// # Performance
13273///
13274/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::resource_kind_from_resource_id`].
13275///
13276/// # Examples
13277///
13278/// ```rust,ignore
13279/// let _result = pharos_native::runtime::json_commands::resource_kind_from_resource_id(todo!());
13280/// ```
13281///
13282/// # Related
13283///
13284/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13285pub fn resource_kind_from_resource_id(resource_id: &str) -> Option<&'static str> {
13286 let prefix = resource_id.split(':').next()?;
13287 match prefix {
13288 "user" => Some("user"),
13289 "business" => Some("business"),
13290 "service" => Some("service"),
13291 "technician" => Some("technician"),
13292 "appointment" => Some("appointment"),
13293 "tenant" => Some("tenant"),
13294 _ => None,
13295 }
13296}
13297
13298/// Compares two surface values for stable sorting.
13299///
13300/// # Parameters
13301///
13302/// - `left`: Input consumed by [`crate::runtime::json_commands::surface_compare_sort_values`].
13303/// - `right`: Input consumed by [`crate::runtime::json_commands::surface_compare_sort_values`].
13304///
13305/// # Returns
13306///
13307/// Returns the computed value produced by [`crate::runtime::json_commands::surface_compare_sort_values`].
13308///
13309/// # Examples
13310///
13311/// ```rust,ignore
13312/// let _result = pharos_native::runtime::json_commands::surface_compare_sort_values(todo!(), todo!());
13313/// ```
13314///
13315/// # Related
13316///
13317/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13318pub fn surface_compare_sort_values(left: &Value, right: &Value) -> std::cmp::Ordering {
13319 value_to_text(left).cmp(&value_to_text(right))
13320}
13321
13322/// Finds one form declaration by its id.
13323///
13324/// # Parameters
13325///
13326/// - `root`: Input consumed by [`crate::runtime::json_commands::surface_find_form`].
13327/// - `form_id`: Input consumed by [`crate::runtime::json_commands::surface_find_form`].
13328///
13329/// # Returns
13330///
13331/// Returns [`Some`] when [`crate::runtime::json_commands::surface_find_form`] can produce a value and [`None`] otherwise.
13332///
13333/// # Performance
13334///
13335/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::surface_find_form`].
13336///
13337/// # Examples
13338///
13339/// ```rust,ignore
13340/// let _result = pharos_native::runtime::json_commands::surface_find_form(std::path::Path::new("."), "form");
13341/// ```
13342///
13343/// # Related
13344///
13345/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13346pub fn surface_find_form(root: &Value, form_id: &str) -> Option<String> {
13347 root.get("forms")?
13348 .as_array()?
13349 .iter()
13350 .find(|form| form.get("form_id").and_then(Value::as_str) == Some(form_id))
13351 .and_then(compact)
13352}
13353
13354/// Returns the first position of a byte needle in a bounded byte slice.
13355///
13356/// # Parameters
13357///
13358/// - `haystack`: Input consumed by [`crate::runtime::json_commands::memmem`].
13359/// - `needle`: Input consumed by [`crate::runtime::json_commands::memmem`].
13360///
13361/// # Returns
13362///
13363/// Returns [`Some`] when [`crate::runtime::json_commands::memmem`] can produce a value and [`None`] otherwise.
13364///
13365/// # Examples
13366///
13367/// ```rust,ignore
13368/// let _result = pharos_native::runtime::json_commands::memmem(todo!(), todo!());
13369/// ```
13370///
13371/// # Related
13372///
13373/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13374pub fn memmem(haystack: &[u8], needle: &[u8]) -> Option<usize> {
13375 if needle.is_empty() {
13376 return Some(0);
13377 }
13378 haystack
13379 .windows(needle.len())
13380 .position(|window| window == needle)
13381}
13382
13383/// Renders one generated-surface action link from an app-owned fragment.
13384///
13385/// # Parameters
13386///
13387/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_action_link`].
13388/// - `action_path`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_action_link`].
13389/// - `label`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_action_link`].
13390///
13391/// # Returns
13392///
13393/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_surface_action_link`] can produce a value and [`None`] otherwise.
13394///
13395/// # Examples
13396///
13397/// ```rust,ignore
13398/// let _result = pharos_native::runtime::json_commands::render_generated_surface_action_link(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
13399/// ```
13400///
13401/// # Related
13402///
13403/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13404pub fn render_generated_surface_action_link(
13405 app_root: &Path,
13406 action_path: &str,
13407 label: &str,
13408) -> Option<String> {
13409 let params = Map::from_iter([
13410 (
13411 "action_path".to_owned(),
13412 Value::String(action_path.to_owned()),
13413 ),
13414 ("button_label".to_owned(), Value::String(label.to_owned())),
13415 (
13416 "button_class".to_owned(),
13417 Value::String("btn-secondary".to_owned()),
13418 ),
13419 ]);
13420 generated::render_generated_admin_fragment_from_value_runtime(
13421 app_root,
13422 "templates/shared/fragments/action_link.html",
13423 ¶ms,
13424 )
13425}
13426
13427/// Renders one generated-surface delete form from a validated action path.
13428///
13429/// # Parameters
13430///
13431/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_delete_form`].
13432/// - `action_path`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_delete_form`].
13433/// - `label`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_delete_form`].
13434///
13435/// # Returns
13436///
13437/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_surface_delete_form`] can produce a value and [`None`] otherwise.
13438///
13439/// # Examples
13440///
13441/// ```rust,ignore
13442/// let _result = pharos_native::runtime::json_commands::render_generated_surface_delete_form(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
13443/// ```
13444///
13445/// # Related
13446///
13447/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13448pub fn render_generated_surface_delete_form(
13449 app_root: &Path,
13450 action_path: &str,
13451 label: &str,
13452) -> Option<String> {
13453 let params = Map::from_iter([
13454 (
13455 "action_path".to_owned(),
13456 Value::String(action_path.to_owned()),
13457 ),
13458 ("button_label".to_owned(), Value::String(label.to_owned())),
13459 ]);
13460 generated::render_generated_admin_fragment_from_value_runtime(
13461 app_root,
13462 "templates/generated_admin/fragments/delete_form.html",
13463 ¶ms,
13464 )
13465}
13466
13467/// Renders a generated-surface table header cell.
13468///
13469/// # Parameters
13470///
13471/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_header_cell`].
13472/// - `label`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_header_cell`].
13473///
13474/// # Returns
13475///
13476/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_surface_table_header_cell`] can produce a value and [`None`] otherwise.
13477///
13478/// # Examples
13479///
13480/// ```rust,ignore
13481/// let _result = pharos_native::runtime::json_commands::render_generated_surface_table_header_cell(std::path::Path::new("apps/dev_docs"), todo!());
13482/// ```
13483///
13484/// # Related
13485///
13486/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13487pub fn render_generated_surface_table_header_cell(app_root: &Path, label: &str) -> Option<String> {
13488 generated::render_generated_admin_fragment_from_value_runtime(
13489 app_root,
13490 "templates/generated_admin/fragments/table_header_cell.html",
13491 &Map::from_iter([("column_label".to_owned(), Value::String(label.to_owned()))]),
13492 )
13493}
13494
13495/// Renders a generated-surface table row cell.
13496///
13497/// # Parameters
13498///
13499/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_row_cell`].
13500/// - `value`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_row_cell`].
13501///
13502/// # Returns
13503///
13504/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_surface_table_row_cell`] can produce a value and [`None`] otherwise.
13505///
13506/// # Examples
13507///
13508/// ```rust,ignore
13509/// let _result = pharos_native::runtime::json_commands::render_generated_surface_table_row_cell(std::path::Path::new("apps/dev_docs"), "value");
13510/// ```
13511///
13512/// # Related
13513///
13514/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13515pub fn render_generated_surface_table_row_cell(app_root: &Path, value: &str) -> Option<String> {
13516 generated::render_generated_admin_fragment_from_value_runtime(
13517 app_root,
13518 "templates/generated_admin/fragments/table_row_cell.html",
13519 &Map::from_iter([("field_value".to_owned(), Value::String(value.to_owned()))]),
13520 )
13521}
13522
13523/// Renders the action cell of one generated-surface row.
13524///
13525/// # Parameters
13526///
13527/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_action_cell`].
13528/// - `edit_path`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_action_cell`].
13529/// - `delete_path`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_action_cell`].
13530///
13531/// # Returns
13532///
13533/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_surface_table_action_cell`] can produce a value and [`None`] otherwise.
13534///
13535/// # Examples
13536///
13537/// ```rust,ignore
13538/// let _result = pharos_native::runtime::json_commands::render_generated_surface_table_action_cell(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
13539/// ```
13540///
13541/// # Related
13542///
13543/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13544pub fn render_generated_surface_table_action_cell(
13545 app_root: &Path,
13546 edit_path: Option<&str>,
13547 delete_path: Option<&str>,
13548) -> Option<String> {
13549 let mut output = String::new();
13550 if let Some(path) = edit_path.filter(|value| !value.is_empty()) {
13551 output.push_str(&render_generated_surface_action_link(
13552 app_root, path, "Edit",
13553 )?);
13554 }
13555 if let Some(path) = delete_path.filter(|value| !value.is_empty()) {
13556 output.push_str(&render_generated_surface_delete_form(
13557 app_root, path, "Delete",
13558 )?);
13559 }
13560 Some(output)
13561}
13562
13563/// Renders one generated-surface table row from cell values.
13564///
13565/// # Parameters
13566///
13567/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_row`].
13568/// - `cells_html`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_row`].
13569/// - `action_cell_html`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_table_row`].
13570///
13571/// # Returns
13572///
13573/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_surface_table_row`] can produce a value and [`None`] otherwise.
13574///
13575/// # Examples
13576///
13577/// ```rust,ignore
13578/// let _result = pharos_native::runtime::json_commands::render_generated_surface_table_row(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
13579/// ```
13580///
13581/// # Related
13582///
13583/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13584pub fn render_generated_surface_table_row(
13585 app_root: &Path,
13586 cells_html: &str,
13587 action_cell_html: &str,
13588) -> Option<String> {
13589 generated::render_generated_admin_fragment_from_value_runtime(
13590 app_root,
13591 "templates/generated_admin/fragments/table_row.html",
13592 &Map::from_iter([
13593 (
13594 "cells_html".to_owned(),
13595 Value::String(cells_html.to_owned()),
13596 ),
13597 (
13598 "action_cell_html".to_owned(),
13599 Value::String(action_cell_html.to_owned()),
13600 ),
13601 ]),
13602 )
13603}
13604
13605/// Tests whether an array contains a positive integer.
13606///
13607/// # Parameters
13608///
13609/// - `values`: Input consumed by [`crate::runtime::json_commands::json_array_contains_long`].
13610/// - `wanted`: Input consumed by [`crate::runtime::json_commands::json_array_contains_long`].
13611///
13612/// # Returns
13613///
13614/// Returns `true` when the condition checked by [`crate::runtime::json_commands::json_array_contains_long`] holds and `false` otherwise.
13615///
13616/// # Performance
13617///
13618/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_array_contains_long`].
13619///
13620/// # Examples
13621///
13622/// ```rust,ignore
13623/// let _result = pharos_native::runtime::json_commands::json_array_contains_long(todo!(), todo!());
13624/// ```
13625///
13626/// # Related
13627///
13628/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13629pub fn json_array_contains_long(values: &Value, wanted: i64) -> bool {
13630 values
13631 .as_array()
13632 .is_some_and(|items| items.iter().any(|item| item.as_i64() == Some(wanted)))
13633}
13634
13635/// Returns the first positive integer in a payload.
13636///
13637/// # Parameters
13638///
13639/// - `value`: Input consumed by [`crate::runtime::json_commands::payload_first_long`].
13640///
13641/// # Returns
13642///
13643/// Returns [`Some`] when [`crate::runtime::json_commands::payload_first_long`] can produce a value and [`None`] otherwise.
13644///
13645/// # Performance
13646///
13647/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::payload_first_long`].
13648///
13649/// # Examples
13650///
13651/// ```rust,ignore
13652/// let _result = pharos_native::runtime::json_commands::payload_first_long("value");
13653/// ```
13654///
13655/// # Related
13656///
13657/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13658pub fn payload_first_long(value: &Value) -> Option<i64> {
13659 match value {
13660 Value::Number(value) => value.as_i64().filter(|value| *value > 0),
13661 Value::Array(items) => items.iter().find_map(payload_first_long),
13662 Value::Object(object) => object.values().find_map(payload_first_long),
13663 _ => None,
13664 }
13665}
13666
13667/// Tests one JSON value against a signed integer.
13668///
13669/// # Parameters
13670///
13671/// - `value`: Input consumed by [`crate::runtime::json_commands::value_long_equals`].
13672/// - `wanted`: Input consumed by [`crate::runtime::json_commands::value_long_equals`].
13673///
13674/// # Returns
13675///
13676/// Returns `true` when the condition checked by [`crate::runtime::json_commands::value_long_equals`] holds and `false` otherwise.
13677///
13678/// # Examples
13679///
13680/// ```rust,ignore
13681/// let _result = pharos_native::runtime::json_commands::value_long_equals("value", todo!());
13682/// ```
13683///
13684/// # Related
13685///
13686/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13687pub fn value_long_equals(value: &Value, wanted: i64) -> bool {
13688 value.as_i64() == Some(wanted)
13689}
13690
13691/// Returns the bounded weekday rank used by calendar sorting.
13692///
13693/// # Parameters
13694///
13695/// - `value`: Input consumed by [`crate::runtime::json_commands::weekday_rank`].
13696///
13697/// # Returns
13698///
13699/// Returns the computed value produced by [`crate::runtime::json_commands::weekday_rank`].
13700///
13701/// # Examples
13702///
13703/// ```rust,ignore
13704/// let _result = pharos_native::runtime::json_commands::weekday_rank("value");
13705/// ```
13706///
13707/// # Related
13708///
13709/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13710pub fn weekday_rank(value: &str) -> u8 {
13711 match value.to_ascii_lowercase().as_str() {
13712 "mon" | "monday" => 1,
13713 "tue" | "tuesday" => 2,
13714 "wed" | "wednesday" => 3,
13715 "thu" | "thursday" => 4,
13716 "fri" | "friday" => 5,
13717 "sat" | "saturday" => 6,
13718 "sun" | "sunday" => 7,
13719 _ => 0,
13720 }
13721}
13722
13723/// Performs a case-insensitive equality check.
13724///
13725/// # Parameters
13726///
13727/// - `left`: Input consumed by [`crate::runtime::json_commands::streq_casefold`].
13728/// - `right`: Input consumed by [`crate::runtime::json_commands::streq_casefold`].
13729///
13730/// # Returns
13731///
13732/// Returns `true` when the condition checked by [`crate::runtime::json_commands::streq_casefold`] holds and `false` otherwise.
13733///
13734/// # Examples
13735///
13736/// ```rust,ignore
13737/// let _result = pharos_native::runtime::json_commands::streq_casefold(todo!(), todo!());
13738/// ```
13739///
13740/// # Related
13741///
13742/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13743pub fn streq_casefold(left: &str, right: &str) -> bool {
13744 left.eq_ignore_ascii_case(right)
13745}
13746
13747/// Returns the JSON array for one state section.
13748///
13749/// # Parameters
13750///
13751/// - `root`: Input consumed by [`crate::runtime::json_commands::state_array`].
13752/// - `section`: Input consumed by [`crate::runtime::json_commands::state_array`].
13753///
13754/// # Returns
13755///
13756/// Returns [`Some`] when [`crate::runtime::json_commands::state_array`] can produce a value and [`None`] otherwise.
13757///
13758/// # Examples
13759///
13760/// ```rust,ignore
13761/// let _result = pharos_native::runtime::json_commands::state_array(std::path::Path::new("."), "items");
13762/// ```
13763///
13764/// # Related
13765///
13766/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13767pub fn state_array(root: &Value, section: &str) -> Option<Vec<Value>> {
13768 Some(bounded_state_array(root, section).to_vec())
13769}
13770
13771/// Matches a generated-surface context against a request context.
13772///
13773/// # Parameters
13774///
13775/// - `surface`: Input consumed by [`crate::runtime::json_commands::surface_context_match`].
13776/// - `context`: Input consumed by [`crate::runtime::json_commands::surface_context_match`].
13777///
13778/// # Returns
13779///
13780/// Returns `true` when the condition checked by [`crate::runtime::json_commands::surface_context_match`] holds and `false` otherwise.
13781///
13782/// # Examples
13783///
13784/// ```rust,ignore
13785/// let _result = pharos_native::runtime::json_commands::surface_context_match(todo!(), todo!());
13786/// ```
13787///
13788/// # Related
13789///
13790/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13791pub fn surface_context_match(surface: &Value, context: &Value) -> bool {
13792 surface
13793 .get("context_binding")
13794 .and_then(Value::as_object)
13795 .is_none_or(|binding| {
13796 binding
13797 .get("param")
13798 .and_then(Value::as_str)
13799 .is_none_or(|param| surface.get(param) == context.get(param))
13800 })
13801}
13802
13803/// Renders a generated-surface submit button through the app-owned fragment.
13804///
13805/// # Parameters
13806///
13807/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_submit_button`].
13808/// - `label`: Input consumed by [`crate::runtime::json_commands::render_generated_surface_submit_button`].
13809///
13810/// # Returns
13811///
13812/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_surface_submit_button`] can produce a value and [`None`] otherwise.
13813///
13814/// # Examples
13815///
13816/// ```rust,ignore
13817/// let _result = pharos_native::runtime::json_commands::render_generated_surface_submit_button(std::path::Path::new("apps/dev_docs"), todo!());
13818/// ```
13819///
13820/// # Related
13821///
13822/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13823pub fn render_generated_surface_submit_button(app_root: &Path, label: &str) -> Option<String> {
13824 generated::render_generated_admin_fragment_from_value_runtime(
13825 app_root,
13826 "templates/shared/fragments/submit_button.html",
13827 &Map::from_iter([("button_label".to_owned(), Value::String(label.to_owned()))]),
13828 )
13829}
13830
13831/// Tests whether a bounded integer array contains one value.
13832///
13833/// # Parameters
13834///
13835/// - `values`: Input consumed by [`crate::runtime::json_commands::long_in_array`].
13836/// - `wanted`: Input consumed by [`crate::runtime::json_commands::long_in_array`].
13837///
13838/// # Returns
13839///
13840/// Returns `true` when the condition checked by [`crate::runtime::json_commands::long_in_array`] holds and `false` otherwise.
13841///
13842/// # Examples
13843///
13844/// ```rust,ignore
13845/// let _result = pharos_native::runtime::json_commands::long_in_array(todo!(), todo!());
13846/// ```
13847///
13848/// # Related
13849///
13850/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13851pub fn long_in_array(values: &Value, wanted: i64) -> bool {
13852 json_array_contains_long(values, wanted)
13853}
13854
13855/// Decodes one URL query component with plus and percent handling.
13856///
13857/// # Parameters
13858///
13859/// - `value`: Input consumed by [`crate::runtime::json_commands::url_decode`].
13860///
13861/// # Returns
13862///
13863/// Returns [`Some`] when [`crate::runtime::json_commands::url_decode`] can produce a value and [`None`] otherwise.
13864///
13865/// # Examples
13866///
13867/// ```rust,ignore
13868/// let _result = pharos_native::runtime::json_commands::url_decode("value");
13869/// ```
13870///
13871/// # Related
13872///
13873/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13874pub fn url_decode(value: &str) -> Option<String> {
13875 url_decode_component(value)
13876}
13877
13878/// Matches one generated API/admin operation in a contract file.
13879///
13880/// # Parameters
13881///
13882/// - `file_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_api_match`].
13883/// - `method`: Input consumed by [`crate::runtime::json_commands::generated_surface_api_match`].
13884/// - `request_path`: Input consumed by [`crate::runtime::json_commands::generated_surface_api_match`].
13885///
13886/// # Returns
13887///
13888/// Returns [`Some`] when [`crate::runtime::json_commands::generated_surface_api_match`] can produce a value and [`None`] otherwise.
13889///
13890/// # Examples
13891///
13892/// ```rust,ignore
13893/// let _result = pharos_native::runtime::json_commands::generated_surface_api_match(todo!(), todo!(), "/");
13894/// ```
13895///
13896/// # Related
13897///
13898/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13899pub fn generated_surface_api_match(
13900 file_path: &Path,
13901 method: &str,
13902 request_path: &str,
13903) -> Option<String> {
13904 file_generated_surface_match(file_path, method, request_path)
13905}
13906
13907/// Releases an owned generated-surface match result.
13908///
13909/// # Parameters
13910///
13911/// - `value`: Input consumed by [`crate::runtime::json_commands::generated_surface_api_match_free`].
13912///
13913/// # Returns
13914///
13915/// Returns after [`crate::runtime::json_commands::generated_surface_api_match_free`] applies its in-place effect.
13916///
13917/// # Examples
13918///
13919/// ```rust,ignore
13920/// let _result = pharos_native::runtime::json_commands::generated_surface_api_match_free("value");
13921/// ```
13922///
13923/// # Related
13924///
13925/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13926pub fn generated_surface_api_match_free(value: &mut Option<String>) {
13927 value.take();
13928}
13929
13930/// Matches a dynamic dispatch request against an app-owned route contract.
13931///
13932/// # Parameters
13933///
13934/// - `file_path`: Input consumed by [`crate::runtime::json_commands::dynamic_dispatch_match`].
13935/// - `method`: Input consumed by [`crate::runtime::json_commands::dynamic_dispatch_match`].
13936/// - `request_path`: Input consumed by [`crate::runtime::json_commands::dynamic_dispatch_match`].
13937///
13938/// # Returns
13939///
13940/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_dispatch_match`] can produce a value and [`None`] otherwise.
13941///
13942/// # Examples
13943///
13944/// ```rust,ignore
13945/// let _result = pharos_native::runtime::json_commands::dynamic_dispatch_match(todo!(), todo!(), "/");
13946/// ```
13947///
13948/// # Related
13949///
13950/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13951pub fn dynamic_dispatch_match(
13952 file_path: &Path,
13953 method: &str,
13954 request_path: &str,
13955) -> Option<String> {
13956 generated_surface_api_match(file_path, method, request_path)
13957}
13958
13959/// Resolves a route fixture path below an app root without traversal.
13960///
13961/// # Parameters
13962///
13963/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_dispatch_resolve_route_fixture`].
13964/// - `relative_path`: Input consumed by [`crate::runtime::json_commands::dynamic_dispatch_resolve_route_fixture`].
13965///
13966/// # Returns
13967///
13968/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_dispatch_resolve_route_fixture`] can produce a value and [`None`] otherwise.
13969///
13970/// # Performance
13971///
13972/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_dispatch_resolve_route_fixture`].
13973///
13974/// # Examples
13975///
13976/// ```rust,ignore
13977/// let _result = pharos_native::runtime::json_commands::dynamic_dispatch_resolve_route_fixture(std::path::Path::new("apps/dev_docs"), "page");
13978/// ```
13979///
13980/// # Related
13981///
13982/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
13983pub fn dynamic_dispatch_resolve_route_fixture(
13984 app_root: &Path,
13985 relative_path: &str,
13986) -> Option<String> {
13987 if app_root.as_os_str().is_empty()
13988 || relative_path.is_empty()
13989 || Path::new(relative_path).is_absolute()
13990 || relative_path.split('/').any(|segment| segment == "..")
13991 {
13992 return None;
13993 }
13994 let path = app_root.join(relative_path);
13995 path.is_file().then(|| path.to_string_lossy().into_owned())
13996}
13997
13998/// Reads a bounded response file below an app artifact root.
13999///
14000/// # Parameters
14001///
14002/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_dispatch_response_file`].
14003/// - `relative_path`: Input consumed by [`crate::runtime::json_commands::dynamic_dispatch_response_file`].
14004///
14005/// # Returns
14006///
14007/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_dispatch_response_file`] can produce a value and [`None`] otherwise.
14008///
14009/// # Performance
14010///
14011/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_dispatch_response_file`].
14012///
14013/// # Examples
14014///
14015/// ```rust,ignore
14016/// let _result = pharos_native::runtime::json_commands::dynamic_dispatch_response_file(std::path::Path::new("apps/dev_docs"), "page");
14017/// ```
14018///
14019/// # Related
14020///
14021/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14022pub fn dynamic_dispatch_response_file(app_root: &Path, relative_path: &str) -> Option<String> {
14023 let path = dynamic_dispatch_resolve_route_fixture(app_root, relative_path)?;
14024 let text = fs::read_to_string(path).ok()?;
14025 (text.len() <= MAX_TEXT_BYTES).then_some(text)
14026}
14027
14028/// Recognizes static media and static asset request paths.
14029///
14030/// # Parameters
14031///
14032/// - `path`: Input consumed by [`crate::runtime::json_commands::dynamic_media_is_static_path`].
14033///
14034/// # Returns
14035///
14036/// Returns `true` when the condition checked by [`crate::runtime::json_commands::dynamic_media_is_static_path`] holds and `false` otherwise.
14037///
14038/// # Examples
14039///
14040/// ```rust,ignore
14041/// let _result = pharos_native::runtime::json_commands::dynamic_media_is_static_path(std::path::Path::new("fixture.json"));
14042/// ```
14043///
14044/// # Related
14045///
14046/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14047pub fn dynamic_media_is_static_path(path: &str) -> bool {
14048 path.starts_with("/static/") || path.starts_with("/media/")
14049}
14050
14051/// Extracts the query component without decoding its individual values.
14052///
14053/// # Parameters
14054///
14055/// - `target`: Input consumed by [`crate::runtime::json_commands::extract_query`].
14056///
14057/// # Returns
14058///
14059/// Returns the computed value produced by [`crate::runtime::json_commands::extract_query`].
14060///
14061/// # Performance
14062///
14063/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::extract_query`].
14064///
14065/// # Examples
14066///
14067/// ```rust,ignore
14068/// let _result = pharos_native::runtime::json_commands::extract_query("/");
14069/// ```
14070///
14071/// # Related
14072///
14073/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14074pub fn extract_query(target: &str) -> String {
14075 target
14076 .split_once('?')
14077 .map(|(_, query)| query.split('#').next().unwrap_or_default().to_owned())
14078 .unwrap_or_default()
14079}
14080
14081/// Handles a framework webhook request using the shared endpoint contract.
14082///
14083/// # Parameters
14084///
14085/// - `root`: Input consumed by [`crate::runtime::json_commands::handle_framework_webhook_request`].
14086/// - `endpoint_id`: Input consumed by [`crate::runtime::json_commands::handle_framework_webhook_request`].
14087///
14088/// # Returns
14089///
14090/// Returns [`Some`] when [`crate::runtime::json_commands::handle_framework_webhook_request`] can produce a value and [`None`] otherwise.
14091///
14092/// # Performance
14093///
14094/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::handle_framework_webhook_request`].
14095///
14096/// # Examples
14097///
14098/// ```rust,ignore
14099/// let _result = pharos_native::runtime::json_commands::handle_framework_webhook_request(std::path::Path::new("."), "webhook");
14100/// ```
14101///
14102/// # Related
14103///
14104/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14105pub fn handle_framework_webhook_request(root: &Value, endpoint_id: &str) -> Option<String> {
14106 if endpoint_id.is_empty() {
14107 return None;
14108 }
14109 bounded_state_array(root, "webhook_events")
14110 .iter()
14111 .find(|row| row.get("endpoint_id").and_then(Value::as_str) == Some(endpoint_id))
14112 .and_then(compact)
14113}
14114
14115/// Handles one generated-surface API request from a route-match document.
14116///
14117/// # Parameters
14118///
14119/// - `match_json`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_api_request`].
14120/// - `state_root`: Input consumed by [`crate::runtime::json_commands::handle_generated_surface_api_request`].
14121///
14122/// # Returns
14123///
14124/// Returns [`Some`] when [`crate::runtime::json_commands::handle_generated_surface_api_request`] can produce a value and [`None`] otherwise.
14125///
14126/// # Performance
14127///
14128/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::handle_generated_surface_api_request`].
14129///
14130/// # Examples
14131///
14132/// ```rust,ignore
14133/// let _result = pharos_native::runtime::json_commands::handle_generated_surface_api_request(todo!(), todo!());
14134/// ```
14135///
14136/// # Related
14137///
14138/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14139pub fn handle_generated_surface_api_request(
14140 match_json: &str,
14141 state_root: &Value,
14142) -> Option<String> {
14143 let matched = serde_json::from_str::<Value>(match_json).ok()?;
14144 let operation = matched.get("operation").and_then(Value::as_str)?;
14145 compact(&serde_json::json!({"operation": operation, "state": state_root}))
14146}
14147
14148/// Handles one page route by returning its declared template path.
14149///
14150/// # Parameters
14151///
14152/// - `app_root`: Input consumed by [`crate::runtime::json_commands::handle_page_route`].
14153/// - `page_id`: Input consumed by [`crate::runtime::json_commands::handle_page_route`].
14154///
14155/// # Returns
14156///
14157/// Returns [`Some`] when [`crate::runtime::json_commands::handle_page_route`] can produce a value and [`None`] otherwise.
14158///
14159/// # Examples
14160///
14161/// ```rust,ignore
14162/// let _result = pharos_native::runtime::json_commands::handle_page_route(std::path::Path::new("apps/dev_docs"), "home");
14163/// ```
14164///
14165/// # Related
14166///
14167/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14168pub fn handle_page_route(app_root: &Path, page_id: &str) -> Option<String> {
14169 declared_page_relative_path(app_root, page_id)
14170}
14171
14172/// Handles one action route by returning its declared operation path.
14173///
14174/// # Parameters
14175///
14176/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::handle_action_route`].
14177/// - `operation`: Input consumed by [`crate::runtime::json_commands::handle_action_route`].
14178///
14179/// # Returns
14180///
14181/// Returns [`Some`] when [`crate::runtime::json_commands::handle_action_route`] can produce a value and [`None`] otherwise.
14182///
14183/// # Performance
14184///
14185/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::handle_action_route`].
14186///
14187/// # Examples
14188///
14189/// ```rust,ignore
14190/// let _result = pharos_native::runtime::json_commands::handle_action_route("{}", todo!());
14191/// ```
14192///
14193/// # Related
14194///
14195/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14196pub fn handle_action_route(surface_json: &str, operation: &str) -> Option<String> {
14197 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
14198 surface
14199 .get("operations")?
14200 .get(operation)?
14201 .get("path")
14202 .and_then(Value::as_str)
14203 .map(ToOwned::to_owned)
14204}
14205
14206/// Removes query and fragment components from a route request path.
14207///
14208/// # Parameters
14209///
14210/// - `target`: Input consumed by [`crate::runtime::json_commands::route_request_path`].
14211///
14212/// # Returns
14213///
14214/// Returns [`Some`] when [`crate::runtime::json_commands::route_request_path`] can produce a value and [`None`] otherwise.
14215///
14216/// # Examples
14217///
14218/// ```rust,ignore
14219/// let _result = pharos_native::runtime::json_commands::route_request_path("/");
14220/// ```
14221///
14222/// # Related
14223///
14224/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14225pub fn route_request_path(target: &str) -> Option<String> {
14226 super::http::request_path_without_query(target)
14227}
14228
14229/// Performs safe contract relative path.
14230fn safe_contract_relative_path(root: &Path, relative: &str) -> Option<PathBuf> {
14231 let path = Path::new(relative);
14232 if relative.is_empty() || path.is_absolute() {
14233 return None;
14234 }
14235 if path.components().any(|component| {
14236 matches!(
14237 component,
14238 Component::ParentDir | Component::RootDir | Component::Prefix(_)
14239 )
14240 }) {
14241 return None;
14242 }
14243 Some(root.join(path))
14244}
14245
14246/// Computes a lowercase SHA-256 digest without invoking an external process.
14247///
14248/// # Parameters
14249///
14250/// - `value`: Input consumed by [`crate::runtime::json_commands::migration_sha256_text`].
14251///
14252/// # Returns
14253///
14254/// Returns the computed value produced by [`crate::runtime::json_commands::migration_sha256_text`].
14255///
14256/// # Performance
14257///
14258/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::migration_sha256_text`].
14259///
14260/// # Examples
14261///
14262/// ```rust,ignore
14263/// let _result = pharos_native::runtime::json_commands::migration_sha256_text("value");
14264/// ```
14265///
14266/// # Related
14267///
14268/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14269pub fn migration_sha256_text(value: &str) -> String {
14270 let digest = Sha256::digest(value.as_bytes());
14271 digest.iter().map(|byte| format!("{byte:02x}")).collect()
14272}
14273
14274/// Returns a JSON literal string, preserving the caller-supplied literal text.
14275///
14276/// # Parameters
14277///
14278/// - `value`: Input consumed by [`crate::runtime::json_commands::json_literal`].
14279///
14280/// # Returns
14281///
14282/// Returns the computed value produced by [`crate::runtime::json_commands::json_literal`].
14283///
14284/// # Examples
14285///
14286/// ```rust,ignore
14287/// let _result = pharos_native::runtime::json_commands::json_literal("value");
14288/// ```
14289///
14290/// # Related
14291///
14292/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14293pub fn json_literal(value: &str) -> String {
14294 value.to_owned()
14295}
14296
14297/// Serializes an optional string as JSON, using the JSON null literal when absent.
14298///
14299/// # Parameters
14300///
14301/// - `value`: Input consumed by [`crate::runtime::json_commands::json_nullable_string`].
14302///
14303/// # Returns
14304///
14305/// Returns the computed value produced by [`crate::runtime::json_commands::json_nullable_string`].
14306///
14307/// # Examples
14308///
14309/// ```rust,ignore
14310/// let _result = pharos_native::runtime::json_commands::json_nullable_string("value");
14311/// ```
14312///
14313/// # Related
14314///
14315/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14316pub fn json_nullable_string(value: Option<&str>) -> String {
14317 value
14318 .filter(|text| !text.is_empty())
14319 .map(|text| serde_json::to_string(text).unwrap_or_else(|_| "null".to_owned()))
14320 .unwrap_or_else(|| "null".to_owned())
14321}
14322
14323/// Serializes a JSON value or returns a validated fallback literal.
14324///
14325/// # Parameters
14326///
14327/// - `value`: Input consumed by [`crate::runtime::json_commands::json_value_or_literal`].
14328/// - `fallback`: Input consumed by [`crate::runtime::json_commands::json_value_or_literal`].
14329///
14330/// # Returns
14331///
14332/// Returns the computed value produced by [`crate::runtime::json_commands::json_value_or_literal`].
14333///
14334/// # Examples
14335///
14336/// ```rust,ignore
14337/// let _result = pharos_native::runtime::json_commands::json_value_or_literal("value", todo!());
14338/// ```
14339///
14340/// # Related
14341///
14342/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14343pub fn json_value_or_literal(value: Option<&Value>, fallback: &str) -> String {
14344 value
14345 .and_then(|item| serde_json::to_string(item).ok())
14346 .unwrap_or_else(|| fallback.to_owned())
14347}
14348
14349/// Serializes an object string field with a plain-string fallback.
14350///
14351/// # Parameters
14352///
14353/// - `value`: Input consumed by [`crate::runtime::json_commands::json_object_string_json`].
14354/// - `key`: Input consumed by [`crate::runtime::json_commands::json_object_string_json`].
14355/// - `fallback`: Input consumed by [`crate::runtime::json_commands::json_object_string_json`].
14356///
14357/// # Returns
14358///
14359/// Returns the computed value produced by [`crate::runtime::json_commands::json_object_string_json`].
14360///
14361/// # Examples
14362///
14363/// ```rust,ignore
14364/// let _result = pharos_native::runtime::json_commands::json_object_string_json("value", "key", todo!());
14365/// ```
14366///
14367/// # Related
14368///
14369/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14370pub fn json_object_string_json(value: &Value, key: &str, fallback: &str) -> String {
14371 let text = value.get(key).and_then(Value::as_str).unwrap_or(fallback);
14372 serde_json::to_string(text).unwrap_or_else(|_| "\"\"".to_owned())
14373}
14374
14375/// Serializes an integer as JSON number text.
14376///
14377/// # Parameters
14378///
14379/// - `value`: Input consumed by [`crate::runtime::json_commands::long_json`].
14380///
14381/// # Returns
14382///
14383/// Returns the computed value produced by [`crate::runtime::json_commands::long_json`].
14384///
14385/// # Examples
14386///
14387/// ```rust,ignore
14388/// let _result = pharos_native::runtime::json_commands::long_json("value");
14389/// ```
14390///
14391/// # Related
14392///
14393/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14394pub fn long_json(value: i64) -> String {
14395 value.to_string()
14396}
14397
14398/// Appends one serialized item to a JSON array under construction.
14399///
14400/// # Parameters
14401///
14402/// - `buffer`: Input consumed by [`crate::runtime::json_commands::append_json_item`].
14403/// - `json_item`: Input consumed by [`crate::runtime::json_commands::append_json_item`].
14404/// - `first`: Input consumed by [`crate::runtime::json_commands::append_json_item`].
14405///
14406/// # Returns
14407///
14408/// Returns `true` when the condition checked by [`crate::runtime::json_commands::append_json_item`] holds and `false` otherwise.
14409///
14410/// # Examples
14411///
14412/// ```rust,ignore
14413/// let _result = pharos_native::runtime::json_commands::append_json_item(&mut [], todo!(), todo!());
14414/// ```
14415///
14416/// # Related
14417///
14418/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14419pub fn append_json_item(buffer: &mut String, json_item: &str, first: &mut bool) -> bool {
14420 if json_item.is_empty() {
14421 return false;
14422 }
14423 if *first {
14424 buffer.push('[');
14425 *first = false;
14426 } else if buffer.is_empty() || buffer.ends_with(']') {
14427 return false;
14428 } else {
14429 buffer.push(',');
14430 }
14431 buffer.push_str(json_item);
14432 true
14433}
14434
14435/// Serializes and appends one string item to a JSON array under construction.
14436///
14437/// # Parameters
14438///
14439/// - `buffer`: Input consumed by [`crate::runtime::json_commands::append_json_string_item`].
14440/// - `value`: Input consumed by [`crate::runtime::json_commands::append_json_string_item`].
14441/// - `first`: Input consumed by [`crate::runtime::json_commands::append_json_string_item`].
14442///
14443/// # Returns
14444///
14445/// Returns `true` when the condition checked by [`crate::runtime::json_commands::append_json_string_item`] holds and `false` otherwise.
14446///
14447/// # Examples
14448///
14449/// ```rust,ignore
14450/// let _result = pharos_native::runtime::json_commands::append_json_string_item(&mut [], "value", todo!());
14451/// ```
14452///
14453/// # Related
14454///
14455/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14456pub fn append_json_string_item(buffer: &mut String, value: Option<&str>, first: &mut bool) -> bool {
14457 let item = serde_json::to_string(value.unwrap_or_default()).ok();
14458 item.as_deref()
14459 .is_some_and(|json| append_json_item(buffer, json, first))
14460}
14461
14462/// Completes a JSON array under construction.
14463///
14464/// # Parameters
14465///
14466/// - `buffer`: Input consumed by [`crate::runtime::json_commands::finish_json_array`].
14467/// - `first`: Input consumed by [`crate::runtime::json_commands::finish_json_array`].
14468///
14469/// # Returns
14470///
14471/// Returns [`Some`] when [`crate::runtime::json_commands::finish_json_array`] can produce a value and [`None`] otherwise.
14472///
14473/// # Examples
14474///
14475/// ```rust,ignore
14476/// let _result = pharos_native::runtime::json_commands::finish_json_array(&mut [], todo!());
14477/// ```
14478///
14479/// # Related
14480///
14481/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14482pub fn finish_json_array(buffer: &mut String, first: bool) -> Option<String> {
14483 if first {
14484 return Some("[]".to_owned());
14485 }
14486 if !buffer.starts_with('[') || buffer.ends_with(']') {
14487 return None;
14488 }
14489 buffer.push(']');
14490 Some(buffer.clone())
14491}
14492
14493/// Checks a seed row count against exact and minimum expectations.
14494///
14495/// # Parameters
14496///
14497/// - `row_count`: Observed seeded row count.
14498/// - `minimum_rows`: Optional minimum row threshold to enforce.
14499/// - `exact_rows`: Optional exact row count to enforce.
14500///
14501/// # Returns
14502///
14503/// Returns the successful value produced by [`crate::runtime::json_commands::seed_expectation_satisfied`] or an error when the operation fails.
14504///
14505/// # Errors
14506///
14507/// Returns an error value when [`crate::runtime::json_commands::seed_expectation_satisfied`] cannot complete successfully.
14508///
14509/// # Examples
14510///
14511/// ```rust,ignore
14512/// let _result = pharos_native::runtime::json_commands::seed_expectation_satisfied(todo!(), todo!(), todo!(), todo!());
14513/// ```
14514///
14515/// # Related
14516///
14517/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14518pub fn seed_expectation_satisfied(
14519 row_count: i64,
14520 minimum_rows: Option<i64>,
14521 exact_rows: Option<i64>,
14522) -> Result<(), &'static str> {
14523 if exact_rows.is_some_and(|expected| row_count != expected) {
14524 return Err("exact_rows_mismatch");
14525 }
14526 if minimum_rows.is_some_and(|minimum| row_count < minimum) {
14527 return Err("minimum_rows_not_met");
14528 }
14529 Ok(())
14530}
14531
14532/// Checks whether an applied migration checksum matches any accepted checksum.
14533///
14534/// # Parameters
14535///
14536/// - `applied`: Input consumed by [`crate::runtime::json_commands::migration_checksum_matches`].
14537/// - `applied_checksum`: Input consumed by [`crate::runtime::json_commands::migration_checksum_matches`].
14538/// - `expected_checksum`: Input consumed by [`crate::runtime::json_commands::migration_checksum_matches`].
14539///
14540/// # Returns
14541///
14542/// Returns `true` when the condition checked by [`crate::runtime::json_commands::migration_checksum_matches`] holds and `false` otherwise.
14543///
14544/// # Examples
14545///
14546/// ```rust,ignore
14547/// let _result = pharos_native::runtime::json_commands::migration_checksum_matches(todo!(), todo!(), todo!());
14548/// ```
14549///
14550/// # Related
14551///
14552/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14553pub fn migration_checksum_matches(
14554 applied: bool,
14555 applied_checksum: Option<&str>,
14556 expected_checksum: Option<&str>,
14557) -> bool {
14558 let Some(applied_checksum) = applied_checksum.filter(|value| !value.is_empty()) else {
14559 return false;
14560 };
14561 if !applied {
14562 return false;
14563 }
14564 expected_checksum == Some(applied_checksum)
14565}
14566
14567/// Returns a migration contract path declared by an app manifest.
14568///
14569/// # Parameters
14570///
14571/// - `app_root`: Input consumed by [`crate::runtime::json_commands::migration_fixture_path_dup_runtime_local`].
14572///
14573/// # Returns
14574///
14575/// Returns [`Some`] when [`crate::runtime::json_commands::migration_fixture_path_dup_runtime_local`] can produce a value and [`None`] otherwise.
14576///
14577/// # Examples
14578///
14579/// ```rust,ignore
14580/// let _result = pharos_native::runtime::json_commands::migration_fixture_path_dup_runtime_local(std::path::Path::new("apps/dev_docs"));
14581/// ```
14582///
14583/// # Related
14584///
14585/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14586pub fn migration_fixture_path_dup_runtime_local(app_root: &Path) -> Option<PathBuf> {
14587 let relative = manifest_database_contract_path(app_root)?;
14588 safe_contract_relative_path(app_root, &relative)
14589}
14590
14591/// Returns a backend record from an array of JSON objects by exact string field.
14592///
14593/// # Parameters
14594///
14595/// - `array`: Input consumed by [`crate::runtime::json_commands::json_find_object_by_string_field`].
14596/// - `field_name`: Input consumed by [`crate::runtime::json_commands::json_find_object_by_string_field`].
14597/// - `expected_value`: Input consumed by [`crate::runtime::json_commands::json_find_object_by_string_field`].
14598///
14599/// # Returns
14600///
14601/// Returns [`Some`] when [`crate::runtime::json_commands::json_find_object_by_string_field`] can produce a value and [`None`] otherwise.
14602///
14603/// # Performance
14604///
14605/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_find_object_by_string_field`].
14606///
14607/// # Examples
14608///
14609/// ```rust,ignore
14610/// let _result = pharos_native::runtime::json_commands::json_find_object_by_string_field(todo!(), todo!(), todo!());
14611/// ```
14612///
14613/// # Related
14614///
14615/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14616pub fn json_find_object_by_string_field<'a>(
14617 array: &'a Value,
14618 field_name: &str,
14619 expected_value: &str,
14620) -> Option<&'a Value> {
14621 array
14622 .as_array()?
14623 .iter()
14624 .find(|item| item.get(field_name).and_then(Value::as_str) == Some(expected_value))
14625}
14626
14627/// Finds a migration record by its stable migration id.
14628///
14629/// # Parameters
14630///
14631/// - `root`: Input consumed by [`crate::runtime::json_commands::json_find_migration_by_id`].
14632/// - `migration_id`: Input consumed by [`crate::runtime::json_commands::json_find_migration_by_id`].
14633///
14634/// # Returns
14635///
14636/// Returns [`Some`] when [`crate::runtime::json_commands::json_find_migration_by_id`] can produce a value and [`None`] otherwise.
14637///
14638/// # Examples
14639///
14640/// ```rust,ignore
14641/// let _result = pharos_native::runtime::json_commands::json_find_migration_by_id(std::path::Path::new("."), "001_init");
14642/// ```
14643///
14644/// # Related
14645///
14646/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14647pub fn json_find_migration_by_id<'a>(root: &'a Value, migration_id: &str) -> Option<&'a Value> {
14648 json_find_object_by_string_field(root.get("migrations")?, "migration_id", migration_id)
14649}
14650
14651/// Computes a bounded content signature for relational state synchronization.
14652///
14653/// # Parameters
14654///
14655/// - `path`: Input consumed by [`crate::runtime::json_commands::dynamic_relational_file_signature`].
14656///
14657/// # Returns
14658///
14659/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_relational_file_signature`] can produce a value and [`None`] otherwise.
14660///
14661/// # Performance
14662///
14663/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_relational_file_signature`].
14664///
14665/// # Examples
14666///
14667/// ```rust,ignore
14668/// let _result = pharos_native::runtime::json_commands::dynamic_relational_file_signature(std::path::Path::new("fixture.json"));
14669/// ```
14670///
14671/// # Related
14672///
14673/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14674pub fn dynamic_relational_file_signature(path: &Path) -> Option<String> {
14675 if path.as_os_str().is_empty() {
14676 return None;
14677 }
14678 if !path.exists() {
14679 return Some("missing".to_owned());
14680 }
14681 let bytes = fs::read(path).ok()?;
14682 let mut hash = 1469598103934665603u64;
14683 for byte in &bytes {
14684 hash ^= u64::from(*byte);
14685 hash = hash.wrapping_mul(1099511628211);
14686 }
14687 Some(format!("{hash:016x}:{}", bytes.len()))
14688}
14689
14690/// Emits a generated-surface schema response from contract JSON.
14691///
14692/// # Parameters
14693///
14694/// - `surface_json`: Input consumed by [`crate::runtime::json_commands::write_generated_surface_schema_response`].
14695///
14696/// # Returns
14697///
14698/// Returns [`Some`] when [`crate::runtime::json_commands::write_generated_surface_schema_response`] can produce a value and [`None`] otherwise.
14699///
14700/// # Performance
14701///
14702/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::write_generated_surface_schema_response`].
14703///
14704/// # Examples
14705///
14706/// ```rust,ignore
14707/// let _result = pharos_native::runtime::json_commands::write_generated_surface_schema_response("{}");
14708/// ```
14709///
14710/// # Related
14711///
14712/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14713pub fn write_generated_surface_schema_response(surface_json: &str) -> Option<String> {
14714 let surface = serde_json::from_str::<Value>(surface_json).ok()?;
14715 compact(surface.get("schema").unwrap_or(&surface))
14716}
14717
14718/// Renders a bounded path template with explicit placeholder replacement.
14719///
14720/// # Parameters
14721///
14722/// - `template`: Input consumed by [`crate::runtime::json_commands::render_path_template_text`].
14723/// - `values`: Input consumed by [`crate::runtime::json_commands::render_path_template_text`].
14724///
14725/// # Returns
14726///
14727/// Returns [`Some`] when [`crate::runtime::json_commands::render_path_template_text`] can produce a value and [`None`] otherwise.
14728///
14729/// # Performance
14730///
14731/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::render_path_template_text`].
14732///
14733/// # Examples
14734///
14735/// ```rust,ignore
14736/// let _result = pharos_native::runtime::json_commands::render_path_template_text(todo!(), todo!());
14737/// ```
14738///
14739/// # Related
14740///
14741/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14742pub fn render_path_template_text(template: &str, values: &Value) -> Option<String> {
14743 if template.len() > 64 * 1024 {
14744 return None;
14745 }
14746 let mut output = template.to_owned();
14747 if let Some(map) = values.as_object() {
14748 for (key, value) in map {
14749 let text = value
14750 .as_str()
14751 .or_else(|| {
14752 value
14753 .as_i64()
14754 .map(|number| Box::leak(number.to_string().into_boxed_str()) as &str)
14755 })
14756 .unwrap_or_default();
14757 output = output.replace(&format!("{{{key}}}"), text);
14758 }
14759 }
14760 Some(output)
14761}
14762
14763/// Serializes one JSON value using the compact runtime policy.
14764///
14765/// # Parameters
14766///
14767/// - `value`: Input consumed by [`crate::runtime::json_commands::json_value_tostring`].
14768///
14769/// # Returns
14770///
14771/// Returns [`Some`] when [`crate::runtime::json_commands::json_value_tostring`] can produce a value and [`None`] otherwise.
14772///
14773/// # Examples
14774///
14775/// ```rust,ignore
14776/// let _result = pharos_native::runtime::json_commands::json_value_tostring("value");
14777/// ```
14778///
14779/// # Related
14780///
14781/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14782pub fn json_value_tostring(value: &Value) -> Option<String> {
14783 serde_json::to_string(value).ok()
14784}
14785
14786/// Resolves the declared deletion strategy with a safe default.
14787///
14788/// # Parameters
14789///
14790/// - `surface`: Input consumed by [`crate::runtime::json_commands::generated_surface_delete_strategy`].
14791///
14792/// # Returns
14793///
14794/// Returns the computed value produced by [`crate::runtime::json_commands::generated_surface_delete_strategy`].
14795///
14796/// # Examples
14797///
14798/// ```rust,ignore
14799/// let _result = pharos_native::runtime::json_commands::generated_surface_delete_strategy(todo!());
14800/// ```
14801///
14802/// # Related
14803///
14804/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14805pub fn generated_surface_delete_strategy(surface: &Value) -> String {
14806 surface
14807 .get("delete_strategy")
14808 .and_then(Value::as_str)
14809 .filter(|value| matches!(*value, "soft" | "hard" | "none"))
14810 .unwrap_or("soft")
14811 .to_owned()
14812}
14813
14814/// Renders a public-state button fragment with HTML-escaped business context.
14815///
14816/// # Parameters
14817///
14818/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_render_business_admin_public_state_button_html`].
14819/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_render_business_admin_public_state_button_html`].
14820/// - `is_public`: Input consumed by [`crate::runtime::json_commands::dynamic_render_business_admin_public_state_button_html`].
14821///
14822/// # Returns
14823///
14824/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_render_business_admin_public_state_button_html`] can produce a value and [`None`] otherwise.
14825///
14826/// # Examples
14827///
14828/// ```rust,ignore
14829/// let _result = pharos_native::runtime::json_commands::dynamic_render_business_admin_public_state_button_html(std::path::Path::new("apps/dev_docs"), "demo", todo!());
14830/// ```
14831///
14832/// # Related
14833///
14834/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14835pub fn dynamic_render_business_admin_public_state_button_html(
14836 app_root: &Path,
14837 business_slug: &str,
14838 is_public: bool,
14839) -> Option<String> {
14840 if app_root.as_os_str().is_empty()
14841 || business_slug.is_empty()
14842 || business_slug.contains(['/', '\\'])
14843 {
14844 return None;
14845 }
14846 let button_label = if is_public { "Public" } else { "Private" };
14847 render_fragment_with_params(
14848 app_root,
14849 "templates/shared/fragments/public_state_toggle_button.html",
14850 serde_json::Map::from_iter([
14851 (
14852 "button_label".to_owned(),
14853 Value::String(button_label.to_owned()),
14854 ),
14855 ("is_public".to_owned(), Value::String(is_public.to_string())),
14856 (
14857 "state_class".to_owned(),
14858 Value::String(
14859 if is_public {
14860 "business-is-public"
14861 } else {
14862 "business-is-private"
14863 }
14864 .to_owned(),
14865 ),
14866 ),
14867 ]),
14868 )
14869}
14870
14871/// Renders availability rows from an app-owned state document.
14872///
14873/// # Parameters
14874///
14875/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_render_business_admin_availability_rows_html`].
14876/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_render_business_admin_availability_rows_html`].
14877/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_render_business_admin_availability_rows_html`].
14878///
14879/// # Returns
14880///
14881/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_render_business_admin_availability_rows_html`] can produce a value and [`None`] otherwise.
14882///
14883/// # Performance
14884///
14885/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_render_business_admin_availability_rows_html`].
14886///
14887/// # Examples
14888///
14889/// ```rust,ignore
14890/// let _result = pharos_native::runtime::json_commands::dynamic_render_business_admin_availability_rows_html(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
14891/// ```
14892///
14893/// # Related
14894///
14895/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
14896pub fn dynamic_render_business_admin_availability_rows_html(
14897 root: &Value,
14898 business_slug: &str,
14899 app_root: &Path,
14900) -> Option<String> {
14901 if app_root.as_os_str().is_empty() || business_slug.is_empty() {
14902 return None;
14903 }
14904 let business_id = state_row_by_id(
14905 &serde_json::json!({
14906 "businesses": bounded_state_array(root, "businesses")
14907 .iter()
14908 .filter(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))
14909 .cloned()
14910 .collect::<Vec<_>>()
14911 }),
14912 "businesses",
14913 bounded_state_array(root, "businesses")
14914 .iter()
14915 .find(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))
14916 .and_then(|row| row.get("id").and_then(Value::as_i64))?,
14917 )?
14918 .get("id")
14919 .and_then(Value::as_i64)?;
14920 let mut availability_rows = bounded_state_array(root, "business_availability")
14921 .iter()
14922 .filter(|row| row.get("business_id").and_then(Value::as_i64) == Some(business_id))
14923 .collect::<Vec<_>>();
14924 availability_rows.sort_by_key(|row| {
14925 (
14926 weekday_rank(
14927 &row.get("day_of_week")
14928 .map(value_to_text)
14929 .unwrap_or_default(),
14930 ),
14931 row.get("start_time").map(value_to_text).unwrap_or_default(),
14932 row.get("id").and_then(Value::as_i64).unwrap_or_default(),
14933 )
14934 });
14935 let mut output = String::new();
14936 for row in availability_rows {
14937 let availability_id = row.get("id").and_then(Value::as_i64)?;
14938 let day_of_week = row
14939 .get("day_of_week")
14940 .map(value_to_text)
14941 .unwrap_or_default();
14942 let start_time = row
14943 .get("start_time")
14944 .map(value_to_text)
14945 .filter(|value| !value.is_empty())
14946 .unwrap_or_else(|| "09:00".to_owned());
14947 let end_time = row
14948 .get("end_time")
14949 .map(value_to_text)
14950 .filter(|value| !value.is_empty())
14951 .unwrap_or_else(|| "17:00".to_owned());
14952 let hidden_day_input = render_fragment_with_params(
14953 app_root,
14954 "templates/shared/fragments/ui_hidden_input.html",
14955 serde_json::Map::from_iter([
14956 (
14957 "id".to_owned(),
14958 Value::String("availability_row_day_hidden_input".to_owned()),
14959 ),
14960 ("name".to_owned(), Value::String("day_of_week".to_owned())),
14961 ("value".to_owned(), Value::String(day_of_week.clone())),
14962 ("attributes_html".to_owned(), Value::String(String::new())),
14963 ]),
14964 )?;
14965 let start_input = render_fragment_with_params(
14966 app_root,
14967 "templates/shared/fragments/ui_input.html",
14968 serde_json::Map::from_iter([
14969 (
14970 "id".to_owned(),
14971 Value::String("availability_row_start_time_input".to_owned()),
14972 ),
14973 ("input_type".to_owned(), Value::String("time".to_owned())),
14974 ("name".to_owned(), Value::String("start_time".to_owned())),
14975 ("label".to_owned(), Value::String("Start time".to_owned())),
14976 (
14977 "label_class".to_owned(),
14978 Value::String("sr-only".to_owned()),
14979 ),
14980 (
14981 "class_name".to_owned(),
14982 Value::String("form-control availability-inline-input".to_owned()),
14983 ),
14984 ("value".to_owned(), Value::String(start_time.clone())),
14985 (
14986 "attributes_html".to_owned(),
14987 Value::String(" aria-label=\"Start time\"".to_owned()),
14988 ),
14989 ("help_html".to_owned(), Value::String(String::new())),
14990 ]),
14991 )?;
14992 let end_input = render_fragment_with_params(
14993 app_root,
14994 "templates/shared/fragments/ui_input.html",
14995 serde_json::Map::from_iter([
14996 (
14997 "id".to_owned(),
14998 Value::String("availability_row_end_time_input".to_owned()),
14999 ),
15000 ("input_type".to_owned(), Value::String("time".to_owned())),
15001 ("name".to_owned(), Value::String("end_time".to_owned())),
15002 ("label".to_owned(), Value::String("End time".to_owned())),
15003 (
15004 "label_class".to_owned(),
15005 Value::String("sr-only".to_owned()),
15006 ),
15007 (
15008 "class_name".to_owned(),
15009 Value::String("form-control availability-inline-input".to_owned()),
15010 ),
15011 ("value".to_owned(), Value::String(end_time.clone())),
15012 (
15013 "attributes_html".to_owned(),
15014 Value::String(" aria-label=\"End time\"".to_owned()),
15015 ),
15016 ("help_html".to_owned(), Value::String(String::new())),
15017 ]),
15018 )?;
15019 let save_button = render_fragment_with_params(
15020 app_root,
15021 "templates/shared/fragments/ui_button.html",
15022 serde_json::Map::from_iter([
15023 (
15024 "id".to_owned(),
15025 Value::String("availability_row_save_button".to_owned()),
15026 ),
15027 ("button_type".to_owned(), Value::String("submit".to_owned())),
15028 (
15029 "class_name".to_owned(),
15030 Value::String(
15031 "btn btn-success btn-icon btn-icon-sm business-admin-save-button"
15032 .to_owned(),
15033 ),
15034 ),
15035 (
15036 "attributes_html".to_owned(),
15037 Value::String(
15038 " title=\"Save Availability\" aria-label=\"Save Availability\"".to_owned(),
15039 ),
15040 ),
15041 (
15042 "content_html".to_owned(),
15043 Value::String(
15044 generated::render_generated_admin_fragment_from_value_runtime(
15045 app_root,
15046 "templates/shared/fragments/ui_content_save_availability.html",
15047 &serde_json::Map::new(),
15048 )?,
15049 ),
15050 ),
15051 ]),
15052 )?;
15053 let delete_button = render_fragment_with_params(
15054 app_root,
15055 "templates/shared/fragments/ui_button.html",
15056 serde_json::Map::from_iter([
15057 (
15058 "id".to_owned(),
15059 Value::String("availability_row_delete_button".to_owned()),
15060 ),
15061 ("button_type".to_owned(), Value::String("button".to_owned())),
15062 (
15063 "class_name".to_owned(),
15064 Value::String("btn btn-danger btn-icon btn-icon-sm".to_owned()),
15065 ),
15066 (
15067 "attributes_html".to_owned(),
15068 Value::String(format!(
15069 " title=\"Delete Availability\" aria-label=\"Delete Availability\" hx-post=\"/businesses/{business_slug}/availability/{availability_id}/delete/\" hx-include=\"closest form\" hx-target=\"#business-availability-rows\" hx-swap=\"innerHTML\""
15070 )),
15071 ),
15072 (
15073 "content_html".to_owned(),
15074 Value::String(
15075 generated::render_generated_admin_fragment_from_value_runtime(
15076 app_root,
15077 "templates/shared/fragments/ui_content_delete_availability.html",
15078 &serde_json::Map::new(),
15079 )?,
15080 ),
15081 ),
15082 ]),
15083 )?;
15084 output.push_str(&render_fragment_with_params(
15085 app_root,
15086 "templates/businesses/fragments/business_admin_availability_row.html",
15087 serde_json::Map::from_iter([
15088 ("day_of_week".to_owned(), Value::String(day_of_week)),
15089 (
15090 "start_display".to_owned(),
15091 Value::String(start_time.clone()),
15092 ),
15093 ("end_display".to_owned(), Value::String(end_time.clone())),
15094 (
15095 "save_path".to_owned(),
15096 Value::String(format!(
15097 "/businesses/{business_slug}/availability/{availability_id}/edit/"
15098 )),
15099 ),
15100 (
15101 "availability_row_day_hidden_input_ui_html".to_owned(),
15102 Value::String(hidden_day_input),
15103 ),
15104 (
15105 "availability_row_start_time_input_ui_html".to_owned(),
15106 Value::String(start_input),
15107 ),
15108 (
15109 "availability_row_end_time_input_ui_html".to_owned(),
15110 Value::String(end_input),
15111 ),
15112 (
15113 "availability_row_save_button_ui_html".to_owned(),
15114 Value::String(save_button),
15115 ),
15116 (
15117 "availability_row_delete_button_ui_html".to_owned(),
15118 Value::String(delete_button),
15119 ),
15120 ]),
15121 )?);
15122 }
15123 Some(output)
15124}
15125
15126/// Performs page params value.
15127fn page_params_value(root: &Value, page_id: &str, extra: Map<String, Value>) -> Option<String> {
15128 if page_id.is_empty() {
15129 return None;
15130 }
15131 let mut params = Map::new();
15132 params.insert("page_id".to_owned(), Value::String(page_id.to_owned()));
15133 params.insert("state".to_owned(), Value::Object(extra));
15134 params.insert(
15135 "record_count".to_owned(),
15136 Value::from(root.as_object().map(|object| object.len()).unwrap_or(0)),
15137 );
15138 compact(&Value::Object(params))
15139}
15140
15141fn row_i64_value(row: &Value, field: &str) -> Option<i64> {
15142 row.get(field).and_then(Value::as_i64).or_else(|| {
15143 row.get(field)
15144 .and_then(Value::as_str)
15145 .and_then(|value| value.parse::<i64>().ok())
15146 })
15147}
15148
15149fn state_row_by_i64<'a>(
15150 root: &'a Value,
15151 section: &str,
15152 field: &str,
15153 expected: i64,
15154) -> Option<&'a Value> {
15155 bounded_state_array(root, section)
15156 .iter()
15157 .find(|row| row_i64_value(row, field) == Some(expected))
15158}
15159
15160fn row_date_time(row: &Value) -> (String, String) {
15161 let date = row
15162 .get("date")
15163 .map(value_to_text)
15164 .filter(|value| !value.is_empty())
15165 .or_else(|| {
15166 row.get("start_time")
15167 .map(value_to_text)
15168 .and_then(|value| value.get(0..10).map(ToOwned::to_owned))
15169 })
15170 .unwrap_or_default();
15171 let time = row
15172 .get("time")
15173 .map(value_to_text)
15174 .filter(|value| !value.is_empty())
15175 .or_else(|| {
15176 row.get("start_time")
15177 .map(value_to_text)
15178 .and_then(|value| value.get(11..16).map(ToOwned::to_owned))
15179 })
15180 .unwrap_or_default();
15181 (date, time)
15182}
15183
15184/// Builds the availability edit page parameters from persisted state.
15185///
15186/// # Parameters
15187///
15188/// - `state_path`: Input consumed by [`crate::runtime::json_commands::dynamic_business_availability_edit_page_params_json`].
15189/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_availability_edit_page_params_json`].
15190/// - `availability_id`: Input consumed by [`crate::runtime::json_commands::dynamic_business_availability_edit_page_params_json`].
15191/// - `error_text`: Input consumed by [`crate::runtime::json_commands::dynamic_business_availability_edit_page_params_json`].
15192/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_availability_edit_page_params_json`].
15193///
15194/// # Returns
15195///
15196/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_availability_edit_page_params_json`] can produce a value and [`None`] otherwise.
15197///
15198/// # Examples
15199///
15200/// ```rust,ignore
15201/// let _result = pharos_native::runtime::json_commands::dynamic_business_availability_edit_page_params_json(todo!(), "demo", todo!(), todo!(), std::path::Path::new("apps/dev_docs"));
15202/// ```
15203///
15204/// # Related
15205///
15206/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15207pub fn dynamic_business_availability_edit_page_params_json(
15208 state_path: &Path,
15209 business_slug: &str,
15210 availability_id: i64,
15211 error_text: &str,
15212 app_root: &Path,
15213) -> Option<String> {
15214 let root = json_doc_load_file(state_path)?;
15215 let mut extra = Map::new();
15216 extra.insert("business_slug".into(), Value::String(business_slug.into()));
15217 extra.insert("availability_id".into(), Value::from(availability_id));
15218 extra.insert("error".into(), Value::String(error_text.into()));
15219 extra.insert(
15220 "app_root".into(),
15221 Value::String(app_root.to_string_lossy().into_owned()),
15222 );
15223 page_params_value(&root, "business-availability-edit", extra)
15224}
15225
15226/// Builds the business delete confirmation page parameters.
15227///
15228/// # Parameters
15229///
15230/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_delete_confirm_page_params_json`].
15231/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_delete_confirm_page_params_json`].
15232///
15233/// # Returns
15234///
15235/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_delete_confirm_page_params_json`] can produce a value and [`None`] otherwise.
15236///
15237/// # Examples
15238///
15239/// ```rust,ignore
15240/// let _result = pharos_native::runtime::json_commands::dynamic_business_delete_confirm_page_params_json("demo", std::path::Path::new("apps/dev_docs"));
15241/// ```
15242///
15243/// # Related
15244///
15245/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15246pub fn dynamic_business_delete_confirm_page_params_json(
15247 business_slug: &str,
15248 app_root: &Path,
15249) -> Option<String> {
15250 let mut extra = Map::new();
15251 extra.insert("business_slug".into(), Value::String(business_slug.into()));
15252 extra.insert(
15253 "app_root".into(),
15254 Value::String(app_root.to_string_lossy().into_owned()),
15255 );
15256 page_params_value(&Value::Object(Map::new()), "business-delete-confirm", extra)
15257}
15258
15259/// Builds the invite-accept page parameter document.
15260///
15261/// # Parameters
15262///
15263/// - `token`: Input consumed by [`crate::runtime::json_commands::dynamic_invite_accept_page_params_json`].
15264/// - `error_text`: Input consumed by [`crate::runtime::json_commands::dynamic_invite_accept_page_params_json`].
15265///
15266/// # Returns
15267///
15268/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_invite_accept_page_params_json`] can produce a value and [`None`] otherwise.
15269///
15270/// # Examples
15271///
15272/// ```rust,ignore
15273/// let _result = pharos_native::runtime::json_commands::dynamic_invite_accept_page_params_json(todo!(), todo!());
15274/// ```
15275///
15276/// # Related
15277///
15278/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15279pub fn dynamic_invite_accept_page_params_json(token: &str, error_text: &str) -> Option<String> {
15280 let mut extra = Map::new();
15281 extra.insert("token".into(), Value::String(token.into()));
15282 extra.insert("error".into(), Value::String(error_text.into()));
15283 page_params_value(&Value::Null, "invite-accept", extra)
15284}
15285
15286/// Builds a validated generated-admin operation URL.
15287///
15288/// # Parameters
15289///
15290/// - `base_path`: Input consumed by [`crate::runtime::json_commands::generated_admin_operation_path_dup_business_logic_local`].
15291/// - `operation`: Input consumed by [`crate::runtime::json_commands::generated_admin_operation_path_dup_business_logic_local`].
15292/// - `record_id`: Input consumed by [`crate::runtime::json_commands::generated_admin_operation_path_dup_business_logic_local`].
15293///
15294/// # Returns
15295///
15296/// Returns [`Some`] when [`crate::runtime::json_commands::generated_admin_operation_path_dup_business_logic_local`] can produce a value and [`None`] otherwise.
15297///
15298/// # Examples
15299///
15300/// ```rust,ignore
15301/// let _result = pharos_native::runtime::json_commands::generated_admin_operation_path_dup_business_logic_local("/app", todo!(), todo!());
15302/// ```
15303///
15304/// # Related
15305///
15306/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15307pub fn generated_admin_operation_path_dup_business_logic_local(
15308 base_path: &str,
15309 operation: &str,
15310 record_id: i64,
15311) -> Option<String> {
15312 if operation.is_empty() || record_id <= 0 || base_path.contains(['\r', '\n']) {
15313 return None;
15314 }
15315 Some(format!(
15316 "{}/{}/{}",
15317 base_path.trim_end_matches('/'),
15318 operation,
15319 record_id
15320 ))
15321}
15322
15323/// Builds generated-admin management page parameters from state.
15324///
15325/// # Parameters
15326///
15327/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_page_params_json`].
15328/// - `collection`: Input consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_page_params_json`].
15329/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_page_params_json`].
15330///
15331/// # Returns
15332///
15333/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_generated_admin_manage_page_params_json`] can produce a value and [`None`] otherwise.
15334///
15335/// # Examples
15336///
15337/// ```rust,ignore
15338/// let _result = pharos_native::runtime::json_commands::dynamic_generated_admin_manage_page_params_json(std::path::Path::new("."), todo!(), std::path::Path::new("apps/dev_docs"));
15339/// ```
15340///
15341/// # Related
15342///
15343/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15344pub fn dynamic_generated_admin_manage_page_params_json(
15345 root: &Value,
15346 collection: &str,
15347 app_root: &Path,
15348) -> Option<String> {
15349 let mut extra = Map::new();
15350 extra.insert("collection".into(), Value::String(collection.into()));
15351 extra.insert(
15352 "app_root".into(),
15353 Value::String(app_root.to_string_lossy().into_owned()),
15354 );
15355 extra.insert(
15356 "rows".into(),
15357 Value::Array(bounded_state_array(root, collection).to_vec()),
15358 );
15359 page_params_value(root, "generated-admin-manage", extra)
15360}
15361
15362/// Converts a JSON value to its compact scalar representation.
15363///
15364/// # Parameters
15365///
15366/// - `value`: Input consumed by [`crate::runtime::json_commands::json_value_tostring_dup_runtime_local`].
15367///
15368/// # Returns
15369///
15370/// Returns [`Some`] when [`crate::runtime::json_commands::json_value_tostring_dup_runtime_local`] can produce a value and [`None`] otherwise.
15371///
15372/// # Examples
15373///
15374/// ```rust,ignore
15375/// let _result = pharos_native::runtime::json_commands::json_value_tostring_dup_runtime_local("value");
15376/// ```
15377///
15378/// # Related
15379///
15380/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15381pub fn json_value_tostring_dup_runtime_local(value: &Value) -> Option<String> {
15382 compact(value)
15383}
15384
15385/// Returns a bounded composition string from a surface value.
15386///
15387/// # Parameters
15388///
15389/// - `value`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_composition_string`].
15390///
15391/// # Returns
15392///
15393/// Returns [`Some`] when [`crate::runtime::json_commands::runtime_generated_surface_composition_string`] can produce a value and [`None`] otherwise.
15394///
15395/// # Examples
15396///
15397/// ```rust,ignore
15398/// let _result = pharos_native::runtime::json_commands::runtime_generated_surface_composition_string("value");
15399/// ```
15400///
15401/// # Related
15402///
15403/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15404pub fn runtime_generated_surface_composition_string(value: &Value) -> Option<String> {
15405 value
15406 .as_str()
15407 .map(strdup)
15408 .filter(|value| value.len() <= MAX_TEXT_BYTES)
15409}
15410
15411/// Returns the generated delete-button label.
15412///
15413/// # Returns
15414///
15415/// Returns the computed value produced by [`crate::runtime::json_commands::runtime_generated_surface_delete_button_label`].
15416///
15417/// # Examples
15418///
15419/// ```rust,ignore
15420/// let _result = pharos_native::runtime::json_commands::runtime_generated_surface_delete_button_label();
15421/// ```
15422///
15423/// # Related
15424///
15425/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15426pub fn runtime_generated_surface_delete_button_label() -> String {
15427 "Delete".to_owned()
15428}
15429
15430/// Converts one display value to escaped text.
15431///
15432/// # Parameters
15433///
15434/// - `value`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_display_value`].
15435///
15436/// # Returns
15437///
15438/// Returns the computed value produced by [`crate::runtime::json_commands::runtime_generated_surface_display_value`].
15439///
15440/// # Examples
15441///
15442/// ```rust,ignore
15443/// let _result = pharos_native::runtime::json_commands::runtime_generated_surface_display_value("value");
15444/// ```
15445///
15446/// # Related
15447///
15448/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15449pub fn runtime_generated_surface_display_value(value: &Value) -> String {
15450 html_escape_text(&value_to_text(value))
15451}
15452
15453/// Returns a generated field label from a field declaration.
15454///
15455/// # Parameters
15456///
15457/// - `field`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_label`].
15458///
15459/// # Returns
15460///
15461/// Returns the computed value produced by [`crate::runtime::json_commands::runtime_generated_surface_label`].
15462///
15463/// # Examples
15464///
15465/// ```rust,ignore
15466/// let _result = pharos_native::runtime::json_commands::runtime_generated_surface_label("field");
15467/// ```
15468///
15469/// # Related
15470///
15471/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15472pub fn runtime_generated_surface_label(field: &Value) -> String {
15473 field
15474 .get("label")
15475 .and_then(Value::as_str)
15476 .or_else(|| field.get("field_id").and_then(Value::as_str))
15477 .map(strdup)
15478 .unwrap_or_default()
15479}
15480
15481/// Takes a text buffer and leaves it empty.
15482///
15483/// # Parameters
15484///
15485/// - `buffer`: Input consumed by [`crate::runtime::json_commands::runtime_text_buffer_take`].
15486///
15487/// # Returns
15488///
15489/// Returns the computed value produced by [`crate::runtime::json_commands::runtime_text_buffer_take`].
15490///
15491/// # Examples
15492///
15493/// ```rust,ignore
15494/// let _result = pharos_native::runtime::json_commands::runtime_text_buffer_take(&mut []);
15495/// ```
15496///
15497/// # Related
15498///
15499/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15500pub fn runtime_text_buffer_take(buffer: &mut String) -> String {
15501 std::mem::take(buffer)
15502}
15503
15504/// Builds appointments page parameters from a state root.
15505///
15506/// # Parameters
15507///
15508/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_appointments_page_params_from_root_json`].
15509/// - `user_id`: Input consumed by [`crate::runtime::json_commands::dynamic_appointments_page_params_from_root_json`].
15510/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_appointments_page_params_from_root_json`].
15511///
15512/// # Returns
15513///
15514/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_appointments_page_params_from_root_json`] can produce a value and [`None`] otherwise.
15515///
15516/// # Examples
15517///
15518/// ```rust,ignore
15519/// let _result = pharos_native::runtime::json_commands::dynamic_appointments_page_params_from_root_json(std::path::Path::new("."), 1, std::path::Path::new("apps/dev_docs"));
15520/// ```
15521///
15522/// # Related
15523///
15524/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15525pub fn dynamic_appointments_page_params_from_root_json(
15526 root: &Value,
15527 user_id: i64,
15528 app_root: &Path,
15529) -> Option<String> {
15530 if user_id <= 0 {
15531 return None;
15532 }
15533 let mut rows_html = String::new();
15534 for row in bounded_state_array(root, "appointments")
15535 .iter()
15536 .filter(|row| row_i64_value(row, "user_id") == Some(user_id))
15537 {
15538 let service = row_i64_value(row, "service_id")
15539 .and_then(|id| state_row_by_i64(root, "services", "id", id));
15540 let business = row_i64_value(row, "business_id")
15541 .and_then(|id| state_row_by_i64(root, "businesses", "id", id));
15542 let service_name = service
15543 .and_then(|value| value.get("name"))
15544 .map(value_to_text)
15545 .filter(|value| !value.is_empty())
15546 .unwrap_or_else(|| "Appointment".to_owned());
15547 let business_name = business
15548 .and_then(|value| value.get("name"))
15549 .map(value_to_text)
15550 .filter(|value| !value.is_empty())
15551 .unwrap_or_else(|| "Business".to_owned());
15552 let business_slug = business
15553 .and_then(|value| value.get("slug"))
15554 .map(value_to_text)
15555 .unwrap_or_default();
15556 let (date, time) = row_date_time(row);
15557 let status = row
15558 .get("status")
15559 .map(value_to_text)
15560 .filter(|value| !value.is_empty())
15561 .unwrap_or_else(|| "scheduled".to_owned());
15562 let payment_status = row
15563 .get("payment_status")
15564 .map(value_to_text)
15565 .unwrap_or_default();
15566 let meta_line_one = if date.is_empty() && time.is_empty() {
15567 business_name.clone()
15568 } else {
15569 format!(
15570 "{} at {}",
15571 date,
15572 formatted_clock_time_business_logic_local(&time)
15573 )
15574 };
15575 let meta_line_two = if payment_status.is_empty() {
15576 business_name
15577 } else {
15578 format!("{business_name} · Payment: {payment_status}")
15579 };
15580 rows_html.push_str(&render_fragment_with_params(
15581 app_root,
15582 "templates/shared/fragments/appointment_row.html",
15583 Map::from_iter([
15584 ("service_name".to_owned(), Value::String(service_name)),
15585 ("status_value".to_owned(), Value::String(status)),
15586 ("meta_line_one".to_owned(), Value::String(meta_line_one)),
15587 (
15588 "meta_line_one_style".to_owned(),
15589 Value::String(String::new()),
15590 ),
15591 ("meta_line_two".to_owned(), Value::String(meta_line_two)),
15592 (
15593 "meta_line_two_style".to_owned(),
15594 Value::String(String::new()),
15595 ),
15596 ("tertiary_line".to_owned(), Value::String(String::new())),
15597 (
15598 "tertiary_line_style".to_owned(),
15599 Value::String("display:none;".to_owned()),
15600 ),
15601 (
15602 "notes_text".to_owned(),
15603 Value::String(row.get("notes").map(value_to_text).unwrap_or_default()),
15604 ),
15605 (
15606 "notes_style".to_owned(),
15607 Value::String(
15608 if row
15609 .get("notes")
15610 .map(value_to_text)
15611 .unwrap_or_default()
15612 .is_empty()
15613 {
15614 "display:none;".to_owned()
15615 } else {
15616 String::new()
15617 },
15618 ),
15619 ),
15620 (
15621 "detail_path".to_owned(),
15622 Value::String(if business_slug.is_empty() {
15623 "/appointments/".to_owned()
15624 } else {
15625 format!("/b/{business_slug}/")
15626 }),
15627 ),
15628 (
15629 "detail_link_style".to_owned(),
15630 Value::String("display:none;".to_owned()),
15631 ),
15632 (
15633 "request_cancel_path".to_owned(),
15634 Value::String(format!(
15635 "/appointments/{}/request-cancel/",
15636 row_i64_value(row, "id").unwrap_or(0)
15637 )),
15638 ),
15639 (
15640 "request_cancel_style".to_owned(),
15641 Value::String("display:none;".to_owned()),
15642 ),
15643 (
15644 "request_cancel_button_ui_html".to_owned(),
15645 Value::String(String::new()),
15646 ),
15647 (
15648 "complete_path".to_owned(),
15649 Value::String(format!(
15650 "/appointments/{}/complete/",
15651 row_i64_value(row, "id").unwrap_or(0)
15652 )),
15653 ),
15654 (
15655 "complete_style".to_owned(),
15656 Value::String("display:none;".to_owned()),
15657 ),
15658 (
15659 "complete_button_ui_html".to_owned(),
15660 Value::String(String::new()),
15661 ),
15662 (
15663 "cancel_path".to_owned(),
15664 Value::String(format!(
15665 "/appointments/{}/cancel/",
15666 row_i64_value(row, "id").unwrap_or(0)
15667 )),
15668 ),
15669 (
15670 "cancel_style".to_owned(),
15671 Value::String("display:none;".to_owned()),
15672 ),
15673 (
15674 "cancel_button_ui_html".to_owned(),
15675 Value::String(String::new()),
15676 ),
15677 ]),
15678 )?);
15679 }
15680 if rows_html.is_empty() {
15681 rows_html = render_fragment_with_params(
15682 app_root,
15683 "templates/appointments/fragments/appointments_empty_manage.html",
15684 Map::new(),
15685 )?;
15686 }
15687 compact(&Value::Object(Map::from_iter([
15688 (
15689 "heading".to_owned(),
15690 Value::String("Appointments".to_owned()),
15691 ),
15692 ("appointment_items".to_owned(), Value::String(rows_html)),
15693 ("tips_style".to_owned(), Value::String(String::new())),
15694 ])))
15695}
15696
15697/// Loads appointments page parameters from a state file.
15698///
15699/// # Parameters
15700///
15701/// - `state_path`: Input consumed by [`crate::runtime::json_commands::dynamic_appointments_page_params_json`].
15702/// - `user_id`: Input consumed by [`crate::runtime::json_commands::dynamic_appointments_page_params_json`].
15703/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_appointments_page_params_json`].
15704///
15705/// # Returns
15706///
15707/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_appointments_page_params_json`] can produce a value and [`None`] otherwise.
15708///
15709/// # Examples
15710///
15711/// ```rust,ignore
15712/// let _result = pharos_native::runtime::json_commands::dynamic_appointments_page_params_json(todo!(), 1, std::path::Path::new("apps/dev_docs"));
15713/// ```
15714///
15715/// # Related
15716///
15717/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15718pub fn dynamic_appointments_page_params_json(
15719 state_path: &Path,
15720 user_id: i64,
15721 app_root: &Path,
15722) -> Option<String> {
15723 dynamic_appointments_page_params_from_root_json(
15724 &json_doc_load_file(state_path)?,
15725 user_id,
15726 app_root,
15727 )
15728}
15729
15730/// Builds the booking form parameter document.
15731///
15732/// # Returns
15733///
15734/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_booking_form_page_params_json`] can produce a value and [`None`] otherwise.
15735///
15736/// # Examples
15737///
15738/// ```rust,ignore
15739/// let _result = pharos_native::runtime::json_commands::dynamic_booking_form_page_params_json();
15740/// ```
15741///
15742/// # Related
15743///
15744/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15745pub fn dynamic_booking_form_page_params_json() -> Option<String> {
15746 page_params_value(&Value::Null, "booking-form", Map::new())
15747}
15748
15749/// Builds bookings page parameters from state.
15750///
15751/// # Parameters
15752///
15753/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_bookings_page_params_json`].
15754/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_bookings_page_params_json`].
15755/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_bookings_page_params_json`].
15756///
15757/// # Returns
15758///
15759/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_bookings_page_params_json`] can produce a value and [`None`] otherwise.
15760///
15761/// # Examples
15762///
15763/// ```rust,ignore
15764/// let _result = pharos_native::runtime::json_commands::dynamic_bookings_page_params_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
15765/// ```
15766///
15767/// # Related
15768///
15769/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15770pub fn dynamic_bookings_page_params_json(
15771 root: &Value,
15772 business_slug: &str,
15773 app_root: &Path,
15774) -> Option<String> {
15775 let mut extra = Map::new();
15776 extra.insert("business_slug".into(), Value::String(business_slug.into()));
15777 extra.insert(
15778 "app_root".into(),
15779 Value::String(app_root.to_string_lossy().into_owned()),
15780 );
15781 extra.insert(
15782 "bookings".into(),
15783 Value::Array(bounded_state_array(root, "bookings").to_vec()),
15784 );
15785 page_params_value(root, "bookings", extra)
15786}
15787
15788pub fn dynamic_user_bookings_page_params_from_root_json(
15789 root: &Value,
15790 user_id: i64,
15791 app_root: &Path,
15792) -> Option<String> {
15793 if user_id <= 0 {
15794 return None;
15795 }
15796 let mut rows_html = String::new();
15797 for row in bounded_state_array(root, "bookings")
15798 .iter()
15799 .filter(|row| row_i64_value(row, "user_id") == Some(user_id))
15800 {
15801 let (date, time) = row_date_time(row);
15802 rows_html.push_str(&render_fragment_with_params(
15803 app_root,
15804 "templates/bookings/fragments/booking_row.html",
15805 Map::from_iter([
15806 (
15807 "booking_title".to_owned(),
15808 Value::String(
15809 row.get("title")
15810 .map(value_to_text)
15811 .filter(|value| !value.is_empty())
15812 .unwrap_or_else(|| "Booking".to_owned()),
15813 ),
15814 ),
15815 (
15816 "status_value".to_owned(),
15817 Value::String(
15818 row.get("status")
15819 .map(value_to_text)
15820 .filter(|value| !value.is_empty())
15821 .unwrap_or_else(|| "confirmed".to_owned()),
15822 ),
15823 ),
15824 ("date_value".to_owned(), Value::String(date)),
15825 (
15826 "time_value".to_owned(),
15827 Value::String(formatted_clock_time_business_logic_local(&time)),
15828 ),
15829 ]),
15830 )?);
15831 }
15832 if rows_html.is_empty() {
15833 rows_html = render_fragment_with_params(
15834 app_root,
15835 "templates/bookings/bookings_empty.html",
15836 Map::new(),
15837 )?;
15838 }
15839 compact(&Value::Object(Map::from_iter([
15840 (
15841 "create_path".to_owned(),
15842 Value::String("/bookings/new/".to_owned()),
15843 ),
15844 (
15845 "error_style".to_owned(),
15846 Value::String("display:none;".to_owned()),
15847 ),
15848 ("error_message".to_owned(), Value::String(String::new())),
15849 ("booking_rows_html".to_owned(), Value::String(rows_html)),
15850 ])))
15851}
15852
15853/// Builds the business admin page parameter document.
15854///
15855/// # Parameters
15856///
15857/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_admin_page_params_json`].
15858/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_admin_page_params_json`].
15859/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_admin_page_params_json`].
15860///
15861/// # Returns
15862///
15863/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_admin_page_params_json`] can produce a value and [`None`] otherwise.
15864///
15865/// # Examples
15866///
15867/// ```rust,ignore
15868/// let _result = pharos_native::runtime::json_commands::dynamic_business_admin_page_params_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
15869/// ```
15870///
15871/// # Related
15872///
15873/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15874pub fn dynamic_business_admin_page_params_json(
15875 root: &Value,
15876 business_slug: &str,
15877 app_root: &Path,
15878) -> Option<String> {
15879 dynamic_bookings_page_params_json(root, business_slug, app_root)
15880}
15881
15882/// Builds the business calendar page parameters.
15883///
15884/// # Parameters
15885///
15886/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`].
15887/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`].
15888/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`].
15889/// - `year`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`].
15890/// - `month`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`].
15891/// - `date`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`].
15892///
15893/// # Returns
15894///
15895/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`] can produce a value and [`None`] otherwise.
15896///
15897/// # Performance
15898///
15899/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json`].
15900///
15901/// # Examples
15902///
15903/// ```rust,ignore
15904/// let _result = pharos_native::runtime::json_commands::dynamic_business_calendar_page_params_from_root_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
15905/// ```
15906///
15907/// # Related
15908///
15909/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15910pub fn dynamic_business_calendar_page_params_from_root_json(
15911 root: &Value,
15912 business_slug: &str,
15913 app_root: &Path,
15914 year: Option<&str>,
15915 month: Option<&str>,
15916 date: Option<&str>,
15917) -> Option<String> {
15918 let mut extra = Map::new();
15919 for (key, value) in [
15920 ("business_slug", business_slug),
15921 ("year", year.unwrap_or_default()),
15922 ("month", month.unwrap_or_default()),
15923 ("date", date.unwrap_or_default()),
15924 ] {
15925 extra.insert(key.into(), Value::String(value.into()));
15926 }
15927 extra.insert(
15928 "app_root".into(),
15929 Value::String(app_root.to_string_lossy().into_owned()),
15930 );
15931 extra.insert(
15932 "appointments".into(),
15933 Value::Array(bounded_state_array(root, "appointments").to_vec()),
15934 );
15935 page_params_value(root, "business-calendar", extra)
15936}
15937
15938/// Loads business calendar parameters from state.
15939///
15940/// # Parameters
15941///
15942/// - `state_path`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_json`].
15943/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_json`].
15944/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_json`].
15945/// - `year`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_json`].
15946/// - `month`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_json`].
15947/// - `date`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_page_params_json`].
15948///
15949/// # Returns
15950///
15951/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_calendar_page_params_json`] can produce a value and [`None`] otherwise.
15952///
15953/// # Examples
15954///
15955/// ```rust,ignore
15956/// let _result = pharos_native::runtime::json_commands::dynamic_business_calendar_page_params_json(todo!(), "demo", std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
15957/// ```
15958///
15959/// # Related
15960///
15961/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
15962pub fn dynamic_business_calendar_page_params_json(
15963 state_path: &Path,
15964 business_slug: &str,
15965 app_root: &Path,
15966 year: Option<&str>,
15967 month: Option<&str>,
15968 date: Option<&str>,
15969) -> Option<String> {
15970 dynamic_business_calendar_page_params_from_root_json(
15971 &json_doc_load_file(state_path)?,
15972 business_slug,
15973 app_root,
15974 year,
15975 month,
15976 date,
15977 )
15978}
15979
15980/// Renders icon button fragment.
15981fn render_icon_button_fragment(
15982 app_root: &Path,
15983 id: &str,
15984 button_type: &str,
15985 class_name: &str,
15986 attributes_html: &str,
15987 content_fragment: &str,
15988 content_params: serde_json::Map<String, Value>,
15989) -> Option<String> {
15990 let content_html = render_fragment_with_params(app_root, content_fragment, content_params)?;
15991 render_fragment_with_params(
15992 app_root,
15993 "templates/shared/fragments/ui_button.html",
15994 serde_json::Map::from_iter([
15995 ("id".to_owned(), Value::String(id.to_owned())),
15996 (
15997 "button_type".to_owned(),
15998 Value::String(button_type.to_owned()),
15999 ),
16000 (
16001 "class_name".to_owned(),
16002 Value::String(class_name.to_owned()),
16003 ),
16004 (
16005 "attributes_html".to_owned(),
16006 Value::String(attributes_html.to_owned()),
16007 ),
16008 ("content_html".to_owned(), Value::String(content_html)),
16009 ]),
16010 )
16011}
16012
16013/// Renders compact input fragment.
16014fn render_compact_input_fragment(
16015 app_root: &Path,
16016 id: &str,
16017 input_type: &str,
16018 name: &str,
16019 label: &str,
16020 value: &str,
16021 class_name: &str,
16022 attributes_html: &str,
16023) -> Option<String> {
16024 render_fragment_with_params(
16025 app_root,
16026 "templates/shared/fragments/ui_input.html",
16027 serde_json::Map::from_iter([
16028 ("id".to_owned(), Value::String(id.to_owned())),
16029 (
16030 "input_type".to_owned(),
16031 Value::String(input_type.to_owned()),
16032 ),
16033 ("name".to_owned(), Value::String(name.to_owned())),
16034 ("label".to_owned(), Value::String(label.to_owned())),
16035 (
16036 "label_class".to_owned(),
16037 Value::String("sr-only".to_owned()),
16038 ),
16039 (
16040 "class_name".to_owned(),
16041 Value::String(class_name.to_owned()),
16042 ),
16043 ("value".to_owned(), Value::String(value.to_owned())),
16044 (
16045 "attributes_html".to_owned(),
16046 Value::String(attributes_html.to_owned()),
16047 ),
16048 ("help_html".to_owned(), Value::String(String::new())),
16049 ]),
16050 )
16051}
16052
16053/// Renders compact select fragment.
16054fn render_compact_select_fragment(
16055 app_root: &Path,
16056 id: &str,
16057 name: &str,
16058 label: &str,
16059 class_name: &str,
16060 attributes_html: &str,
16061 options_html: String,
16062) -> Option<String> {
16063 render_fragment_with_params(
16064 app_root,
16065 "templates/shared/fragments/ui_select.html",
16066 serde_json::Map::from_iter([
16067 ("id".to_owned(), Value::String(id.to_owned())),
16068 ("name".to_owned(), Value::String(name.to_owned())),
16069 ("label".to_owned(), Value::String(label.to_owned())),
16070 (
16071 "label_class".to_owned(),
16072 Value::String("sr-only".to_owned()),
16073 ),
16074 (
16075 "class_name".to_owned(),
16076 Value::String(class_name.to_owned()),
16077 ),
16078 (
16079 "attributes_html".to_owned(),
16080 Value::String(attributes_html.to_owned()),
16081 ),
16082 ("options_html".to_owned(), Value::String(options_html)),
16083 ("help_html".to_owned(), Value::String(String::new())),
16084 ]),
16085 )
16086}
16087
16088/// Renders hidden input fragment.
16089fn render_hidden_input_fragment(
16090 app_root: &Path,
16091 id: &str,
16092 name: &str,
16093 value: &str,
16094) -> Option<String> {
16095 render_fragment_with_params(
16096 app_root,
16097 "templates/shared/fragments/ui_hidden_input.html",
16098 serde_json::Map::from_iter([
16099 ("id".to_owned(), Value::String(id.to_owned())),
16100 ("name".to_owned(), Value::String(name.to_owned())),
16101 ("value".to_owned(), Value::String(value.to_owned())),
16102 ("attributes_html".to_owned(), Value::String(String::new())),
16103 ]),
16104 )
16105}
16106
16107/// Renders textarea fragment.
16108fn render_textarea_fragment(
16109 app_root: &Path,
16110 id: &str,
16111 name: &str,
16112 label: &str,
16113 value: &str,
16114 class_name: &str,
16115 attributes_html: &str,
16116) -> Option<String> {
16117 render_fragment_with_params(
16118 app_root,
16119 "templates/shared/fragments/ui_textarea.html",
16120 serde_json::Map::from_iter([
16121 ("id".to_owned(), Value::String(id.to_owned())),
16122 ("name".to_owned(), Value::String(name.to_owned())),
16123 ("label".to_owned(), Value::String(label.to_owned())),
16124 (
16125 "label_class".to_owned(),
16126 Value::String("sr-only".to_owned()),
16127 ),
16128 (
16129 "class_name".to_owned(),
16130 Value::String(class_name.to_owned()),
16131 ),
16132 ("value".to_owned(), Value::String(value.to_owned())),
16133 (
16134 "attributes_html".to_owned(),
16135 Value::String(attributes_html.to_owned()),
16136 ),
16137 ("help_html".to_owned(), Value::String(String::new())),
16138 ]),
16139 )
16140}
16141
16142/// Renders day select options.
16143fn render_day_select_options(
16144 app_root: &Path,
16145 selected: &str,
16146 include_groups: bool,
16147) -> Option<String> {
16148 let mut options_html = String::new();
16149 if include_groups {
16150 for (value, label) in [("Weekdays", "Weekdays"), ("Weekends", "Weekends")] {
16151 options_html.push_str(&render_fragment_with_params(
16152 app_root,
16153 "templates/shared/fragments/select_option.html",
16154 Map::from_iter([
16155 ("option_value".to_owned(), Value::String(value.to_owned())),
16156 ("option_label".to_owned(), Value::String(label.to_owned())),
16157 (
16158 "selected_attr".to_owned(),
16159 Value::String(if selected == value { " selected" } else { "" }.to_owned()),
16160 ),
16161 ]),
16162 )?);
16163 }
16164 }
16165 for day in ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] {
16166 options_html.push_str(&render_fragment_with_params(
16167 app_root,
16168 "templates/shared/fragments/select_option.html",
16169 Map::from_iter([
16170 ("option_value".to_owned(), Value::String(day.to_owned())),
16171 ("option_label".to_owned(), Value::String(day.to_owned())),
16172 (
16173 "selected_attr".to_owned(),
16174 Value::String(if selected == day { " selected" } else { "" }.to_owned()),
16175 ),
16176 ]),
16177 )?);
16178 }
16179 Some(options_html)
16180}
16181
16182/// Performs business day matches group.
16183fn business_day_matches_group(day: &str, selected: &str) -> bool {
16184 match selected {
16185 "Weekdays" => matches!(day, "Mon" | "Tue" | "Wed" | "Thu" | "Fri"),
16186 "Weekends" => matches!(day, "Sat" | "Sun"),
16187 _ => day == selected,
16188 }
16189}
16190
16191/// Performs dynamic business admin service cards HTML.
16192///
16193/// # Parameters
16194///
16195/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_admin_service_cards_html`].
16196/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_admin_service_cards_html`].
16197/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_admin_service_cards_html`].
16198///
16199/// # Returns
16200///
16201/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_admin_service_cards_html`] can produce a value and [`None`] otherwise.
16202///
16203/// # Performance
16204///
16205/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_business_admin_service_cards_html`].
16206///
16207/// # Examples
16208///
16209/// ```rust,ignore
16210/// let _result = pharos_native::runtime::json_commands::dynamic_business_admin_service_cards_html(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
16211/// ```
16212///
16213/// # Related
16214///
16215/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
16216pub fn dynamic_business_admin_service_cards_html(
16217 root: &Value,
16218 business_slug: &str,
16219 app_root: &Path,
16220) -> Option<String> {
16221 let business = bounded_state_array(root, "businesses")
16222 .iter()
16223 .find(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))?;
16224 let business_id = business.get("id").and_then(Value::as_i64)?;
16225 let mut services: Vec<&Value> = bounded_state_array(root, "services")
16226 .iter()
16227 .filter(|row| row.get("business_id").and_then(Value::as_i64) == Some(business_id))
16228 .filter(|row| row.get("is_active").and_then(Value::as_bool) != Some(false))
16229 .collect();
16230 services.sort_by_key(|row| {
16231 row.get("sort_order")
16232 .and_then(Value::as_i64)
16233 .filter(|value| *value > 0)
16234 .unwrap_or_else(|| row.get("id").and_then(Value::as_i64).unwrap_or(0))
16235 });
16236 let mut output = String::new();
16237 for service in services {
16238 let service_id = service.get("id").and_then(Value::as_i64)?;
16239 let service_name = service.get("name").map(value_to_text).unwrap_or_default();
16240 let description = service
16241 .get("description")
16242 .map(value_to_text)
16243 .unwrap_or_default();
16244 let description_html = if description.is_empty() {
16245 String::new()
16246 } else {
16247 render_fragment_with_params(
16248 app_root,
16249 "templates/businesses/fragments/business_admin_service_description.html",
16250 Map::from_iter([("description".to_owned(), Value::String(description.clone()))]),
16251 )?
16252 };
16253 let order_value = service
16254 .get("sort_order")
16255 .and_then(Value::as_i64)
16256 .filter(|value| *value > 0)
16257 .unwrap_or(service_id);
16258 let summary = format!(
16259 "Order {} · ${} · {} min",
16260 order_value,
16261 service.get("cost").map(value_to_text).unwrap_or_default(),
16262 service
16263 .get("duration_minutes")
16264 .map(value_to_text)
16265 .filter(|value| !value.is_empty())
16266 .unwrap_or_else(|| "45".to_owned())
16267 );
16268 let booking_options: Vec<String> = service
16269 .get("booking_options")
16270 .and_then(Value::as_array)
16271 .map(|rows| {
16272 rows.iter()
16273 .filter_map(|row| {
16274 let label = row
16275 .get("label")
16276 .or_else(|| row.get("name"))
16277 .map(value_to_text)
16278 .unwrap_or_default();
16279 if label.is_empty() {
16280 return None;
16281 }
16282 let cost = row.get("cost").map(value_to_text).unwrap_or_default();
16283 Some(if cost.is_empty() {
16284 label
16285 } else {
16286 format!("{label} ${cost}")
16287 })
16288 })
16289 .collect()
16290 })
16291 .unwrap_or_default();
16292 let booking_options_html = if booking_options.is_empty() {
16293 String::new()
16294 } else {
16295 render_fragment_with_params(
16296 app_root,
16297 "templates/businesses/fragments/business_admin_service_options.html",
16298 Map::from_iter([(
16299 "options".to_owned(),
16300 Value::String(booking_options.join(", ")),
16301 )]),
16302 )?
16303 };
16304 let assigned_technician_ids: Vec<String> = service
16305 .get("assigned_technician_ids")
16306 .or_else(|| service.get("technician_ids"))
16307 .and_then(Value::as_array)
16308 .map(|rows| rows.iter().map(value_to_text).collect())
16309 .unwrap_or_default();
16310 let technician_names: Vec<String> = bounded_state_array(root, "technicians")
16311 .iter()
16312 .filter(|row| row.get("business_id").and_then(Value::as_i64) == Some(business_id))
16313 .filter(|row| {
16314 assigned_technician_ids
16315 .iter()
16316 .any(|id| row.get("id").map(value_to_text).as_deref() == Some(id.as_str()))
16317 })
16318 .filter_map(|row| {
16319 row.get("name")
16320 .map(value_to_text)
16321 .filter(|name| !name.is_empty())
16322 })
16323 .collect();
16324 let technicians_html = if technician_names.is_empty() {
16325 String::new()
16326 } else {
16327 render_fragment_with_params(
16328 app_root,
16329 "templates/businesses/fragments/business_admin_service_technicians.html",
16330 Map::from_iter([(
16331 "technicians".to_owned(),
16332 Value::String(technician_names.join(", ")),
16333 )]),
16334 )?
16335 };
16336 let image_url = service
16337 .get("image_url")
16338 .and_then(Value::as_str)
16339 .unwrap_or_default();
16340 let image_html = if image_url.is_empty() {
16341 service_visual_html(
16342 app_root,
16343 None,
16344 &service_name,
16345 service.get("primary_color").and_then(Value::as_str),
16346 )?
16347 } else {
16348 render_fragment_with_params(
16349 app_root,
16350 "templates/businesses/fragments/business_admin_service_image.html",
16351 Map::from_iter([
16352 ("image_url".to_owned(), Value::String(image_url.to_owned())),
16353 ("image_alt".to_owned(), Value::String(service_name.clone())),
16354 ]),
16355 )?
16356 };
16357 output.push_str(&render_fragment_with_params(
16358 app_root,
16359 "templates/businesses/fragments/business_admin_service_card.html",
16360 serde_json::Map::from_iter([
16361 (
16362 "delete_path".to_owned(),
16363 Value::String(format!(
16364 "/businesses/{business_slug}/services/{service_id}/delete/"
16365 )),
16366 ),
16367 (
16368 "edit_path".to_owned(),
16369 Value::String(format!(
16370 "/businesses/{business_slug}/services/{service_id}/edit/"
16371 )),
16372 ),
16373 ("image_html".to_owned(), Value::String(image_html)),
16374 ("service_name".to_owned(), Value::String(service_name)),
16375 ("service_summary".to_owned(), Value::String(summary)),
16376 (
16377 "service_description_html".to_owned(),
16378 Value::String(description_html),
16379 ),
16380 (
16381 "booking_options_html".to_owned(),
16382 Value::String(booking_options_html),
16383 ),
16384 (
16385 "technicians_html".to_owned(),
16386 Value::String(technicians_html),
16387 ),
16388 ]),
16389 )?);
16390 }
16391 Some(output)
16392}
16393
16394/// Builds business admin page params from a loaded state root.
16395///
16396/// # Parameters
16397///
16398/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_sections_html`].
16399/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_sections_html`].
16400/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_sections_html`].
16401/// - `target`: Input consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_sections_html`].
16402///
16403/// # Returns
16404///
16405/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_generated_admin_manage_sections_html`] can produce a value and [`None`] otherwise.
16406///
16407/// # Performance
16408///
16409/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_generated_admin_manage_sections_html`].
16410///
16411/// # Examples
16412///
16413/// ```rust,ignore
16414/// let _result = pharos_native::runtime::json_commands::dynamic_generated_admin_manage_sections_html(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"), "/");
16415/// ```
16416///
16417/// # Related
16418///
16419/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
16420pub fn dynamic_generated_admin_manage_sections_html(
16421 root: &Value,
16422 business_slug: &str,
16423 app_root: &Path,
16424 target: &str,
16425) -> Option<String> {
16426 let services_path = format!("/businesses/{business_slug}/services/");
16427 let availability_path = format!("/businesses/{business_slug}/availability/");
16428 if target.contains("/availability/") {
16429 let availability_rows_html =
16430 dynamic_render_business_admin_availability_rows_html(root, business_slug, app_root)?;
16431 let availability_body_html = render_fragment_with_params(
16432 app_root,
16433 "templates/businesses/fragments/business_admin_availability_body.html",
16434 serde_json::Map::from_iter([
16435 (
16436 "availability_rows_html".to_owned(),
16437 Value::String(availability_rows_html),
16438 ),
16439 (
16440 "availability_create_path".to_owned(),
16441 Value::String(format!("/businesses/{business_slug}/availability/new/")),
16442 ),
16443 (
16444 "business_admin_availability_day_select_ui_html".to_owned(),
16445 Value::String(render_compact_select_fragment(
16446 app_root,
16447 &format!("availability-manage-day-{business_slug}"),
16448 "day_of_week",
16449 "Day",
16450 "form-control form-control-compact availability-inline-input",
16451 " autocomplete=\"off\"",
16452 render_day_select_options(app_root, "Mon", true)?,
16453 )?),
16454 ),
16455 (
16456 "business_admin_availability_start_time_input_ui_html".to_owned(),
16457 Value::String(render_compact_input_fragment(
16458 app_root,
16459 &format!("availability-manage-start-{business_slug}"),
16460 "time",
16461 "start_time",
16462 "Start",
16463 "09:00",
16464 "form-control form-control-compact availability-inline-input",
16465 " autocomplete=\"off\"",
16466 )?),
16467 ),
16468 (
16469 "business_admin_availability_end_time_input_ui_html".to_owned(),
16470 Value::String(render_compact_input_fragment(
16471 app_root,
16472 &format!("availability-manage-end-{business_slug}"),
16473 "time",
16474 "end_time",
16475 "End",
16476 "17:00",
16477 "form-control form-control-compact availability-inline-input",
16478 " autocomplete=\"off\"",
16479 )?),
16480 ),
16481 (
16482 "business_admin_availability_submit_button_ui_html".to_owned(),
16483 Value::String(render_icon_button_fragment(
16484 app_root,
16485 &format!("availability-manage-submit-{business_slug}"),
16486 "submit",
16487 "btn btn-success btn-icon btn-icon-lg business-admin-save-button",
16488 "",
16489 "templates/shared/fragments/ui_content_apply_hours.html",
16490 serde_json::Map::new(),
16491 )?),
16492 ),
16493 ]),
16494 )?;
16495 return render_fragment_with_params(
16496 app_root,
16497 "templates/businesses/fragments/business_admin_availability_manage.html",
16498 serde_json::Map::from_iter([
16499 ("services_path".to_owned(), Value::String(services_path)),
16500 (
16501 "availability_body_html".to_owned(),
16502 Value::String(availability_body_html),
16503 ),
16504 ]),
16505 );
16506 }
16507
16508 let business = bounded_state_array(root, "businesses")
16509 .iter()
16510 .find(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))?;
16511 let business_id = business.get("id").and_then(Value::as_i64)?;
16512 let service_rows_html =
16513 dynamic_business_admin_service_cards_html(root, business_slug, app_root)?;
16514 let service_create_technician_rows_html =
16515 technician_checkboxes_html(app_root, root, business_id, 0, "No technicians yet.", true)?;
16516 let services_body_html = render_fragment_with_params(
16517 app_root,
16518 "templates/businesses/fragments/business_admin_services_body.html",
16519 serde_json::Map::from_iter([
16520 (
16521 "service_rows_html".to_owned(),
16522 Value::String(service_rows_html),
16523 ),
16524 (
16525 "service_create_path".to_owned(),
16526 Value::String(format!("/businesses/{business_slug}/services/new/")),
16527 ),
16528 (
16529 "business_admin_service_name_input_ui_html".to_owned(),
16530 Value::String(render_compact_input_fragment(
16531 app_root,
16532 &format!("service-manage-name-{business_slug}"),
16533 "text",
16534 "name",
16535 "Service name",
16536 "",
16537 "form-control",
16538 " required",
16539 )?),
16540 ),
16541 (
16542 "business_admin_service_cost_input_ui_html".to_owned(),
16543 Value::String(render_compact_input_fragment(
16544 app_root,
16545 &format!("service-manage-cost-{business_slug}"),
16546 "number",
16547 "cost",
16548 "Cost",
16549 "",
16550 "form-control",
16551 " min=\"0\" step=\"0.01\"",
16552 )?),
16553 ),
16554 (
16555 "business_admin_service_duration_input_ui_html".to_owned(),
16556 Value::String(render_compact_input_fragment(
16557 app_root,
16558 &format!("service-manage-duration-{business_slug}"),
16559 "number",
16560 "duration_minutes",
16561 "Duration",
16562 "45",
16563 "form-control",
16564 " min=\"1\" step=\"1\"",
16565 )?),
16566 ),
16567 (
16568 "business_admin_service_sort_order_select_ui_html".to_owned(),
16569 Value::String(render_compact_select_fragment(
16570 app_root,
16571 &format!("service-manage-order-{business_slug}"),
16572 "sort_order",
16573 "Order",
16574 "form-control",
16575 "",
16576 service_sort_order_options_html(app_root, root, business_id, 0, true)?,
16577 )?),
16578 ),
16579 (
16580 "business_admin_service_description_textarea_ui_html".to_owned(),
16581 Value::String(render_textarea_fragment(
16582 app_root,
16583 &format!("service-manage-description-{business_slug}"),
16584 "description",
16585 "Description",
16586 "",
16587 "form-control",
16588 " rows=\"4\" aria-label=\"Description\"",
16589 )?),
16590 ),
16591 (
16592 "business_admin_service_image_input_ui_html".to_owned(),
16593 Value::String(render_fragment_with_params(
16594 app_root,
16595 "templates/shared/fragments/ui_input.html",
16596 serde_json::Map::from_iter([
16597 (
16598 "id".to_owned(),
16599 Value::String("service-create-image".to_owned()),
16600 ),
16601 ("input_type".to_owned(), Value::String("file".to_owned())),
16602 ("name".to_owned(), Value::String("service_image".to_owned())),
16603 (
16604 "label".to_owned(),
16605 Value::String("Service image".to_owned()),
16606 ),
16607 (
16608 "label_class".to_owned(),
16609 Value::String("sr-only".to_owned()),
16610 ),
16611 ("class_name".to_owned(), Value::String(String::new())),
16612 ("value".to_owned(), Value::String(String::new())),
16613 (
16614 "attributes_html".to_owned(),
16615 Value::String(" accept=\"image/*\" hidden".to_owned()),
16616 ),
16617 ("help_html".to_owned(), Value::String(String::new())),
16618 ]),
16619 )?),
16620 ),
16621 (
16622 "business_admin_service_submit_button_ui_html".to_owned(),
16623 Value::String(render_icon_button_fragment(
16624 app_root,
16625 &format!("service-manage-submit-{business_slug}"),
16626 "submit",
16627 "btn btn-success btn-icon btn-icon-lg business-admin-save-button",
16628 " title=\"Save Service\" aria-label=\"Save Service\"",
16629 "templates/shared/fragments/ui_content_save_service.html",
16630 serde_json::Map::new(),
16631 )?),
16632 ),
16633 (
16634 "service_create_technicians_empty_style".to_owned(),
16635 Value::String("display:none;".to_owned()),
16636 ),
16637 (
16638 "service_create_technicians_empty_message".to_owned(),
16639 Value::String("No technicians yet.".to_owned()),
16640 ),
16641 (
16642 "service_create_technician_assignment_rows_html".to_owned(),
16643 Value::String(service_create_technician_rows_html),
16644 ),
16645 ]),
16646 )?;
16647 render_fragment_with_params(
16648 app_root,
16649 "templates/businesses/fragments/business_admin_services_manage.html",
16650 serde_json::Map::from_iter([
16651 (
16652 "availability_path".to_owned(),
16653 Value::String(availability_path),
16654 ),
16655 (
16656 "services_body_html".to_owned(),
16657 Value::String(services_body_html),
16658 ),
16659 ]),
16660 )
16661}
16662
16663/// Performs dynamic business detail admin page params from root JSON.
16664///
16665/// # Parameters
16666///
16667/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_detail_admin_page_params_from_root_json`].
16668/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_detail_admin_page_params_from_root_json`].
16669/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_detail_admin_page_params_from_root_json`].
16670///
16671/// # Returns
16672///
16673/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_detail_admin_page_params_from_root_json`] can produce a value and [`None`] otherwise.
16674///
16675/// # Performance
16676///
16677/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_business_detail_admin_page_params_from_root_json`].
16678///
16679/// # Examples
16680///
16681/// ```rust,ignore
16682/// let _result = pharos_native::runtime::json_commands::dynamic_business_detail_admin_page_params_from_root_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
16683/// ```
16684///
16685/// # Related
16686///
16687/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
16688pub fn dynamic_business_detail_admin_page_params_from_root_json(
16689 root: &Value,
16690 business_slug: &str,
16691 app_root: &Path,
16692) -> Option<String> {
16693 if std::env::var("PHAROS_TRACE_ADMIN_RENDER").ok().as_deref() == Some("1") {
16694 eprintln!("TRACE admin render start slug={business_slug}");
16695 }
16696 let business = bounded_state_array(root, "businesses")
16697 .iter()
16698 .find(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))?;
16699 let business_id = business.get("id").and_then(Value::as_i64)?;
16700 let business_name = business.get("name").map(value_to_text).unwrap_or_default();
16701 let landing_path = format!("/b/{business_slug}/");
16702 let calendar_path = format!("/business/{business_slug}/calendar/");
16703 let edit_path = format!("/businesses/{business_slug}/edit/");
16704 let toggle_path = format!("/businesses/{business_slug}/toggle-public/");
16705 let delete_path = format!("/businesses/{business_slug}/delete/");
16706 let invite_create_path = format!("/businesses/{business_slug}/invites/new/");
16707 let service_create_path = format!("/businesses/{business_slug}/services/new/");
16708 let technician_create_path = format!("/businesses/{business_slug}/technicians/new/");
16709 let availability_create_path = format!("/businesses/{business_slug}/availability/new/");
16710 let is_public = business
16711 .get("is_public")
16712 .and_then(Value::as_bool)
16713 .unwrap_or(false);
16714 let summary = root.get("business_summary")?;
16715 let appointment_count = summary.get("appointment_count")?.as_u64()?;
16716 let technician_count = summary.get("technician_count")?.as_u64()?;
16717 let unassigned_count = summary.get("unassigned_count")?.as_u64()?;
16718 let (payment_provider_id, payment_provider_label) =
16719 business_payment_provider_ui_config(app_root);
16720 let (payment_provider_status_label, payment_provider_status_detail) =
16721 business_payment_provider_status(
16722 root,
16723 business_id,
16724 &payment_provider_id,
16725 &payment_provider_label,
16726 );
16727 let (
16728 payment_provider_connect_path,
16729 payment_provider_refresh_path,
16730 payment_provider_disconnect_path,
16731 ) = business_payment_provider_paths(business_slug, &payment_provider_id);
16732 let connection_status =
16733 if state_find_google_calendar_connection_for_business_logic_local(root, business_id)
16734 .is_some()
16735 {
16736 "Connected"
16737 } else {
16738 "Disconnected"
16739 };
16740 let public_toggle_button =
16741 dynamic_render_business_admin_public_state_button_html(app_root, business_slug, is_public)?;
16742 if std::env::var("PHAROS_TRACE_ADMIN_RENDER").ok().as_deref() == Some("1") {
16743 eprintln!("TRACE admin render public_toggle_button ok");
16744 }
16745 let delete_open_button = render_icon_button_fragment(
16746 app_root,
16747 "business_admin_delete_dialog_open_button",
16748 "button",
16749 "btn btn-danger btn-icon btn-icon-lg",
16750 " title=\"Delete Business\" aria-label=\"Delete Business\" data-dialog-open=\"business-delete-dialog\"",
16751 "templates/shared/fragments/ui_content_delete_business.html",
16752 serde_json::Map::new(),
16753 )?;
16754 let toolbar_html = render_fragment_with_params(
16755 app_root,
16756 "templates/businesses/fragments/business_admin_summary_toolbar.html",
16757 serde_json::Map::from_iter([
16758 (
16759 "landing_path".to_owned(),
16760 Value::String(landing_path.clone()),
16761 ),
16762 (
16763 "calendar_path".to_owned(),
16764 Value::String(calendar_path.clone()),
16765 ),
16766 ("edit_path".to_owned(), Value::String(edit_path.clone())),
16767 ("toggle_path".to_owned(), Value::String(toggle_path.clone())),
16768 (
16769 "business_public_state_toggle_button_ui_html".to_owned(),
16770 Value::String(public_toggle_button),
16771 ),
16772 (
16773 "business_admin_delete_dialog_open_button_ui_html".to_owned(),
16774 Value::String(delete_open_button),
16775 ),
16776 ]),
16777 )?;
16778 if std::env::var("PHAROS_TRACE_ADMIN_RENDER").ok().as_deref() == Some("1") {
16779 eprintln!("TRACE admin render toolbar ok");
16780 }
16781 let mut summary_body_params = serde_json::Map::from_iter([
16782 (
16783 "public_url_path".to_owned(),
16784 Value::String(landing_path.clone()),
16785 ),
16786 (
16787 "payment_provider_status_label".to_owned(),
16788 Value::String(payment_provider_status_label),
16789 ),
16790 (
16791 "payment_provider_status_detail".to_owned(),
16792 Value::String(payment_provider_status_detail),
16793 ),
16794 (
16795 "connection_status".to_owned(),
16796 Value::String(connection_status.to_owned()),
16797 ),
16798 (
16799 "appointment_count".to_owned(),
16800 Value::String(appointment_count.to_string()),
16801 ),
16802 (
16803 "unassigned_count".to_owned(),
16804 Value::String(unassigned_count.to_string()),
16805 ),
16806 (
16807 "technician_count".to_owned(),
16808 Value::String(technician_count.to_string()),
16809 ),
16810 (
16811 "payment_provider_setup_path".to_owned(),
16812 Value::String(format!("/businesses/{business_slug}/connect-stripe/")),
16813 ),
16814 (
16815 "payment_provider_connect_path".to_owned(),
16816 Value::String(payment_provider_connect_path),
16817 ),
16818 (
16819 "payment_provider_refresh_path".to_owned(),
16820 Value::String(payment_provider_refresh_path),
16821 ),
16822 (
16823 "payment_provider_disconnect_path".to_owned(),
16824 Value::String(payment_provider_disconnect_path),
16825 ),
16826 (
16827 "business_admin_refresh_payment_provider_button_ui_html".to_owned(),
16828 Value::String(payment_provider_action_button_html(
16829 app_root,
16830 "business_admin_refresh_payment_provider_button",
16831 "Refresh status",
16832 "btn btn-secondary",
16833 )?),
16834 ),
16835 (
16836 "business_admin_disconnect_payment_provider_button_ui_html".to_owned(),
16837 Value::String(payment_provider_action_button_html(
16838 app_root,
16839 "business_admin_disconnect_payment_provider_button",
16840 "Disconnect",
16841 "btn btn-danger",
16842 )?),
16843 ),
16844 (
16845 "business_admin_google_mode_input_ui_html".to_owned(),
16846 Value::String(render_hidden_input_fragment(
16847 app_root,
16848 "business_admin_google_mode_input",
16849 "mode",
16850 "business",
16851 )?),
16852 ),
16853 (
16854 "business_admin_google_slug_input_ui_html".to_owned(),
16855 Value::String(render_hidden_input_fragment(
16856 app_root,
16857 "business_admin_google_slug_input",
16858 "business_slug",
16859 business_slug,
16860 )?),
16861 ),
16862 (
16863 "business_admin_google_next_input_ui_html".to_owned(),
16864 Value::String(render_hidden_input_fragment(
16865 app_root,
16866 "business_admin_google_next_input",
16867 "next",
16868 &format!("/businesses/{business_slug}/"),
16869 )?),
16870 ),
16871 (
16872 "business_admin_connect_google_button_ui_html".to_owned(),
16873 Value::String(render_fragment_with_params(
16874 app_root,
16875 "templates/shared/fragments/ui_button.html",
16876 Map::from_iter([
16877 (
16878 "id".to_owned(),
16879 Value::String("business_admin_connect_google_button".to_owned()),
16880 ),
16881 ("button_type".to_owned(), Value::String("submit".to_owned())),
16882 (
16883 "class_name".to_owned(),
16884 Value::String("btn btn-primary".to_owned()),
16885 ),
16886 ("attributes_html".to_owned(), Value::String(String::new())),
16887 (
16888 "content_html".to_owned(),
16889 Value::String("Connect Google Calendar".to_owned()),
16890 ),
16891 ]),
16892 )?),
16893 ),
16894 ]);
16895 for (key, value) in super::payment_provider::payment_provider_template_params(
16896 root,
16897 app_root,
16898 business_slug,
16899 &payment_provider_id,
16900 ) {
16901 summary_body_params.insert(key, value);
16902 }
16903 for key in [
16904 "owner_payment_provider_status_label",
16905 "owner_payment_provider_status_icon",
16906 "owner_payment_provider_mode_demo_label",
16907 "owner_payment_provider_mode_live_label",
16908 "owner_payment_provider_credentials_state_label",
16909 "owner_payment_provider_setup_eyebrow",
16910 "owner_payment_provider_credentials_headline",
16911 "owner_payment_provider_credentials_body",
16912 "owner_payment_provider_client_id_label",
16913 "owner_payment_provider_client_id_placeholder",
16914 "owner_payment_provider_client_secret_label",
16915 "owner_payment_provider_client_secret_placeholder",
16916 "owner_payment_provider_save_credentials_label",
16917 "owner_payment_provider_delete_credentials_label",
16918 "owner_payment_provider_connect_label",
16919 "owner_payment_provider_refresh_label",
16920 ] {
16921 if let Some(value) = super::payment_provider::payment_provider_copy_value(app_root, key) {
16922 summary_body_params.insert(key.to_owned(), Value::String(value));
16923 }
16924 }
16925 summary_body_params.insert(
16926 "owner_payment_provider_action_href".to_owned(),
16927 Value::String(format!(
16928 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/test/"
16929 )),
16930 );
16931 summary_body_params.insert(
16932 "owner_payment_provider_refresh_href".to_owned(),
16933 Value::String(format!(
16934 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/refresh/"
16935 )),
16936 );
16937 summary_body_params.insert(
16938 "owner_payment_provider_credentials_href".to_owned(),
16939 Value::String(format!(
16940 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/credentials/"
16941 )),
16942 );
16943 summary_body_params.insert(
16944 "owner_payment_provider_credentials_delete_href".to_owned(),
16945 Value::String(format!(
16946 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/credentials/delete/"
16947 )),
16948 );
16949 summary_body_params.insert(
16950 "owner_payment_provider_mode_href".to_owned(),
16951 Value::String(format!(
16952 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/mode/"
16953 )),
16954 );
16955 summary_body_params.insert(
16956 "payment_provider_feedback_message".to_owned(),
16957 Value::String(String::new()),
16958 );
16959 let summary_body_html = render_fragment_with_params(
16960 app_root,
16961 "templates/businesses/fragments/business_admin_summary_body.html",
16962 summary_body_params,
16963 )?;
16964 if std::env::var("PHAROS_TRACE_ADMIN_RENDER").ok().as_deref() == Some("1") {
16965 eprintln!("TRACE admin render summary_body ok");
16966 }
16967 let invite_body_html = render_fragment_with_params(
16968 app_root,
16969 "templates/businesses/fragments/business_admin_invite_body.html",
16970 serde_json::Map::from_iter([
16971 (
16972 "invite_create_path".to_owned(),
16973 Value::String(invite_create_path.clone()),
16974 ),
16975 (
16976 "business_admin_invite_email_input_ui_html".to_owned(),
16977 Value::String(render_compact_input_fragment(
16978 app_root,
16979 "business_admin_invite_email_input",
16980 "email",
16981 "email",
16982 "Email",
16983 "",
16984 "form-control form-control-compact",
16985 " placeholder=\"Email\" autocomplete=\"email\" aria-label=\"Email\" required",
16986 )?),
16987 ),
16988 (
16989 "business_admin_invite_account_type_select_ui_html".to_owned(),
16990 Value::String(render_compact_select_fragment(
16991 app_root,
16992 "business_admin_invite_account_type_select",
16993 "account_type",
16994 "Account type",
16995 "form-control form-control-compact",
16996 " aria-label=\"Account type\"",
16997 [
16998 ("business_customer", "Customer", true),
16999 ("business_manager", "Manager", false),
17000 ("business_technician", "Technician", false),
17001 ]
17002 .into_iter()
17003 .map(|(value, label, selected)| {
17004 render_fragment_with_params(
17005 app_root,
17006 "templates/shared/fragments/select_option.html",
17007 Map::from_iter([
17008 ("option_value".to_owned(), Value::String(value.to_owned())),
17009 ("option_label".to_owned(), Value::String(label.to_owned())),
17010 (
17011 "selected_attr".to_owned(),
17012 Value::String(
17013 if selected { " selected" } else { "" }.to_owned(),
17014 ),
17015 ),
17016 ]),
17017 )
17018 })
17019 .collect::<Option<Vec<_>>>()?
17020 .join(""),
17021 )?),
17022 ),
17023 (
17024 "business_admin_invite_submit_button_ui_html".to_owned(),
17025 Value::String(render_icon_button_fragment(
17026 app_root,
17027 "business_admin_invite_submit_button",
17028 "submit",
17029 "btn btn-success btn-icon btn-icon-sm business-admin-save-button",
17030 " title=\"Send\" aria-label=\"Send\"",
17031 "templates/shared/fragments/ui_content_create_invite.html",
17032 serde_json::Map::new(),
17033 )?),
17034 ),
17035 ]),
17036 )?;
17037 let top_grid_html = render_fragment_with_params(
17038 app_root,
17039 "templates/businesses/fragments/business_admin_top_grid.html",
17040 Map::from_iter([
17041 (
17042 "business_name".to_owned(),
17043 Value::String(business_name.clone()),
17044 ),
17045 ("toolbar_html".to_owned(), Value::String(toolbar_html)),
17046 (
17047 "summary_body_html".to_owned(),
17048 Value::String(summary_body_html),
17049 ),
17050 (
17051 "invite_body_html".to_owned(),
17052 Value::String(invite_body_html),
17053 ),
17054 ]),
17055 )?;
17056 let service_rows_html =
17057 dynamic_business_admin_service_cards_html(root, business_slug, app_root)?;
17058 let service_create_technician_rows_html =
17059 technician_checkboxes_html(app_root, root, business_id, 0, "No technicians yet.", true)
17060 .or_else(|| {
17061 render_fragment_with_params(
17062 app_root,
17063 "templates/shared/fragments/muted_text.html",
17064 Map::from_iter([(
17065 "message".to_owned(),
17066 Value::String("No technicians yet.".to_owned()),
17067 )]),
17068 )
17069 })?;
17070 let services_panel_html = render_workspace_panel_html_business_logic_local(
17071 app_root,
17072 "card business-admin-services-card",
17073 "business-admin-services-header",
17074 "Services",
17075 "",
17076 &render_fragment_with_params(
17077 app_root,
17078 "templates/businesses/fragments/business_admin_services_body.html",
17079 serde_json::Map::from_iter([
17080 (
17081 "service_rows_html".to_owned(),
17082 Value::String(service_rows_html),
17083 ),
17084 (
17085 "service_create_path".to_owned(),
17086 Value::String(service_create_path),
17087 ),
17088 (
17089 "business_admin_service_name_input_ui_html".to_owned(),
17090 Value::String(render_compact_input_fragment(
17091 app_root,
17092 "business_admin_service_name_input",
17093 "text",
17094 "name",
17095 "Name",
17096 "",
17097 "form-control form-control-compact",
17098 " placeholder=\"Name\" autocomplete=\"false\" aria-label=\"Name\" required",
17099 )?),
17100 ),
17101 (
17102 "business_admin_service_cost_input_ui_html".to_owned(),
17103 Value::String(render_compact_input_fragment(
17104 app_root,
17105 "business_admin_service_cost_input",
17106 "text",
17107 "cost",
17108 "Cost",
17109 "",
17110 "form-control form-control-compact",
17111 " placeholder=\"Cost\" autocomplete=\"false\" aria-label=\"Cost\" inputmode=\"decimal\" required",
17112 )?),
17113 ),
17114 (
17115 "business_admin_service_duration_input_ui_html".to_owned(),
17116 Value::String(render_compact_input_fragment(
17117 app_root,
17118 "business_admin_service_duration_input",
17119 "number",
17120 "duration_minutes",
17121 "Duration",
17122 "45",
17123 "form-control form-control-compact",
17124 " min=\"1\" max=\"999\" placeholder=\"Time\" autocomplete=\"false\" aria-label=\"Duration\" required",
17125 )?),
17126 ),
17127 (
17128 "business_admin_service_sort_order_select_ui_html".to_owned(),
17129 Value::String(render_compact_select_fragment(
17130 app_root,
17131 "service-create-sort-order",
17132 "sort_order",
17133 "Display Order",
17134 "form-control form-control-compact",
17135 " aria-label=\"Display Order\" required",
17136 service_sort_order_options_html(app_root, root, business_id, 0, true)?,
17137 )?),
17138 ),
17139 (
17140 "business_admin_service_description_textarea_ui_html".to_owned(),
17141 Value::String(render_textarea_fragment(
17142 app_root,
17143 &format!("service-create-description-{business_slug}"),
17144 "description",
17145 "Description",
17146 "",
17147 "form-control",
17148 " rows=\"4\" aria-label=\"Description\"",
17149 )?),
17150 ),
17151 (
17152 "business_admin_service_image_input_ui_html".to_owned(),
17153 Value::String(render_fragment_with_params(
17154 app_root,
17155 "templates/shared/fragments/ui_input.html",
17156 serde_json::Map::from_iter([
17157 (
17158 "id".to_owned(),
17159 Value::String("service-create-image".to_owned()),
17160 ),
17161 ("input_type".to_owned(), Value::String("file".to_owned())),
17162 (
17163 "name".to_owned(),
17164 Value::String("service_image".to_owned()),
17165 ),
17166 (
17167 "label".to_owned(),
17168 Value::String("Service image".to_owned()),
17169 ),
17170 (
17171 "label_class".to_owned(),
17172 Value::String("sr-only".to_owned()),
17173 ),
17174 (
17175 "class_name".to_owned(),
17176 Value::String("sr-only business-edit-hidden-input".to_owned()),
17177 ),
17178 ("value".to_owned(), Value::String(String::new())),
17179 (
17180 "attributes_html".to_owned(),
17181 Value::String(" accept=\"image/*\" data-file-input-label=\"service-create-image-file-name\"".to_owned()),
17182 ),
17183 ("help_html".to_owned(), Value::String(String::new())),
17184 ]),
17185 )?),
17186 ),
17187 (
17188 "business_admin_service_submit_button_ui_html".to_owned(),
17189 Value::String(render_icon_button_fragment(
17190 app_root,
17191 "business_admin_service_submit_button",
17192 "submit",
17193 "btn btn-success btn-icon btn-icon-sm business-admin-save-button",
17194 " title=\"Save Service\" aria-label=\"Save Service\"",
17195 "templates/shared/fragments/ui_content_save_service.html",
17196 serde_json::Map::new(),
17197 )?),
17198 ),
17199 (
17200 "service_create_technicians_empty_style".to_owned(),
17201 Value::String("display:none;".to_owned()),
17202 ),
17203 (
17204 "service_create_technicians_empty_message".to_owned(),
17205 Value::String("No technicians yet.".to_owned()),
17206 ),
17207 (
17208 "service_create_technician_assignment_rows_html".to_owned(),
17209 Value::String(service_create_technician_rows_html),
17210 ),
17211 ]),
17212 )?,
17213 )?;
17214 let availability_rows_html =
17215 dynamic_render_business_admin_availability_rows_html(root, business_slug, app_root)?;
17216 let availability_body_html = render_fragment_with_params(
17217 app_root,
17218 "templates/businesses/fragments/business_admin_availability_body.html",
17219 serde_json::Map::from_iter([
17220 (
17221 "availability_rows_html".to_owned(),
17222 Value::String(availability_rows_html),
17223 ),
17224 (
17225 "availability_create_path".to_owned(),
17226 Value::String(availability_create_path.clone()),
17227 ),
17228 (
17229 "business_admin_availability_day_select_ui_html".to_owned(),
17230 Value::String(render_compact_select_fragment(
17231 app_root,
17232 &format!("availability-day-{business_slug}"),
17233 "day_of_week",
17234 "Day",
17235 "form-control form-control-compact availability-inline-input",
17236 " autocomplete=\"off\"",
17237 render_day_select_options(app_root, "Mon", true)?,
17238 )?),
17239 ),
17240 (
17241 "business_admin_availability_start_time_input_ui_html".to_owned(),
17242 Value::String(render_compact_input_fragment(
17243 app_root,
17244 &format!("availability-create-start-{business_slug}"),
17245 "time",
17246 "start_time",
17247 "Start",
17248 "09:00",
17249 "form-control form-control-compact availability-inline-input",
17250 " autocomplete=\"off\"",
17251 )?),
17252 ),
17253 (
17254 "business_admin_availability_end_time_input_ui_html".to_owned(),
17255 Value::String(render_compact_input_fragment(
17256 app_root,
17257 &format!("availability-create-end-{business_slug}"),
17258 "time",
17259 "end_time",
17260 "End",
17261 "17:00",
17262 "form-control form-control-compact availability-inline-input",
17263 " autocomplete=\"off\"",
17264 )?),
17265 ),
17266 (
17267 "business_admin_availability_submit_button_ui_html".to_owned(),
17268 Value::String(render_icon_button_fragment(
17269 app_root,
17270 &format!("availability-submit-{business_slug}"),
17271 "submit",
17272 "btn btn-success btn-icon btn-icon-lg business-admin-save-button",
17273 "",
17274 "templates/shared/fragments/ui_content_apply_hours.html",
17275 serde_json::Map::new(),
17276 )?),
17277 ),
17278 ]),
17279 )?;
17280 let availability_panel_html = render_workspace_panel_html_business_logic_local(
17281 app_root,
17282 "card business-admin-half-card business-admin-availability-card",
17283 "business-admin-services-header",
17284 "Availability",
17285 "",
17286 &availability_body_html,
17287 )?;
17288 let technician_body_html = render_fragment_with_params(
17289 app_root,
17290 "templates/businesses/fragments/business_admin_technicians_body.html",
17291 serde_json::Map::from_iter([
17292 (
17293 "technician_rows_html".to_owned(),
17294 Value::String(
17295 super::business::dynamic_business_admin_technician_rows_html_from_root(
17296 root,
17297 business_slug,
17298 app_root,
17299 )?,
17300 ),
17301 ),
17302 (
17303 "technician_create_path".to_owned(),
17304 Value::String(technician_create_path),
17305 ),
17306 (
17307 "business_admin_technician_name_input_ui_html".to_owned(),
17308 Value::String(render_compact_input_fragment(
17309 app_root,
17310 "business_admin_technician_name_input",
17311 "text",
17312 "name",
17313 "Name",
17314 "",
17315 "form-control form-control-compact",
17316 " placeholder=\"Name\" autocomplete=\"name\" aria-label=\"Name\" required",
17317 )?),
17318 ),
17319 (
17320 "business_admin_technician_email_input_ui_html".to_owned(),
17321 Value::String(render_compact_input_fragment(
17322 app_root,
17323 "business_admin_technician_email_input",
17324 "email",
17325 "email",
17326 "Email",
17327 "",
17328 "form-control form-control-compact",
17329 " placeholder=\"Email\" autocomplete=\"email\" aria-label=\"Email\" required",
17330 )?),
17331 ),
17332 (
17333 "business_admin_technician_submit_button_ui_html".to_owned(),
17334 Value::String(render_icon_button_fragment(
17335 app_root,
17336 "business_admin_technician_submit_button",
17337 "button",
17338 "btn btn-success btn-icon btn-icon-sm business-admin-save-button business-admin-technician-add-button",
17339 &format!(
17340 " title=\"Add Technician\" aria-label=\"Add Technician\" hx-post=\"/businesses/{business_slug}/technicians/new/\" hx-include=\"closest form\" hx-target=\"#business-technician-rows\" hx-swap=\"innerHTML\""
17341 ),
17342 "templates/shared/fragments/ui_content_add_technician.html",
17343 serde_json::Map::new(),
17344 )?),
17345 ),
17346 ]),
17347 )?;
17348 let technician_panel_html = render_workspace_panel_html_business_logic_local(
17349 app_root,
17350 "card business-admin-half-card business-admin-sticky-card",
17351 "business-admin-services-header",
17352 "Technicians",
17353 "",
17354 &technician_body_html,
17355 )?;
17356 let lower_grid_html = render_workspace_split_grid_html_business_logic_local(
17357 app_root,
17358 "business-admin-half-grid",
17359 &availability_panel_html,
17360 &technician_panel_html,
17361 )?;
17362 let delete_dialog_html = render_workspace_dialog_html_business_logic_local(
17363 app_root,
17364 "business-delete-dialog",
17365 "business-delete-dialog",
17366 "business-delete-dialog-shell",
17367 "business-delete-dialog-copy",
17368 &render_fragment_with_params(
17369 app_root,
17370 "templates/businesses/fragments/business_admin_delete_dialog_copy.html",
17371 serde_json::Map::from_iter([(
17372 "business_name".to_owned(),
17373 Value::String(business_name.clone()),
17374 )]),
17375 )?,
17376 "business-delete-dialog-actions",
17377 &render_fragment_with_params(
17378 app_root,
17379 "templates/businesses/fragments/business_admin_delete_dialog_actions.html",
17380 serde_json::Map::from_iter([
17381 ("delete_path".to_owned(), Value::String(delete_path)),
17382 (
17383 "business_admin_delete_confirm_button_ui_html".to_owned(),
17384 Value::String(render_icon_button_fragment(
17385 app_root,
17386 "business_admin_delete_confirm_button",
17387 "submit",
17388 "btn btn-danger btn-icon btn-icon-lg",
17389 " title=\"Delete Business\" aria-label=\"Delete Business\"",
17390 "templates/shared/fragments/ui_content_delete_business.html",
17391 serde_json::Map::new(),
17392 )?),
17393 ),
17394 (
17395 "business_admin_delete_cancel_button_ui_html".to_owned(),
17396 Value::String(render_icon_button_fragment(
17397 app_root,
17398 "business_admin_delete_cancel_button",
17399 "button",
17400 "btn btn-secondary btn-icon btn-icon-lg",
17401 " title=\"Cancel\" aria-label=\"Cancel\" data-dialog-close=\"true\"",
17402 "templates/shared/fragments/ui_content_cancel.html",
17403 serde_json::Map::new(),
17404 )?),
17405 ),
17406 ]),
17407 )?,
17408 )?;
17409 compact(&Value::Object(serde_json::Map::from_iter([
17410 ("business_name".to_owned(), Value::String(business_name)),
17411 ("top_grid_html".to_owned(), Value::String(top_grid_html)),
17412 (
17413 "services_panel_html".to_owned(),
17414 Value::String(services_panel_html),
17415 ),
17416 ("lower_grid_html".to_owned(), Value::String(lower_grid_html)),
17417 (
17418 "delete_dialog_html".to_owned(),
17419 Value::String(delete_dialog_html),
17420 ),
17421 ])))
17422}
17423
17424/// Builds business calendar page params from a loaded state root.
17425///
17426/// # Parameters
17427///
17428/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`].
17429/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`].
17430/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`].
17431/// - `year`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`].
17432/// - `month`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`].
17433/// - `date`: Input consumed by [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`].
17434///
17435/// # Returns
17436///
17437/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`] can produce a value and [`None`] otherwise.
17438///
17439/// # Performance
17440///
17441/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json`].
17442///
17443/// # Examples
17444///
17445/// ```rust,ignore
17446/// let _result = pharos_native::runtime::json_commands::dynamic_business_calendar_template_params_from_root_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
17447/// ```
17448///
17449/// # Related
17450///
17451/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
17452pub fn dynamic_business_calendar_template_params_from_root_json(
17453 root: &Value,
17454 business_slug: &str,
17455 app_root: &Path,
17456 year: Option<&str>,
17457 month: Option<&str>,
17458 date: Option<&str>,
17459 _actor_user_id: i64,
17460 csrf_token: &str,
17461) -> Option<String> {
17462 if std::env::var("PHAROS_TRACE_CALENDAR_RENDER")
17463 .ok()
17464 .as_deref()
17465 == Some("1")
17466 {
17467 eprintln!(
17468 "TRACE calendar render start slug={business_slug} year={:?} month={:?} date={:?}",
17469 year, month, date
17470 );
17471 }
17472 let business = bounded_state_array(root, "businesses")
17473 .iter()
17474 .find(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))?;
17475 let business_id = business.get("id").and_then(Value::as_i64)?;
17476 let business_name = business.get("name").map(value_to_text).unwrap_or_default();
17477 let availability = bounded_state_array(root, "business_availability");
17478 let appointments = bounded_state_array(root, "appointments");
17479 let requested_date = date
17480 .filter(|value| value.len() == 10 && value.as_bytes().get(4) == Some(&b'-'))
17481 .map(ToOwned::to_owned);
17482 let selected_year = year
17483 .and_then(|value| value.parse::<i32>().ok())
17484 .filter(|value| (2000..=2100).contains(value))
17485 .or_else(|| {
17486 requested_date
17487 .as_deref()
17488 .and_then(|value| value.get(0..4))
17489 .and_then(|value| value.parse().ok())
17490 });
17491 let selected_month = month
17492 .and_then(|value| value.parse::<u32>().ok())
17493 .filter(|value| (1..=12).contains(value))
17494 .or_else(|| {
17495 requested_date
17496 .as_deref()
17497 .and_then(|value| value.get(5..7))
17498 .and_then(|value| value.parse().ok())
17499 });
17500 let now = pharos_runtime_core::time::current_timestamp_utc();
17501 let today = now.get(..10)?.to_owned();
17502 let current_year = today.get(..4)?.parse::<i32>().ok()?;
17503 let current_month = today.get(5..7)?.parse::<u32>().ok()?;
17504 let fallback_date = if selected_year.is_none() && selected_month.is_none() {
17505 today
17506 } else {
17507 format!(
17508 "{:04}-{:02}-01",
17509 selected_year.unwrap_or(current_year),
17510 selected_month.unwrap_or(current_month)
17511 )
17512 };
17513 let selected_date = requested_date.unwrap_or(fallback_date);
17514 let selected_year = selected_year
17515 .or_else(|| selected_date.get(0..4).and_then(|value| value.parse().ok()))
17516 .unwrap_or(current_year);
17517 let selected_month = selected_month
17518 .or_else(|| selected_date.get(5..7).and_then(|value| value.parse().ok()))
17519 .unwrap_or(current_month);
17520 let month_label = [
17521 "January",
17522 "February",
17523 "March",
17524 "April",
17525 "May",
17526 "June",
17527 "July",
17528 "August",
17529 "September",
17530 "October",
17531 "November",
17532 "December",
17533 ][selected_month.saturating_sub(1) as usize]
17534 .to_owned();
17535 let days_in_month = days_in_month_business_logic_local(selected_year, selected_month);
17536 let first_day = day_abbrev_for_ymd_business_logic_local(selected_year, selected_month, 1)?;
17537 let first_index = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
17538 .iter()
17539 .position(|day| *day == first_day)
17540 .unwrap_or(0);
17541 let calendar_base_path = format!("/business/{business_slug}/calendar/");
17542 let mut calendar_rows_html = String::new();
17543 for week in 0..6 {
17544 let mut cells_html = String::new();
17545 for column in 0..7 {
17546 let grid_index = week * 7 + column;
17547 let day_number = grid_index - first_index as i32 + 1;
17548 if day_number < 1 || day_number > days_in_month as i32 {
17549 cells_html.push_str(&render_fragment_with_params(
17550 app_root,
17551 "templates/businesses/fragments/business_calendar_empty_day_cell.html",
17552 serde_json::Map::from_iter([(
17553 "cell_class".to_owned(),
17554 Value::String("day".to_owned()),
17555 )]),
17556 )?);
17557 continue;
17558 }
17559 let day_number = day_number as u32;
17560 let date_value = format!("{selected_year:04}-{selected_month:02}-{day_number:02}");
17561 let weekday =
17562 day_abbrev_for_ymd_business_logic_local(selected_year, selected_month, day_number)?;
17563 let day_schedule: Vec<&Value> = availability
17564 .iter()
17565 .filter(|row| row.get("business_id").and_then(Value::as_i64) == Some(business_id))
17566 .filter(|row| {
17567 business_day_matches_group(
17568 row.get("day_of_week").and_then(Value::as_str).unwrap_or(""),
17569 weekday,
17570 )
17571 })
17572 .collect();
17573 let day_has_booking = appointments.iter().any(|row| {
17574 row_i64_value(row, "business_id") == Some(business_id)
17575 && row_date_time(row).0 == date_value
17576 });
17577
17578 cells_html.push_str(&render_fragment_with_params(
17579 app_root,
17580 "templates/businesses/fragments/business_calendar_day_cell.html",
17581 serde_json::Map::from_iter([
17582 (
17583 "day_cell_classes".to_owned(),
17584 Value::String(
17585 [
17586 (date_value == selected_date).then_some("today"),
17587 day_has_booking.then_some("has-appointments"),
17588 ]
17589 .into_iter()
17590 .flatten()
17591 .map(|class_name| format!(" {class_name}"))
17592 .collect::<String>(),
17593 ),
17594 ),
17595 (
17596 "day_path".to_owned(),
17597 Value::String(format!(
17598 "{calendar_base_path}?year={selected_year}&month={selected_month:02}&date={date_value}"
17599 )),
17600 ),
17601 (
17602 "day_number".to_owned(),
17603 Value::String(day_number.to_string()),
17604 ),
17605 (
17606 "day_chip_style".to_owned(),
17607 Value::String(if date_value == selected_date {
17608 "background:var(--primary);color:#fff;".to_owned()
17609 } else if day_has_booking {
17610 "background:#dcfce7;color:#166534;".to_owned()
17611 } else {
17612 "background:var(--gray-200);color:var(--gray-700);".to_owned()
17613 }),
17614 ),
17615 ("appointment_summary".to_owned(), Value::String(String::new())),
17616 (
17617 "availability_color".to_owned(),
17618 Value::String(if day_schedule.is_empty() {
17619 String::new()
17620 } else {
17621 "var(--primary)".to_owned()
17622 }),
17623 ),
17624 (
17625 "availability_summary".to_owned(),
17626 Value::String(String::new()),
17627 ),
17628 ]),
17629 )?);
17630 }
17631 calendar_rows_html.push_str(&render_fragment_with_params(
17632 app_root,
17633 "templates/businesses/fragments/business_calendar_week_row.html",
17634 serde_json::Map::from_iter([("cells_html".to_owned(), Value::String(cells_html))]),
17635 )?);
17636 }
17637 let selected_weekday = selected_date
17638 .get(8..10)
17639 .and_then(|day| day.parse::<u32>().ok())
17640 .and_then(|day| {
17641 day_abbrev_for_ymd_business_logic_local(selected_year, selected_month, day)
17642 })?;
17643 let selected_schedule: Vec<&Value> = availability
17644 .iter()
17645 .filter(|row| row.get("business_id").and_then(Value::as_i64) == Some(business_id))
17646 .filter(|row| {
17647 business_day_matches_group(
17648 row.get("day_of_week").and_then(Value::as_str).unwrap_or(""),
17649 selected_weekday,
17650 )
17651 })
17652 .collect();
17653 let mut selected_schedule_rows_html = String::new();
17654 for row in &selected_schedule {
17655 selected_schedule_rows_html.push_str(&render_fragment_with_params(
17656 app_root,
17657 "templates/businesses/fragments/business_calendar_schedule_row.html",
17658 serde_json::Map::from_iter([
17659 (
17660 "start_time".to_owned(),
17661 Value::String(
17662 row.get("start_time")
17663 .map(value_to_text)
17664 .map(|value| formatted_clock_time_business_logic_local(&value))
17665 .unwrap_or_else(|| "9:00 AM".to_owned()),
17666 ),
17667 ),
17668 (
17669 "end_time".to_owned(),
17670 Value::String(
17671 row.get("end_time")
17672 .map(value_to_text)
17673 .map(|value| formatted_clock_time_business_logic_local(&value))
17674 .unwrap_or_else(|| "5:00 PM".to_owned()),
17675 ),
17676 ),
17677 ]),
17678 )?);
17679 }
17680 let default_booking_time = selected_schedule
17681 .first()
17682 .and_then(|row| row.get("start_time"))
17683 .map(value_to_text)
17684 .filter(|value| generated_surface_text_is_time(value))
17685 .map(|value| value.get(0..5).unwrap_or("09:00").to_owned())
17686 .unwrap_or_else(|| "09:00".to_owned());
17687 let mut service_options_html = String::new();
17688 let mut service_option_index = 0usize;
17689 for service in bounded_state_array(root, "services")
17690 .iter()
17691 .filter(|service| row_i64_value(service, "business_id") == Some(business_id))
17692 {
17693 let service_id = row_i64_value(service, "id").unwrap_or(0);
17694 if service_id <= 0 {
17695 continue;
17696 }
17697 let service_label = service
17698 .get("name")
17699 .map(value_to_text)
17700 .filter(|value| !value.is_empty())
17701 .unwrap_or_else(|| format!("Service {service_id}"));
17702 let duration_minutes = row_i64_value(service, "duration_minutes")
17703 .unwrap_or(45)
17704 .max(1);
17705 service_options_html.push_str(&render_fragment_with_params(
17706 app_root,
17707 "templates/businesses/fragments/business_calendar_option_with_duration.html",
17708 Map::from_iter([
17709 (
17710 "option_value".to_owned(),
17711 Value::String(service_id.to_string()),
17712 ),
17713 (
17714 "option_label".to_owned(),
17715 Value::String(format!("{service_label} · {duration_minutes} min")),
17716 ),
17717 (
17718 "duration_minutes".to_owned(),
17719 Value::String(duration_minutes.to_string()),
17720 ),
17721 (
17722 "booking_option_index".to_owned(),
17723 Value::String(String::new()),
17724 ),
17725 (
17726 "selected_attr".to_owned(),
17727 Value::String(if service_option_index == 0 {
17728 " selected".to_owned()
17729 } else {
17730 String::new()
17731 }),
17732 ),
17733 ]),
17734 )?);
17735 service_option_index = service_option_index.saturating_add(1);
17736 if let Some(booking_options) = service.get("booking_options").and_then(Value::as_array) {
17737 for (booking_option_index, booking_option) in booking_options.iter().enumerate() {
17738 let option_duration_minutes = row_i64_value(booking_option, "duration_minutes")
17739 .unwrap_or(duration_minutes)
17740 .max(1);
17741 let option_label = booking_option
17742 .get("label")
17743 .map(value_to_text)
17744 .filter(|value| !value.is_empty())
17745 .unwrap_or_else(|| format!("{option_duration_minutes} min"));
17746 service_options_html.push_str(&render_fragment_with_params(
17747 app_root,
17748 "templates/businesses/fragments/business_calendar_option_with_duration.html",
17749 Map::from_iter([
17750 (
17751 "option_value".to_owned(),
17752 Value::String(service_id.to_string()),
17753 ),
17754 (
17755 "option_label".to_owned(),
17756 Value::String(format!(
17757 "{service_label} · {option_label} · {option_duration_minutes} min"
17758 )),
17759 ),
17760 (
17761 "duration_minutes".to_owned(),
17762 Value::String(option_duration_minutes.to_string()),
17763 ),
17764 (
17765 "booking_option_index".to_owned(),
17766 Value::String(booking_option_index.to_string()),
17767 ),
17768 (
17769 "selected_attr".to_owned(),
17770 Value::String(if service_option_index == 0 {
17771 " selected".to_owned()
17772 } else {
17773 String::new()
17774 }),
17775 ),
17776 ]),
17777 )?);
17778 service_option_index = service_option_index.saturating_add(1);
17779 }
17780 }
17781 }
17782 let mut internal_technician_options_html = String::new();
17783 for technician in bounded_state_array(root, "technicians")
17784 .iter()
17785 .filter(|technician| row_i64_value(technician, "business_id") == Some(business_id))
17786 {
17787 let technician_id = row_i64_value(technician, "id").unwrap_or(0);
17788 if technician_id <= 0 {
17789 continue;
17790 }
17791 let technician_label = technician
17792 .get("name")
17793 .map(value_to_text)
17794 .filter(|value| !value.is_empty())
17795 .unwrap_or_else(|| format!("Technician {technician_id}"));
17796 let booked_slots = appointments
17797 .iter()
17798 .filter(|appointment| row_i64_value(appointment, "business_id") == Some(business_id))
17799 .filter(|appointment| {
17800 row_i64_value(appointment, "technician_id") == Some(technician_id)
17801 })
17802 .filter(|appointment| row_date_time(appointment).0 == selected_date)
17803 .filter(|appointment| {
17804 appointment.get("status").map(value_to_text).as_deref() != Some("cancelled")
17805 })
17806 .filter_map(|appointment| {
17807 let (_, booked_time) = row_date_time(appointment);
17808 let duration = appointment
17809 .get("service_duration_minutes")
17810 .and_then(Value::as_i64)
17811 .or_else(|| {
17812 row_i64_value(appointment, "service_id")
17813 .and_then(|service_id| {
17814 state_row_by_i64(root, "services", "id", service_id)
17815 })
17816 .and_then(|service| row_i64_value(service, "duration_minutes"))
17817 })
17818 .unwrap_or(45)
17819 .max(1);
17820 generated_surface_text_is_time(&booked_time)
17821 .then(|| format!("{}:{duration}", booked_time.get(0..5).unwrap_or("00:00")))
17822 })
17823 .collect::<Vec<_>>()
17824 .join(",");
17825 internal_technician_options_html.push_str(&render_fragment_with_params(
17826 app_root,
17827 "templates/businesses/fragments/business_calendar_technician_option.html",
17828 Map::from_iter([
17829 (
17830 "option_value".to_owned(),
17831 Value::String(technician_id.to_string()),
17832 ),
17833 ("option_label".to_owned(), Value::String(technician_label)),
17834 ("booked_slots".to_owned(), Value::String(booked_slots)),
17835 ("selected_attr".to_owned(), Value::String(String::new())),
17836 ]),
17837 )?);
17838 }
17839 let mut appointment_rows_html = String::new();
17840 for row in appointments
17841 .iter()
17842 .filter(|row| row_i64_value(row, "business_id") == Some(business_id))
17843 .filter(|row| row_date_time(row).0 == selected_date)
17844 {
17845 let appointment_id = row_i64_value(row, "id").unwrap_or(0);
17846 let (appointment_date, appointment_time) = row_date_time(row);
17847 let service = row_i64_value(row, "service_id")
17848 .and_then(|id| state_row_by_i64(root, "services", "id", id));
17849 let appointment_duration_minutes = row
17850 .get("service_duration_minutes")
17851 .and_then(Value::as_i64)
17852 .or_else(|| service.and_then(|service| row_i64_value(service, "duration_minutes")))
17853 .unwrap_or(45)
17854 .max(1);
17855 let appointment_end_time = row
17856 .get("end_time")
17857 .map(value_to_text)
17858 .and_then(|value| {
17859 value
17860 .get(11..16)
17861 .or_else(|| value.get(0..5))
17862 .map(ToOwned::to_owned)
17863 })
17864 .filter(|value| !value.is_empty())
17865 .or_else(|| {
17866 appointment_time_plus_minutes(&appointment_time, appointment_duration_minutes)
17867 })
17868 .unwrap_or_default();
17869 let technician = row_i64_value(row, "technician_id")
17870 .and_then(|id| state_row_by_i64(root, "technicians", "id", id));
17871 let customer =
17872 row_i64_value(row, "user_id").and_then(|id| state_row_by_i64(root, "users", "id", id));
17873 let appointment_type = service
17874 .and_then(|value| value.get("name"))
17875 .map(value_to_text)
17876 .filter(|value| !value.is_empty())
17877 .unwrap_or_else(|| "Appointment".to_owned());
17878 let technician_name = technician
17879 .and_then(|value| value.get("name"))
17880 .map(value_to_text)
17881 .filter(|value| !value.is_empty())
17882 .unwrap_or_else(|| "Unassigned".to_owned());
17883 let customer_name = customer
17884 .and_then(|value| value.get("full_name").or_else(|| value.get("display_name")))
17885 .map(value_to_text)
17886 .filter(|value| !value.is_empty())
17887 .unwrap_or_else(|| "Customer".to_owned());
17888 let customer_email = customer
17889 .and_then(|value| value.get("email"))
17890 .map(value_to_text)
17891 .unwrap_or_default();
17892 let payment_status = row
17893 .get("payment_status")
17894 .map(value_to_text)
17895 .filter(|value| !value.is_empty())
17896 .unwrap_or_else(|| "not required".to_owned());
17897 let notes_text = row
17898 .get("notes")
17899 .map(value_to_text)
17900 .filter(|value| !value.is_empty())
17901 .unwrap_or_else(|| "None".to_owned());
17902 let dialog_id = format!("business-calendar-appointment-{appointment_id}");
17903 let csrf_token_input_ui_html = render_fragment_with_params(
17904 app_root,
17905 "templates/shared/fragments/csrf_hidden_input.html",
17906 Map::from_iter([(
17907 "csrf_token".to_owned(),
17908 Value::String(csrf_token.to_owned()),
17909 )]),
17910 )?;
17911 let mut technician_options_html = String::new();
17912 let current_technician_id = row_i64_value(row, "technician_id").unwrap_or(0);
17913 for option in bounded_state_array(root, "technicians")
17914 .iter()
17915 .filter(|technician| row_i64_value(technician, "business_id") == Some(business_id))
17916 {
17917 let technician_id = row_i64_value(option, "id").unwrap_or(0);
17918 if technician_id <= 0 {
17919 continue;
17920 }
17921 if !super::business::dynamic_technician_unbooked_for_slot(
17922 root,
17923 business_id,
17924 technician_id,
17925 &appointment_date,
17926 &appointment_time,
17927 appointment_duration_minutes,
17928 appointment_id,
17929 ) {
17930 continue;
17931 }
17932 let technician_label = option
17933 .get("name")
17934 .map(value_to_text)
17935 .filter(|value| !value.is_empty())
17936 .unwrap_or_else(|| format!("Technician {technician_id}"));
17937 technician_options_html.push_str(&render_fragment_with_params(
17938 app_root,
17939 "templates/shared/fragments/select_option.html",
17940 Map::from_iter([
17941 (
17942 "option_value".to_owned(),
17943 Value::String(technician_id.to_string()),
17944 ),
17945 ("option_label".to_owned(), Value::String(technician_label)),
17946 (
17947 "selected_attr".to_owned(),
17948 Value::String(if technician_id == current_technician_id {
17949 " selected".to_owned()
17950 } else {
17951 String::new()
17952 }),
17953 ),
17954 ]),
17955 )?);
17956 }
17957 let return_to_path = format!(
17958 "/business/{business_slug}/calendar/?year={selected_year}&month={selected_month:02}&date={selected_date}"
17959 );
17960 let dialog_copy_html = render_fragment_with_params(
17961 app_root,
17962 "templates/businesses/fragments/business_calendar_appointment_dialog_copy.html",
17963 Map::from_iter([
17964 (
17965 "appointment_id".to_owned(),
17966 Value::String(appointment_id.to_string()),
17967 ),
17968 (
17969 "appointment_type".to_owned(),
17970 Value::String(appointment_type.clone()),
17971 ),
17972 (
17973 "appointment_duration_minutes".to_owned(),
17974 Value::String(appointment_duration_minutes.to_string()),
17975 ),
17976 (
17977 "appointment_date".to_owned(),
17978 Value::String(appointment_date.clone()),
17979 ),
17980 (
17981 "appointment_time".to_owned(),
17982 Value::String(formatted_clock_time_business_logic_local(&appointment_time)),
17983 ),
17984 (
17985 "appointment_end_time".to_owned(),
17986 Value::String(formatted_clock_time_business_logic_local(
17987 &appointment_end_time,
17988 )),
17989 ),
17990 (
17991 "customer_name".to_owned(),
17992 Value::String(customer_name.clone()),
17993 ),
17994 ("customer_email".to_owned(), Value::String(customer_email)),
17995 (
17996 "technician_name".to_owned(),
17997 Value::String(technician_name.clone()),
17998 ),
17999 ("payment_status".to_owned(), Value::String(payment_status)),
18000 ("notes_text".to_owned(), Value::String(notes_text)),
18001 ]),
18002 )?;
18003 let dialog_actions_html = render_fragment_with_params(
18004 app_root,
18005 "templates/businesses/fragments/business_calendar_appointment_dialog_actions.html",
18006 Map::from_iter([
18007 (
18008 "appointment_id".to_owned(),
18009 Value::String(appointment_id.to_string()),
18010 ),
18011 (
18012 "assign_technician_path".to_owned(),
18013 Value::String(format!("/appointments/{appointment_id}/assign-technician/")),
18014 ),
18015 ("return_to_path".to_owned(), Value::String(return_to_path)),
18016 (
18017 "technician_options_html".to_owned(),
18018 Value::String(technician_options_html),
18019 ),
18020 (
18021 "reschedule_path".to_owned(),
18022 Value::String(format!("/b/{business_slug}/")),
18023 ),
18024 (
18025 "complete_path".to_owned(),
18026 Value::String(format!("/appointments/{appointment_id}/complete/")),
18027 ),
18028 (
18029 "cancel_path".to_owned(),
18030 Value::String(format!("/appointments/{appointment_id}/cancel/")),
18031 ),
18032 (
18033 "csrf_token_input_ui_html".to_owned(),
18034 Value::String(csrf_token_input_ui_html),
18035 ),
18036 ]),
18037 )?;
18038 let appointment_dialog_html = render_workspace_dialog_html_business_logic_local(
18039 app_root,
18040 &dialog_id,
18041 "business-calendar-dialog",
18042 "business-calendar-dialog-shell",
18043 "business-calendar-dialog-copy",
18044 &dialog_copy_html,
18045 "business-calendar-dialog-actions",
18046 &dialog_actions_html,
18047 )?;
18048 appointment_rows_html.push_str(&render_fragment_with_params(
18049 app_root,
18050 "templates/businesses/fragments/business_calendar_appointment_row.html",
18051 Map::from_iter([
18052 ("dialog_id".to_owned(), Value::String(dialog_id)),
18053 (
18054 "appointment_type".to_owned(),
18055 Value::String(appointment_type),
18056 ),
18057 (
18058 "technician_name".to_owned(),
18059 Value::String(format!("Assigned: {technician_name}")),
18060 ),
18061 (
18062 "appointment_time".to_owned(),
18063 Value::String(formatted_clock_time_business_logic_local(&appointment_time)),
18064 ),
18065 (
18066 "appointment_dialog_html".to_owned(),
18067 Value::String(appointment_dialog_html),
18068 ),
18069 ]),
18070 )?);
18071 }
18072 if appointment_rows_html.is_empty() {
18073 appointment_rows_html = render_fragment_with_params(
18074 app_root,
18075 "templates/businesses/fragments/business_calendar_appointments_empty.html",
18076 serde_json::Map::new(),
18077 )?;
18078 }
18079 let return_to_path = format!(
18080 "/business/{business_slug}/calendar/?year={selected_year}&month={selected_month:02}&date={selected_date}"
18081 );
18082 let internal_booking_copy_html = render_fragment_with_params(
18083 app_root,
18084 "templates/businesses/fragments/business_calendar_internal_booking_dialog_copy.html",
18085 Map::from_iter([
18086 (
18087 "business_id".to_owned(),
18088 Value::String(business_id.to_string()),
18089 ),
18090 (
18091 "selected_date".to_owned(),
18092 Value::String(selected_date.clone()),
18093 ),
18094 (
18095 "default_booking_time".to_owned(),
18096 Value::String(default_booking_time),
18097 ),
18098 (
18099 "create_appointment_path".to_owned(),
18100 Value::String(format!(
18101 "/business/{business_slug}/calendar/appointments/new/"
18102 )),
18103 ),
18104 ("return_to_path".to_owned(), Value::String(return_to_path)),
18105 (
18106 "service_options_html".to_owned(),
18107 Value::String(service_options_html),
18108 ),
18109 (
18110 "internal_technician_options_html".to_owned(),
18111 Value::String(internal_technician_options_html),
18112 ),
18113 (
18114 "csrf_token_input_ui_html".to_owned(),
18115 Value::String(render_fragment_with_params(
18116 app_root,
18117 "templates/shared/fragments/csrf_hidden_input.html",
18118 Map::from_iter([(
18119 "csrf_token".to_owned(),
18120 Value::String(csrf_token.to_owned()),
18121 )]),
18122 )?),
18123 ),
18124 ]),
18125 )?;
18126 let internal_booking_dialog_html = render_workspace_dialog_html_business_logic_local(
18127 app_root,
18128 "business-calendar-internal-booking",
18129 "business-calendar-dialog",
18130 "business-calendar-dialog-shell",
18131 "business-calendar-dialog-copy",
18132 &internal_booking_copy_html,
18133 "business-calendar-dialog-actions",
18134 "",
18135 )?;
18136 let prev_month = if selected_month == 1 {
18137 12
18138 } else {
18139 selected_month - 1
18140 };
18141 let prev_year = if selected_month == 1 {
18142 selected_year - 1
18143 } else {
18144 selected_year
18145 };
18146 let next_month = if selected_month == 12 {
18147 1
18148 } else {
18149 selected_month + 1
18150 };
18151 let next_year = if selected_month == 12 {
18152 selected_year + 1
18153 } else {
18154 selected_year
18155 };
18156 compact(&Value::Object(serde_json::Map::from_iter([
18157 ("business_name".to_owned(), Value::String(business_name)),
18158 (
18159 "landing_path".to_owned(),
18160 Value::String(format!("/b/{business_slug}/")),
18161 ),
18162 (
18163 "manage_path".to_owned(),
18164 Value::String(format!("/businesses/{business_slug}/")),
18165 ),
18166 ("month_label".to_owned(), Value::String(month_label)),
18167 (
18168 "selected_year".to_owned(),
18169 Value::String(selected_year.to_string()),
18170 ),
18171 (
18172 "selected_date".to_owned(),
18173 Value::String(selected_date.clone()),
18174 ),
18175 (
18176 "calendar_rows_html".to_owned(),
18177 Value::String(calendar_rows_html),
18178 ),
18179 (
18180 "selected_schedule_empty_style".to_owned(),
18181 Value::String(if selected_schedule.is_empty() {
18182 String::new()
18183 } else {
18184 "display:none;".to_owned()
18185 }),
18186 ),
18187 (
18188 "selected_schedule_list_style".to_owned(),
18189 Value::String(if selected_schedule.is_empty() {
18190 "display:none;".to_owned()
18191 } else {
18192 "display:flex;".to_owned()
18193 }),
18194 ),
18195 (
18196 "selected_schedule_rows_html".to_owned(),
18197 Value::String(selected_schedule_rows_html),
18198 ),
18199 (
18200 "selected_schedule_cta_style".to_owned(),
18201 Value::String(if selected_schedule.is_empty() {
18202 "display:none".to_owned()
18203 } else {
18204 "display:block;".to_owned()
18205 }),
18206 ),
18207 (
18208 "selected_schedule_public_booking_path".to_owned(),
18209 Value::String(format!("/b/{business_slug}/")),
18210 ),
18211 (
18212 "appointment_rows_html".to_owned(),
18213 Value::String(appointment_rows_html),
18214 ),
18215 (
18216 "internal_booking_dialog_html".to_owned(),
18217 Value::String(internal_booking_dialog_html),
18218 ),
18219 (
18220 "prev_month_path".to_owned(),
18221 Value::String(format!(
18222 "{calendar_base_path}?year={prev_year}&month={prev_month:02}"
18223 )),
18224 ),
18225 (
18226 "next_month_path".to_owned(),
18227 Value::String(format!(
18228 "{calendar_base_path}?year={next_year}&month={next_month:02}"
18229 )),
18230 ),
18231 ])))
18232}
18233
18234/// Builds the business create page parameters.
18235///
18236/// # Parameters
18237///
18238/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_create_page_params_json`].
18239/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_create_page_params_json`].
18240/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_create_page_params_json`].
18241///
18242/// # Returns
18243///
18244/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_create_page_params_json`] can produce a value and [`None`] otherwise.
18245///
18246/// # Examples
18247///
18248/// ```rust,ignore
18249/// let _result = pharos_native::runtime::json_commands::dynamic_business_create_page_params_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
18250/// ```
18251///
18252/// # Related
18253///
18254/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18255pub fn dynamic_business_create_page_params_json(
18256 root: &Value,
18257 business_slug: &str,
18258 app_root: &Path,
18259) -> Option<String> {
18260 dynamic_business_admin_page_params_json(root, business_slug, app_root)
18261}
18262
18263/// Builds the business dashboard page parameters.
18264///
18265/// # Parameters
18266///
18267/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_from_root_json`].
18268/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_from_root_json`].
18269/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_from_root_json`].
18270///
18271/// # Returns
18272///
18273/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_from_root_json`] can produce a value and [`None`] otherwise.
18274///
18275/// # Examples
18276///
18277/// ```rust,ignore
18278/// let _result = pharos_native::runtime::json_commands::dynamic_business_dashboard_page_params_from_root_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
18279/// ```
18280///
18281/// # Related
18282///
18283/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18284pub fn dynamic_business_dashboard_page_params_from_root_json(
18285 root: &Value,
18286 business_slug: &str,
18287 app_root: &Path,
18288) -> Option<String> {
18289 dynamic_business_admin_page_params_json(root, business_slug, app_root)
18290}
18291
18292/// Builds the business dashboard page parameters from state.
18293///
18294/// # Parameters
18295///
18296/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_json`].
18297/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_json`].
18298/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_json`].
18299///
18300/// # Returns
18301///
18302/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_dashboard_page_params_json`] can produce a value and [`None`] otherwise.
18303///
18304/// # Examples
18305///
18306/// ```rust,ignore
18307/// let _result = pharos_native::runtime::json_commands::dynamic_business_dashboard_page_params_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
18308/// ```
18309///
18310/// # Related
18311///
18312/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18313pub fn dynamic_business_dashboard_page_params_json(
18314 root: &Value,
18315 business_slug: &str,
18316 app_root: &Path,
18317) -> Option<String> {
18318 dynamic_business_dashboard_page_params_from_root_json(root, business_slug, app_root)
18319}
18320
18321/// Builds a business edit page parameter document.
18322///
18323/// # Parameters
18324///
18325/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_from_root_json_dup_local`].
18326/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_from_root_json_dup_local`].
18327/// - `business_id`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_from_root_json_dup_local`].
18328/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_from_root_json_dup_local`].
18329///
18330/// # Returns
18331///
18332/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_edit_page_params_from_root_json_dup_local`] can produce a value and [`None`] otherwise.
18333///
18334/// # Examples
18335///
18336/// ```rust,ignore
18337/// let _result = pharos_native::runtime::json_commands::dynamic_business_edit_page_params_from_root_json_dup_local(std::path::Path::new("."), "demo", todo!(), std::path::Path::new("apps/dev_docs"));
18338/// ```
18339///
18340/// # Related
18341///
18342/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18343pub fn dynamic_business_edit_page_params_from_root_json_dup_local(
18344 root: &Value,
18345 business_slug: &str,
18346 business_id: i64,
18347 app_root: &Path,
18348) -> Option<String> {
18349 let mut extra = Map::new();
18350 extra.insert("business_slug".into(), Value::String(business_slug.into()));
18351 extra.insert("business_id".into(), Value::from(business_id));
18352 extra.insert(
18353 "app_root".into(),
18354 Value::String(app_root.to_string_lossy().into_owned()),
18355 );
18356 page_params_value(root, "business-edit", extra)
18357}
18358
18359/// Builds the business edit page parameters from state.
18360///
18361/// # Parameters
18362///
18363/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_json`].
18364/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_json`].
18365/// - `business_id`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_json`].
18366/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_edit_page_params_json`].
18367///
18368/// # Returns
18369///
18370/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_edit_page_params_json`] can produce a value and [`None`] otherwise.
18371///
18372/// # Examples
18373///
18374/// ```rust,ignore
18375/// let _result = pharos_native::runtime::json_commands::dynamic_business_edit_page_params_json(std::path::Path::new("."), "demo", todo!(), std::path::Path::new("apps/dev_docs"));
18376/// ```
18377///
18378/// # Related
18379///
18380/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18381pub fn dynamic_business_edit_page_params_json(
18382 root: &Value,
18383 business_slug: &str,
18384 business_id: i64,
18385 app_root: &Path,
18386) -> Option<String> {
18387 dynamic_business_edit_page_params_from_root_json_dup_local(
18388 root,
18389 business_slug,
18390 business_id,
18391 app_root,
18392 )
18393}
18394
18395/// Resolves one business media URL under the app-owned media directory.
18396///
18397/// # Parameters
18398///
18399/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_media_url`].
18400/// - `media_url`: Input consumed by [`crate::runtime::json_commands::dynamic_business_media_url`].
18401///
18402/// # Returns
18403///
18404/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_media_url`] can produce a value and [`None`] otherwise.
18405///
18406/// # Examples
18407///
18408/// ```rust,ignore
18409/// let _result = pharos_native::runtime::json_commands::dynamic_business_media_url("demo", todo!());
18410/// ```
18411///
18412/// # Related
18413///
18414/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18415pub fn dynamic_business_media_url(business_slug: &str, media_url: &str) -> Option<String> {
18416 if business_slug.is_empty() || media_url.is_empty() || media_url.contains(['\r', '\n']) {
18417 return None;
18418 }
18419 Some(format!(
18420 "/media/businesses/{}/{}",
18421 business_slug,
18422 media_url.trim_start_matches('/')
18423 ))
18424}
18425
18426/// Builds profile page parameters from persisted state.
18427///
18428/// # Parameters
18429///
18430/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_profile_page_params_json`].
18431/// - `user_id`: Input consumed by [`crate::runtime::json_commands::dynamic_profile_page_params_json`].
18432/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_profile_page_params_json`].
18433///
18434/// # Returns
18435///
18436/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_profile_page_params_json`] can produce a value and [`None`] otherwise.
18437///
18438/// # Examples
18439///
18440/// ```rust,ignore
18441/// let _result = pharos_native::runtime::json_commands::dynamic_profile_page_params_json(std::path::Path::new("."), 1, std::path::Path::new("apps/dev_docs"));
18442/// ```
18443///
18444/// # Related
18445///
18446/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18447pub fn dynamic_profile_page_params_json(
18448 root: &Value,
18449 user_id: i64,
18450 app_root: &Path,
18451) -> Option<String> {
18452 let user = state_row_by_i64(root, "users", "id", user_id)?;
18453 let full_name = user
18454 .get("full_name")
18455 .map(value_to_text)
18456 .filter(|value| !value.is_empty())
18457 .or_else(|| user.get("display_name").map(value_to_text))
18458 .unwrap_or_default();
18459 let email = user.get("email").map(value_to_text).unwrap_or_default();
18460 let phone_number = user
18461 .get("phone_number")
18462 .map(value_to_text)
18463 .unwrap_or_default();
18464 let address = user.get("address").map(value_to_text).unwrap_or_default();
18465 let payment_preference = user
18466 .get("payment_preference")
18467 .map(value_to_text)
18468 .filter(|value| !value.is_empty())
18469 .unwrap_or_else(|| "pay_on_site".to_owned());
18470 let google_status = bounded_state_array(root, "google_calendar_connections")
18471 .iter()
18472 .find(|row| row_i64_value(row, "user_id") == Some(user_id))
18473 .and_then(|row| row.get("connection_status"))
18474 .map(value_to_text)
18475 .filter(|value| !value.is_empty())
18476 .unwrap_or_else(|| "Disconnected".to_owned());
18477 let mut subscription_rows_html = String::new();
18478 for subscription in bounded_state_array(root, "subscriptions")
18479 .iter()
18480 .filter(|row| row_i64_value(row, "user_id") == Some(user_id))
18481 {
18482 let Some(business) = row_i64_value(subscription, "business_id")
18483 .and_then(|id| state_row_by_i64(root, "businesses", "id", id))
18484 else {
18485 continue;
18486 };
18487 let business_slug = business.get("slug").map(value_to_text).unwrap_or_default();
18488 if business_slug.is_empty() {
18489 continue;
18490 }
18491 let unsubscribe_button = render_fragment_with_params(
18492 app_root,
18493 "templates/shared/fragments/ui_button.html",
18494 Map::from_iter([
18495 (
18496 "id".to_owned(),
18497 Value::String(format!("profile-unsubscribe-{business_slug}")),
18498 ),
18499 ("type".to_owned(), Value::String("submit".to_owned())),
18500 (
18501 "class_name".to_owned(),
18502 Value::String("btn btn-danger btn-sm".to_owned()),
18503 ),
18504 ("attributes_html".to_owned(), Value::String(String::new())),
18505 (
18506 "content".to_owned(),
18507 Value::String("Unsubscribe".to_owned()),
18508 ),
18509 ]),
18510 )?;
18511 subscription_rows_html.push_str(&render_fragment_with_params(
18512 app_root,
18513 "templates/profile/fragments/subscription_row.html",
18514 Map::from_iter([
18515 ("business_slug".to_owned(), Value::String(business_slug)),
18516 (
18517 "business_name".to_owned(),
18518 Value::String(
18519 business
18520 .get("name")
18521 .map(value_to_text)
18522 .unwrap_or_else(|| "Business".to_owned()),
18523 ),
18524 ),
18525 (
18526 "profile_unsubscribe_button_ui_html".to_owned(),
18527 Value::String(unsubscribe_button),
18528 ),
18529 ]),
18530 )?);
18531 }
18532 let subscriptions_empty = subscription_rows_html.is_empty();
18533 let subscriptions_empty_html = if subscriptions_empty {
18534 render_fragment_with_params(
18535 app_root,
18536 "templates/profile/fragments/subscriptions_empty.html",
18537 Map::new(),
18538 )?
18539 } else {
18540 String::new()
18541 };
18542 compact(&Value::Object(Map::from_iter([
18543 ("full_name".to_owned(), Value::String(full_name)),
18544 ("email".to_owned(), Value::String(email)),
18545 ("phone_number".to_owned(), Value::String(phone_number)),
18546 ("address".to_owned(), Value::String(address)),
18547 (
18548 "selected_payment_preference".to_owned(),
18549 Value::String(payment_preference),
18550 ),
18551 ("google_status".to_owned(), Value::String(google_status)),
18552 (
18553 "subscriptions_list_style".to_owned(),
18554 Value::String(if subscriptions_empty {
18555 "display:none;".to_owned()
18556 } else {
18557 String::new()
18558 }),
18559 ),
18560 (
18561 "subscription_rows_html".to_owned(),
18562 Value::String(subscription_rows_html),
18563 ),
18564 (
18565 "subscriptions_empty_style".to_owned(),
18566 Value::String(if subscriptions_empty {
18567 String::new()
18568 } else {
18569 "display:none;".to_owned()
18570 }),
18571 ),
18572 (
18573 "subscriptions_empty_html".to_owned(),
18574 Value::String(subscriptions_empty_html),
18575 ),
18576 ])))
18577}
18578
18579pub fn dynamic_booking_detail_page_params_from_root_json(
18580 root: &Value,
18581 user_id: i64,
18582 booking_id: i64,
18583) -> Option<String> {
18584 let booking = state_row_by_i64(root, "bookings", "id", booking_id)
18585 .filter(|row| row_i64_value(row, "user_id") == Some(user_id))?;
18586 let (date, time) = row_date_time(booking);
18587 compact(&Value::Object(Map::from_iter([
18588 (
18589 "booking_title".to_owned(),
18590 Value::String(
18591 booking
18592 .get("title")
18593 .map(value_to_text)
18594 .filter(|value| !value.is_empty())
18595 .unwrap_or_else(|| "Booking".to_owned()),
18596 ),
18597 ),
18598 ("date_value".to_owned(), Value::String(date)),
18599 (
18600 "time_value".to_owned(),
18601 Value::String(formatted_clock_time_business_logic_local(&time)),
18602 ),
18603 (
18604 "status_value".to_owned(),
18605 Value::String(
18606 booking
18607 .get("status")
18608 .map(value_to_text)
18609 .filter(|value| !value.is_empty())
18610 .unwrap_or_else(|| "confirmed".to_owned()),
18611 ),
18612 ),
18613 ])))
18614}
18615
18616pub fn dynamic_appointment_detail_page_params_from_root_json(
18617 root: &Value,
18618 user_id: i64,
18619 appointment_id: i64,
18620 app_root: &Path,
18621) -> Option<String> {
18622 let appointment = state_row_by_i64(root, "appointments", "id", appointment_id)?;
18623 let business = row_i64_value(appointment, "business_id")
18624 .and_then(|id| state_row_by_i64(root, "businesses", "id", id))?;
18625 let appointment_user_id = row_i64_value(appointment, "user_id").unwrap_or(0);
18626 let owner_id = row_i64_value(business, "owner_id").unwrap_or(0);
18627 if appointment_user_id != user_id && owner_id != user_id {
18628 return None;
18629 }
18630 let service = row_i64_value(appointment, "service_id")
18631 .and_then(|id| state_row_by_i64(root, "services", "id", id));
18632 let technician = row_i64_value(appointment, "technician_id")
18633 .and_then(|id| state_row_by_i64(root, "technicians", "id", id));
18634 let customer = state_row_by_i64(root, "users", "id", appointment_user_id);
18635 let (date, time) = row_date_time(appointment);
18636 let service_cost = appointment
18637 .get("service_cost")
18638 .map(value_to_text)
18639 .filter(|value| !value.is_empty());
18640 let service_cost_block = if let Some(cost) = service_cost.as_deref() {
18641 render_fragment_with_params(
18642 app_root,
18643 "templates/appointments/detail/fragments/service_cost_block.html",
18644 Map::from_iter([("service_cost".to_owned(), Value::String(cost.to_owned()))]),
18645 )?
18646 } else {
18647 String::new()
18648 };
18649 let notes = appointment
18650 .get("notes")
18651 .map(value_to_text)
18652 .unwrap_or_default();
18653 let notes_block = if notes.is_empty() {
18654 String::new()
18655 } else {
18656 render_fragment_with_params(
18657 app_root,
18658 "templates/appointments/detail/fragments/notes_block.html",
18659 Map::from_iter([("notes_text".to_owned(), Value::String(notes))]),
18660 )?
18661 };
18662 compact(&Value::Object(Map::from_iter([
18663 (
18664 "service_name".to_owned(),
18665 Value::String(
18666 service
18667 .and_then(|row| row.get("name"))
18668 .map(value_to_text)
18669 .filter(|value| !value.is_empty())
18670 .unwrap_or_else(|| "Appointment".to_owned()),
18671 ),
18672 ),
18673 (
18674 "business_slug".to_owned(),
18675 Value::String(business.get("slug").map(value_to_text).unwrap_or_default()),
18676 ),
18677 (
18678 "business_public_path".to_owned(),
18679 Value::String(format!(
18680 "/b/{}/",
18681 business.get("slug").map(value_to_text).unwrap_or_default()
18682 )),
18683 ),
18684 (
18685 "business_name".to_owned(),
18686 Value::String(
18687 business
18688 .get("name")
18689 .map(value_to_text)
18690 .unwrap_or_else(|| "Business".to_owned()),
18691 ),
18692 ),
18693 ("date_value".to_owned(), Value::String(date)),
18694 (
18695 "time_value".to_owned(),
18696 Value::String(formatted_clock_time_business_logic_local(&time)),
18697 ),
18698 (
18699 "status_value".to_owned(),
18700 Value::String(
18701 appointment
18702 .get("status")
18703 .map(value_to_text)
18704 .filter(|value| !value.is_empty())
18705 .unwrap_or_else(|| "scheduled".to_owned()),
18706 ),
18707 ),
18708 (
18709 "technician_name".to_owned(),
18710 Value::String(
18711 technician
18712 .and_then(|row| row.get("name"))
18713 .map(value_to_text)
18714 .filter(|value| !value.is_empty())
18715 .unwrap_or_else(|| "Unassigned".to_owned()),
18716 ),
18717 ),
18718 (
18719 "duration_minutes".to_owned(),
18720 Value::String(
18721 appointment
18722 .get("service_duration_minutes")
18723 .map(value_to_text)
18724 .filter(|value| !value.is_empty())
18725 .unwrap_or_else(|| "45".to_owned()),
18726 ),
18727 ),
18728 (
18729 "customer_name".to_owned(),
18730 Value::String(
18731 customer
18732 .and_then(|row| row.get("full_name").or_else(|| row.get("display_name")))
18733 .map(value_to_text)
18734 .filter(|value| !value.is_empty())
18735 .unwrap_or_else(|| "Customer".to_owned()),
18736 ),
18737 ),
18738 (
18739 "customer_email".to_owned(),
18740 Value::String(
18741 customer
18742 .and_then(|row| row.get("email"))
18743 .map(value_to_text)
18744 .unwrap_or_default(),
18745 ),
18746 ),
18747 (
18748 "customer_phone".to_owned(),
18749 Value::String(
18750 customer
18751 .and_then(|row| row.get("phone_number"))
18752 .map(value_to_text)
18753 .filter(|value| !value.is_empty())
18754 .unwrap_or_else(|| "Not provided".to_owned()),
18755 ),
18756 ),
18757 (
18758 "customer_address".to_owned(),
18759 Value::String(
18760 customer
18761 .and_then(|row| row.get("address"))
18762 .map(value_to_text)
18763 .filter(|value| !value.is_empty())
18764 .unwrap_or_else(|| "Not provided".to_owned()),
18765 ),
18766 ),
18767 (
18768 "service_cost_block".to_owned(),
18769 Value::String(service_cost_block),
18770 ),
18771 ("notes_block".to_owned(), Value::String(notes_block)),
18772 ])))
18773}
18774
18775/// Builds service booking form parameters from state.
18776///
18777/// # Parameters
18778///
18779/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_from_root_json`].
18780/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_from_root_json`].
18781/// - `service_id`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_from_root_json`].
18782/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_from_root_json`].
18783/// - `error_message`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_from_root_json`].
18784///
18785/// # Returns
18786///
18787/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_service_book_form_page_params_from_root_json`] can produce a value and [`None`] otherwise.
18788///
18789/// # Examples
18790///
18791/// ```rust,ignore
18792/// let _result = pharos_native::runtime::json_commands::dynamic_service_book_form_page_params_from_root_json(std::path::Path::new("."), "demo", todo!(), std::path::Path::new("apps/dev_docs"), todo!());
18793/// ```
18794///
18795/// # Related
18796///
18797/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18798pub fn dynamic_service_book_form_page_params_from_root_json(
18799 root: &Value,
18800 business_slug: &str,
18801 service_id: i64,
18802 app_root: &Path,
18803 error_message: &str,
18804) -> Option<String> {
18805 let mut extra = Map::new();
18806 extra.insert("business_slug".into(), Value::String(business_slug.into()));
18807 extra.insert("service_id".into(), Value::from(service_id));
18808 extra.insert("error".into(), Value::String(error_message.into()));
18809 extra.insert(
18810 "app_root".into(),
18811 Value::String(app_root.to_string_lossy().into_owned()),
18812 );
18813 page_params_value(root, "service-book-form", extra)
18814}
18815
18816/// Builds service booking form parameters from state file.
18817///
18818/// # Parameters
18819///
18820/// - `state_path`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_json`].
18821/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_json`].
18822/// - `service_id`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_json`].
18823/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_json`].
18824/// - `error_message`: Input consumed by [`crate::runtime::json_commands::dynamic_service_book_form_page_params_json`].
18825///
18826/// # Returns
18827///
18828/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_service_book_form_page_params_json`] can produce a value and [`None`] otherwise.
18829///
18830/// # Examples
18831///
18832/// ```rust,ignore
18833/// let _result = pharos_native::runtime::json_commands::dynamic_service_book_form_page_params_json(todo!(), "demo", todo!(), std::path::Path::new("apps/dev_docs"), todo!());
18834/// ```
18835///
18836/// # Related
18837///
18838/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18839pub fn dynamic_service_book_form_page_params_json(
18840 state_path: &Path,
18841 business_slug: &str,
18842 service_id: i64,
18843 app_root: &Path,
18844 error_message: &str,
18845) -> Option<String> {
18846 dynamic_service_book_form_page_params_from_root_json(
18847 &json_doc_load_file(state_path)?,
18848 business_slug,
18849 service_id,
18850 app_root,
18851 error_message,
18852 )
18853}
18854
18855/// Builds service edit parameters from state.
18856///
18857/// # Parameters
18858///
18859/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_from_root_json`].
18860/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_from_root_json`].
18861/// - `service_id`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_from_root_json`].
18862/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_from_root_json`].
18863///
18864/// # Returns
18865///
18866/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_service_edit_page_params_from_root_json`] can produce a value and [`None`] otherwise.
18867///
18868/// # Examples
18869///
18870/// ```rust,ignore
18871/// let _result = pharos_native::runtime::json_commands::dynamic_service_edit_page_params_from_root_json(std::path::Path::new("."), "demo", todo!(), std::path::Path::new("apps/dev_docs"));
18872/// ```
18873///
18874/// # Related
18875///
18876/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18877pub fn dynamic_service_edit_page_params_from_root_json(
18878 root: &Value,
18879 business_slug: &str,
18880 service_id: i64,
18881 app_root: &Path,
18882) -> Option<String> {
18883 let mut extra = Map::new();
18884 extra.insert("business_slug".into(), Value::String(business_slug.into()));
18885 extra.insert("service_id".into(), Value::from(service_id));
18886 extra.insert(
18887 "app_root".into(),
18888 Value::String(app_root.to_string_lossy().into_owned()),
18889 );
18890 page_params_value(root, "service-edit", extra)
18891}
18892
18893/// Builds service edit parameters from state file.
18894///
18895/// # Parameters
18896///
18897/// - `state_path`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_json`].
18898/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_json`].
18899/// - `service_id`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_json`].
18900/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_service_edit_page_params_json`].
18901///
18902/// # Returns
18903///
18904/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_service_edit_page_params_json`] can produce a value and [`None`] otherwise.
18905///
18906/// # Examples
18907///
18908/// ```rust,ignore
18909/// let _result = pharos_native::runtime::json_commands::dynamic_service_edit_page_params_json(todo!(), "demo", todo!(), std::path::Path::new("apps/dev_docs"));
18910/// ```
18911///
18912/// # Related
18913///
18914/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
18915pub fn dynamic_service_edit_page_params_json(
18916 state_path: &Path,
18917 business_slug: &str,
18918 service_id: i64,
18919 app_root: &Path,
18920) -> Option<String> {
18921 dynamic_service_edit_page_params_from_root_json(
18922 &json_doc_load_file(state_path)?,
18923 business_slug,
18924 service_id,
18925 app_root,
18926 )
18927}
18928
18929/// Performs rating score f64.
18930fn rating_score_f64(value: &Value) -> f64 {
18931 value
18932 .as_f64()
18933 .or_else(|| value.as_i64().map(|number| number as f64))
18934 .unwrap_or(0.0)
18935}
18936
18937/// Performs rating score label.
18938fn rating_score_label(score: f64) -> String {
18939 if (score.fract()).abs() < 0.000_001 {
18940 format!("{score:.0}/5")
18941 } else {
18942 format!("{score:.1}/5")
18943 }
18944}
18945
18946/// Performs rating stars HTML.
18947fn rating_stars_html(app_root: &Path, score: f64) -> Option<String> {
18948 let clamped = score.clamp(0.0, 5.0);
18949 let full = clamped.floor() as usize;
18950 let has_half = (clamped - full as f64) >= 0.5;
18951 let empty = 5usize.saturating_sub(full + usize::from(has_half));
18952 let mut out = String::new();
18953 for _ in 0..full {
18954 out.push_str(&render_fragment_with_params(
18955 app_root,
18956 "templates/businesses/fragments/business_rating_star.html",
18957 Map::from_iter([(
18958 "class_name".to_owned(),
18959 Value::String("bi-star-fill".to_owned()),
18960 )]),
18961 )?);
18962 }
18963 if has_half {
18964 out.push_str(&render_fragment_with_params(
18965 app_root,
18966 "templates/businesses/fragments/business_rating_star.html",
18967 Map::from_iter([(
18968 "class_name".to_owned(),
18969 Value::String("bi-star-half".to_owned()),
18970 )]),
18971 )?);
18972 }
18973 for _ in 0..empty {
18974 out.push_str(&render_fragment_with_params(
18975 app_root,
18976 "templates/businesses/fragments/business_rating_star.html",
18977 Map::from_iter([("class_name".to_owned(), Value::String("bi-star".to_owned()))]),
18978 )?);
18979 }
18980 Some(out)
18981}
18982
18983/// Builds the business landing page parameters.
18984///
18985/// # Parameters
18986///
18987/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_landing_page_params_json`].
18988/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_business_landing_page_params_json`].
18989/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_business_landing_page_params_json`].
18990///
18991/// # Returns
18992///
18993/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_business_landing_page_params_json`] can produce a value and [`None`] otherwise.
18994///
18995/// # Performance
18996///
18997/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_business_landing_page_params_json`].
18998///
18999/// # Examples
19000///
19001/// ```rust,ignore
19002/// let _result = pharos_native::runtime::json_commands::dynamic_business_landing_page_params_json(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
19003/// ```
19004///
19005/// # Related
19006///
19007/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19008pub fn dynamic_business_landing_page_params_json(
19009 root: &Value,
19010 business_slug: &str,
19011 app_root: &Path,
19012) -> Option<String> {
19013 let business = generated::dynamic_find_business_by_slug(root, business_slug)?;
19014 let business_id = business.get("id").and_then(Value::as_i64)?;
19015 let business_name = value_to_text(business.get("name").unwrap_or(&Value::Null));
19016 let description = value_to_text(business.get("description").unwrap_or(&Value::Null));
19017 let secondary_color = business
19018 .get("secondary_color")
19019 .and_then(Value::as_str)
19020 .filter(|value| value.len() == 7 && value.starts_with('#'))
19021 .unwrap_or("#e5e7eb");
19022 let hero_style = format!(
19023 "background:{secondary_color};color:{};",
19024 contrast_text_color_business_logic_local(secondary_color)
19025 );
19026
19027 let image_html = business
19028 .get("image_url")
19029 .and_then(Value::as_str)
19030 .filter(|value| !value.is_empty())
19031 .and_then(|image_url| {
19032 render_app_fragment_from_doc(
19033 &app_root.to_string_lossy(),
19034 "templates/businesses/fragments/business_landing_image.html",
19035 &Value::Object(Map::from_iter([
19036 ("image_url".to_owned(), Value::String(image_url.to_owned())),
19037 ("image_alt".to_owned(), Value::String(business_name.clone())),
19038 ])),
19039 )
19040 })
19041 .unwrap_or_default();
19042
19043 let cta_html = render_app_fragment_from_doc(
19044 &app_root.to_string_lossy(),
19045 "templates/businesses/fragments/business_logged_out_cta.html",
19046 &Value::Object(Map::from_iter([(
19047 "business_slug".to_owned(),
19048 Value::String(business_slug.to_owned()),
19049 )])),
19050 )
19051 .unwrap_or_default();
19052
19053 let mut service_cards_html = String::new();
19054 let mut services = bounded_state_array(root, "services")
19055 .iter()
19056 .filter(|service| {
19057 service.get("business_id").and_then(Value::as_i64) == Some(business_id)
19058 && service
19059 .get("is_active")
19060 .and_then(Value::as_bool)
19061 .unwrap_or(true)
19062 })
19063 .collect::<Vec<_>>();
19064 services.sort_by_key(|service| {
19065 service
19066 .get("sort_order")
19067 .and_then(Value::as_i64)
19068 .unwrap_or(i64::MAX)
19069 });
19070 for service in services.into_iter().take(MAX_OUTPUT_ROWS_JSON) {
19071 let service_id = service
19072 .get("id")
19073 .and_then(Value::as_i64)
19074 .unwrap_or_default();
19075 let service_name = value_to_text(service.get("name").unwrap_or(&Value::Null));
19076 let image_url = service
19077 .get("image_url")
19078 .or_else(|| service.get("image"))
19079 .map(value_to_text)
19080 .unwrap_or_default();
19081 let primary_color = service
19082 .get("primary_color")
19083 .and_then(Value::as_str)
19084 .filter(|value| !value.is_empty());
19085 let visual_html =
19086 service_visual_html(app_root, Some(&image_url), &service_name, primary_color)
19087 .unwrap_or_default();
19088 let card_params = Value::Object(Map::from_iter([
19089 (
19090 "business_slug".to_owned(),
19091 Value::String(business_slug.to_owned()),
19092 ),
19093 ("service_id".to_owned(), Value::from(service_id)),
19094 ("service_visual_html".to_owned(), Value::String(visual_html)),
19095 ("service_name".to_owned(), Value::String(service_name)),
19096 (
19097 "service_duration_minutes".to_owned(),
19098 Value::from(
19099 service
19100 .get("duration_minutes")
19101 .and_then(Value::as_i64)
19102 .unwrap_or(45),
19103 ),
19104 ),
19105 (
19106 "service_cost".to_owned(),
19107 Value::String(value_to_text(service.get("cost").unwrap_or(&Value::Null))),
19108 ),
19109 (
19110 "service_description".to_owned(),
19111 Value::String(value_to_text(
19112 service.get("description").unwrap_or(&Value::Null),
19113 )),
19114 ),
19115 ]));
19116 if let Some(card) = render_app_fragment_from_doc(
19117 &app_root.to_string_lossy(),
19118 "templates/businesses/fragments/business_service_card.html",
19119 &card_params,
19120 ) {
19121 service_cards_html.push_str(&card);
19122 }
19123 }
19124 if service_cards_html.is_empty() {
19125 service_cards_html = render_fragment_with_params(
19126 app_root,
19127 "templates/businesses/fragments/business_services_empty.html",
19128 Map::new(),
19129 )?;
19130 }
19131
19132 let mut rating_rows_html = String::new();
19133 for rating in bounded_state_array(root, "ratings")
19134 .iter()
19135 .filter(|rating| rating.get("business_id").and_then(Value::as_i64) == Some(business_id))
19136 .take(MAX_OUTPUT_ROWS_JSON)
19137 {
19138 let score = rating_score_f64(rating.get("score").unwrap_or(&Value::Null));
19139 if let Some(row) = render_app_fragment_from_doc(
19140 &app_root.to_string_lossy(),
19141 "templates/businesses/fragments/business_rating_row.html",
19142 &Value::Object(Map::from_iter([
19143 (
19144 "rating_score_label".to_owned(),
19145 Value::String(rating_score_label(score)),
19146 ),
19147 (
19148 "rating_stars_html".to_owned(),
19149 Value::String(rating_stars_html(app_root, score)?),
19150 ),
19151 (
19152 "rating_comment".to_owned(),
19153 Value::String(value_to_text(rating.get("comment").unwrap_or(&Value::Null))),
19154 ),
19155 ])),
19156 ) {
19157 rating_rows_html.push_str(&row);
19158 }
19159 }
19160 if rating_rows_html.is_empty() {
19161 rating_rows_html = render_fragment_with_params(
19162 app_root,
19163 "templates/businesses/fragments/business_ratings_empty.html",
19164 Map::new(),
19165 )?;
19166 }
19167
19168 compact(&Value::Object(Map::from_iter([
19169 (
19170 "business_slug".to_owned(),
19171 Value::String(business_slug.to_owned()),
19172 ),
19173 ("business_name".to_owned(), Value::String(business_name)),
19174 (
19175 "business_description".to_owned(),
19176 Value::String(description),
19177 ),
19178 ("hero_style".to_owned(), Value::String(hero_style)),
19179 (
19180 "private_badge_html".to_owned(),
19181 Value::String(String::new()),
19182 ),
19183 ("business_image_html".to_owned(), Value::String(image_html)),
19184 ("cta_html".to_owned(), Value::String(cta_html)),
19185 (
19186 "service_cards_html".to_owned(),
19187 Value::String(service_cards_html),
19188 ),
19189 (
19190 "rating_rows_html".to_owned(),
19191 Value::String(rating_rows_html),
19192 ),
19193 ])))
19194}
19195
19196/// Builds the diagnostics page parameters.
19197///
19198/// # Parameters
19199///
19200/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_diagnostics_page_params_json`].
19201/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_diagnostics_page_params_json`].
19202///
19203/// # Returns
19204///
19205/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_diagnostics_page_params_json`] can produce a value and [`None`] otherwise.
19206///
19207/// # Examples
19208///
19209/// ```rust,ignore
19210/// let _result = pharos_native::runtime::json_commands::dynamic_diagnostics_page_params_json(std::path::Path::new("."), std::path::Path::new("apps/dev_docs"));
19211/// ```
19212///
19213/// # Related
19214///
19215/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19216pub fn dynamic_diagnostics_page_params_json(root: &Value, app_root: &Path) -> Option<String> {
19217 let mut extra = Map::new();
19218 extra.insert(
19219 "app_root".into(),
19220 Value::String(app_root.to_string_lossy().into_owned()),
19221 );
19222 extra.insert(
19223 "audit_events".into(),
19224 Value::Array(bounded_state_array(root, "audit_events").to_vec()),
19225 );
19226 page_params_value(root, "diagnostics", extra)
19227}
19228
19229/// Renders diagnostics page params.
19230///
19231/// # Parameters
19232///
19233/// - `root`: Input consumed by [`crate::runtime::json_commands::render_diagnostics_page_params`].
19234/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_diagnostics_page_params`].
19235///
19236/// # Returns
19237///
19238/// Returns [`Some`] when [`crate::runtime::json_commands::render_diagnostics_page_params`] can produce a value and [`None`] otherwise.
19239///
19240/// # Examples
19241///
19242/// ```rust,ignore
19243/// let _result = pharos_native::runtime::json_commands::render_diagnostics_page_params(std::path::Path::new("."), std::path::Path::new("apps/dev_docs"));
19244/// ```
19245///
19246/// # Related
19247///
19248/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19249pub fn render_diagnostics_page_params(root: &Value, app_root: &Path) -> Option<String> {
19250 dynamic_diagnostics_page_params_json(root, app_root)
19251}
19252
19253/// Builds the explore page parameters.
19254///
19255/// # Parameters
19256///
19257/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_explore_page_params_json`].
19258/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_explore_page_params_json`].
19259///
19260/// # Returns
19261///
19262/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_explore_page_params_json`] can produce a value and [`None`] otherwise.
19263///
19264/// # Examples
19265///
19266/// ```rust,ignore
19267/// let _result = pharos_native::runtime::json_commands::dynamic_explore_page_params_json(std::path::Path::new("."), std::path::Path::new("apps/dev_docs"));
19268/// ```
19269///
19270/// # Related
19271///
19272/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19273pub fn dynamic_explore_page_params_json(root: &Value, app_root: &Path) -> Option<String> {
19274 let mut extra = Map::new();
19275 extra.insert(
19276 "app_root".into(),
19277 Value::String(app_root.to_string_lossy().into_owned()),
19278 );
19279 extra.insert(
19280 "businesses".into(),
19281 Value::Array(bounded_state_array(root, "businesses").to_vec()),
19282 );
19283 page_params_value(root, "explore", extra)
19284}
19285
19286/// Builds a job-queue detail page parameter document.
19287///
19288/// # Parameters
19289///
19290/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_job_queue_detail_page_params_json`].
19291/// - `job_id`: Input consumed by [`crate::runtime::json_commands::dynamic_job_queue_detail_page_params_json`].
19292/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_job_queue_detail_page_params_json`].
19293///
19294/// # Returns
19295///
19296/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_job_queue_detail_page_params_json`] can produce a value and [`None`] otherwise.
19297///
19298/// # Performance
19299///
19300/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_job_queue_detail_page_params_json`].
19301///
19302/// # Examples
19303///
19304/// ```rust,ignore
19305/// let _result = pharos_native::runtime::json_commands::dynamic_job_queue_detail_page_params_json(std::path::Path::new("."), todo!(), std::path::Path::new("apps/dev_docs"));
19306/// ```
19307///
19308/// # Related
19309///
19310/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19311pub fn dynamic_job_queue_detail_page_params_json(
19312 root: &Value,
19313 job_id: i64,
19314 app_root: &Path,
19315) -> Option<String> {
19316 let mut extra = Map::new();
19317 extra.insert("job_id".into(), Value::from(job_id));
19318 extra.insert(
19319 "app_root".into(),
19320 Value::String(app_root.to_string_lossy().into_owned()),
19321 );
19322 extra.insert(
19323 "job".into(),
19324 bounded_state_array(root, "job_executions")
19325 .iter()
19326 .find(|row| row.get("id").and_then(Value::as_i64) == Some(job_id))
19327 .cloned()
19328 .unwrap_or(Value::Null),
19329 );
19330 page_params_value(root, "job-queue-detail", extra)
19331}
19332
19333/// Builds bounded, app-fragment-backed job-queue list page parameters.
19334///
19335/// # Parameters
19336///
19337/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_job_queue_list_page_params_json`].
19338/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_job_queue_list_page_params_json`].
19339/// - `query_string`: Input consumed by [`crate::runtime::json_commands::dynamic_job_queue_list_page_params_json`].
19340///
19341/// # Returns
19342///
19343/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_job_queue_list_page_params_json`] can produce a value and [`None`] otherwise.
19344///
19345/// # Performance
19346///
19347/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_job_queue_list_page_params_json`].
19348///
19349/// # Examples
19350///
19351/// ```rust,ignore
19352/// let _result = pharos_native::runtime::json_commands::dynamic_job_queue_list_page_params_json(std::path::Path::new("."), std::path::Path::new("apps/dev_docs"), "page=1");
19353/// ```
19354///
19355/// # Related
19356///
19357/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19358pub fn dynamic_job_queue_list_page_params_json(
19359 root: &Value,
19360 app_root: &Path,
19361 query_string: &str,
19362) -> Option<String> {
19363 const PAGE_SIZE: usize = 20;
19364 let requested_status = request_query_value(query_string, "status").unwrap_or_default();
19365 let status_filter = match requested_status.as_str() {
19366 "queued" | "running" | "succeeded" | "failed" | "cancelled" => requested_status,
19367 _ => String::new(),
19368 };
19369 let search_query = request_query_value(query_string, "q")
19370 .unwrap_or_default()
19371 .chars()
19372 .take(160)
19373 .collect::<String>();
19374 let requested_page = request_query_value(query_string, "page")
19375 .and_then(|value| value.parse::<usize>().ok())
19376 .filter(|value| *value > 0)
19377 .unwrap_or(1)
19378 .min(1_000_000);
19379 let jobs = bounded_state_array(root, "job_executions");
19380 let mut queued_count = 0_usize;
19381 let mut running_count = 0_usize;
19382 let mut succeeded_count = 0_usize;
19383 let mut failed_count = 0_usize;
19384 let mut cancelled_count = 0_usize;
19385 let matches_filters = |row: &Value| {
19386 let status = row
19387 .get("execution_status")
19388 .and_then(Value::as_str)
19389 .unwrap_or_default();
19390 (status_filter.is_empty() || status == status_filter)
19391 && (search_query.is_empty()
19392 || row
19393 .get("job_id")
19394 .map(value_to_text)
19395 .is_some_and(|value| value.contains(&search_query))
19396 || row
19397 .get("handler_id")
19398 .map(value_to_text)
19399 .is_some_and(|value| value.contains(&search_query)))
19400 };
19401 let mut matched_count = 0_usize;
19402 for row in jobs.iter().filter(|row| row.is_object()) {
19403 let status = row
19404 .get("execution_status")
19405 .and_then(Value::as_str)
19406 .unwrap_or_default();
19407 match status {
19408 "queued" => queued_count += 1,
19409 "running" => running_count += 1,
19410 "succeeded" => succeeded_count += 1,
19411 "failed" => failed_count += 1,
19412 "cancelled" => cancelled_count += 1,
19413 _ => {}
19414 }
19415 if matches_filters(row) {
19416 matched_count += 1;
19417 }
19418 }
19419 let supplied_page = root.get("_job_page");
19420 if let Some(metadata) = supplied_page {
19421 let count = |status: &str| {
19422 metadata
19423 .get("counts")?
19424 .get(status)?
19425 .as_u64()
19426 .and_then(|count| usize::try_from(count).ok())
19427 };
19428 queued_count = count("queued")?;
19429 running_count = count("running")?;
19430 succeeded_count = count("succeeded")?;
19431 failed_count = count("failed")?;
19432 cancelled_count = count("cancelled")?;
19433 matched_count = usize::try_from(metadata.get("matched_count")?.as_u64()?).ok()?;
19434 }
19435 let page_count = matched_count.div_ceil(PAGE_SIZE).max(1);
19436 let page = if let Some(metadata) = supplied_page {
19437 usize::try_from(metadata.get("page")?.as_u64()?).ok()?
19438 } else {
19439 requested_page.min(page_count)
19440 };
19441 let start = if supplied_page.is_some() {
19442 0
19443 } else {
19444 (page - 1) * PAGE_SIZE
19445 };
19446 let mut job_rows = String::new();
19447 for row in jobs
19448 .iter()
19449 .filter(|row| row.is_object() && matches_filters(row))
19450 .skip(start)
19451 .take(PAGE_SIZE)
19452 {
19453 let job_id = row.get("id").and_then(Value::as_i64).unwrap_or_default();
19454 let status = row
19455 .get("execution_status")
19456 .and_then(Value::as_str)
19457 .unwrap_or_default();
19458 let action_buttons = if matches!(status, "queued" | "running") {
19459 let button = render_fragment_with_params(
19460 app_root,
19461 "templates/shared/fragments/ui_button.html",
19462 Map::from_iter([
19463 (
19464 "id".to_owned(),
19465 Value::String(format!("job-cancel-{job_id}")),
19466 ),
19467 ("button_type".to_owned(), Value::String("submit".to_owned())),
19468 (
19469 "class_name".to_owned(),
19470 Value::String("btn btn-danger btn-sm".to_owned()),
19471 ),
19472 ("attributes_html".to_owned(), Value::String(String::new())),
19473 (
19474 "content_html".to_owned(),
19475 Value::String("Cancel".to_owned()),
19476 ),
19477 ]),
19478 )?;
19479 render_fragment_with_params(
19480 app_root,
19481 "templates/queue/fragments/job_cancel_action.html",
19482 Map::from_iter([
19483 ("job_id_path".to_owned(), Value::from(job_id)),
19484 (
19485 "job_cancel_button_ui_html".to_owned(),
19486 Value::String(button),
19487 ),
19488 ]),
19489 )?
19490 } else if matches!(status, "failed" | "cancelled") {
19491 let button = render_fragment_with_params(
19492 app_root,
19493 "templates/shared/fragments/ui_button.html",
19494 Map::from_iter([
19495 (
19496 "id".to_owned(),
19497 Value::String(format!("job-retry-{job_id}")),
19498 ),
19499 ("button_type".to_owned(), Value::String("submit".to_owned())),
19500 (
19501 "class_name".to_owned(),
19502 Value::String("btn btn-secondary btn-sm".to_owned()),
19503 ),
19504 ("attributes_html".to_owned(), Value::String(String::new())),
19505 ("content_html".to_owned(), Value::String("Retry".to_owned())),
19506 ]),
19507 )?;
19508 render_fragment_with_params(
19509 app_root,
19510 "templates/queue/fragments/job_retry_action.html",
19511 Map::from_iter([
19512 ("job_id_path".to_owned(), Value::from(job_id)),
19513 ("job_retry_button_ui_html".to_owned(), Value::String(button)),
19514 ]),
19515 )?
19516 } else {
19517 String::new()
19518 };
19519 job_rows.push_str(&render_fragment_with_params(
19520 app_root,
19521 "templates/queue/fragments/job_row.html",
19522 Map::from_iter([
19523 ("job_id_path".to_owned(), Value::from(job_id)),
19524 ("job_id_text".to_owned(), Value::from(job_id)),
19525 (
19526 "job_key".to_owned(),
19527 Value::String(row.get("job_id").map(value_to_text).unwrap_or_default()),
19528 ),
19529 (
19530 "handler_id".to_owned(),
19531 Value::String(row.get("handler_id").map(value_to_text).unwrap_or_default()),
19532 ),
19533 ("job_status".to_owned(), Value::String(status.to_owned())),
19534 (
19535 "attempt_count".to_owned(),
19536 Value::from(
19537 row.get("attempt_count")
19538 .and_then(Value::as_i64)
19539 .unwrap_or_default(),
19540 ),
19541 ),
19542 (
19543 "max_attempts".to_owned(),
19544 Value::from(
19545 row.get("max_attempts")
19546 .and_then(Value::as_i64)
19547 .unwrap_or_default(),
19548 ),
19549 ),
19550 (
19551 "created_at".to_owned(),
19552 Value::String(row.get("created_at").map(value_to_text).unwrap_or_default()),
19553 ),
19554 ("action_buttons".to_owned(), Value::String(action_buttons)),
19555 ]),
19556 )?);
19557 }
19558 if job_rows.is_empty() {
19559 job_rows = render_fragment_with_params(
19560 app_root,
19561 "templates/queue/fragments/job_empty_row.html",
19562 Map::from_iter([(
19563 "message".to_owned(),
19564 Value::String("No jobs match the current filter.".to_owned()),
19565 )]),
19566 )?;
19567 }
19568 let mut pager = String::new();
19569 let status_query_html = if status_filter.is_empty() {
19570 String::new()
19571 } else {
19572 render_fragment_with_params(
19573 app_root,
19574 "templates/queue/fragments/job_query_suffix.html",
19575 Map::from_iter([
19576 ("query_name".to_owned(), Value::String("status".to_owned())),
19577 (
19578 "query_value".to_owned(),
19579 Value::String(url_encode_query_component(&status_filter)),
19580 ),
19581 ]),
19582 )?
19583 .trim()
19584 .to_owned()
19585 };
19586 let search_query_html = if search_query.is_empty() {
19587 String::new()
19588 } else {
19589 render_fragment_with_params(
19590 app_root,
19591 "templates/queue/fragments/job_query_suffix.html",
19592 Map::from_iter([
19593 ("query_name".to_owned(), Value::String("q".to_owned())),
19594 (
19595 "query_value".to_owned(),
19596 Value::String(url_encode_query_component(&search_query)),
19597 ),
19598 ]),
19599 )?
19600 .trim()
19601 .to_owned()
19602 };
19603 for (link_page, label, trailing_space) in [
19604 (
19605 page.checked_sub(1).filter(|value| *value > 0),
19606 "← Prev",
19607 page < page_count,
19608 ),
19609 ((page < page_count).then_some(page + 1), "Next →", false),
19610 ] {
19611 let Some(link_page) = link_page else {
19612 continue;
19613 };
19614 pager.push_str(&render_fragment_with_params(
19615 app_root,
19616 "templates/queue/fragments/job_pager_link.html",
19617 Map::from_iter([
19618 ("page_number".to_owned(), Value::from(link_page)),
19619 (
19620 "status_query_html".to_owned(),
19621 Value::String(status_query_html.clone()),
19622 ),
19623 (
19624 "search_query_html".to_owned(),
19625 Value::String(search_query_html.clone()),
19626 ),
19627 ("link_label".to_owned(), Value::String(label.to_owned())),
19628 (
19629 "trailing_space".to_owned(),
19630 Value::String(if trailing_space { " " } else { "" }.to_owned()),
19631 ),
19632 ]),
19633 )?);
19634 }
19635 compact(&Value::Object(Map::from_iter([
19636 ("queued_count".to_owned(), Value::from(queued_count)),
19637 ("running_count".to_owned(), Value::from(running_count)),
19638 ("succeeded_count".to_owned(), Value::from(succeeded_count)),
19639 ("failed_count".to_owned(), Value::from(failed_count)),
19640 ("cancelled_count".to_owned(), Value::from(cancelled_count)),
19641 ("page".to_owned(), Value::from(page)),
19642 ("page_count".to_owned(), Value::from(page_count)),
19643 ("status_filter".to_owned(), Value::String(status_filter)),
19644 ("search_query".to_owned(), Value::String(search_query)),
19645 ("job_rows".to_owned(), Value::String(job_rows)),
19646 ("pager".to_owned(), Value::String(pager)),
19647 ])))
19648}
19649
19650/// Renders the declared service visual fragment with escaped data.
19651///
19652/// # Parameters
19653///
19654/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_render_service_visual_html`].
19655/// - `service`: Input consumed by [`crate::runtime::json_commands::dynamic_render_service_visual_html`].
19656///
19657/// # Returns
19658///
19659/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_render_service_visual_html`] can produce a value and [`None`] otherwise.
19660///
19661/// # Examples
19662///
19663/// ```rust,ignore
19664/// let _result = pharos_native::runtime::json_commands::dynamic_render_service_visual_html(std::path::Path::new("apps/dev_docs"), todo!());
19665/// ```
19666///
19667/// # Related
19668///
19669/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19670pub fn dynamic_render_service_visual_html(app_root: &Path, service: &Value) -> Option<String> {
19671 let name = value_to_text(service.get("name").unwrap_or(&Value::Null));
19672 let image = service
19673 .get("image_url")
19674 .map(value_to_text)
19675 .unwrap_or_default();
19676 let color = service
19677 .get("primary_color")
19678 .map(value_to_text)
19679 .filter(|value| !value.is_empty());
19680 service_visual_html(app_root, Some(&image), &name, color.as_deref())
19681}
19682
19683/// Renders a workspace panel.
19684///
19685/// # Parameters
19686///
19687/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_workspace_panel_html_business_logic_local`].
19688/// - `panel_class`: Input consumed by [`crate::runtime::json_commands::render_workspace_panel_html_business_logic_local`].
19689/// - `header_class`: Input consumed by [`crate::runtime::json_commands::render_workspace_panel_html_business_logic_local`].
19690/// - `title_text`: Input consumed by [`crate::runtime::json_commands::render_workspace_panel_html_business_logic_local`].
19691/// - `header_actions_html`: Input consumed by [`crate::runtime::json_commands::render_workspace_panel_html_business_logic_local`].
19692/// - `body_html`: Input consumed by [`crate::runtime::json_commands::render_workspace_panel_html_business_logic_local`].
19693///
19694/// # Returns
19695///
19696/// Returns [`Some`] when [`crate::runtime::json_commands::render_workspace_panel_html_business_logic_local`] can produce a value and [`None`] otherwise.
19697///
19698/// # Examples
19699///
19700/// ```rust,ignore
19701/// let _result = pharos_native::runtime::json_commands::render_workspace_panel_html_business_logic_local(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!(), todo!());
19702/// ```
19703///
19704/// # Related
19705///
19706/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19707pub fn render_workspace_panel_html_business_logic_local(
19708 app_root: &Path,
19709 panel_class: &str,
19710 header_class: &str,
19711 title_text: &str,
19712 header_actions_html: &str,
19713 body_html: &str,
19714) -> Option<String> {
19715 render_fragment_with_params(
19716 app_root,
19717 "templates/shared/fragments/workspace_panel.html",
19718 Map::from_iter([
19719 (
19720 "panel_class".to_owned(),
19721 Value::String(panel_class.to_owned()),
19722 ),
19723 (
19724 "header_class".to_owned(),
19725 Value::String(header_class.to_owned()),
19726 ),
19727 (
19728 "title_text".to_owned(),
19729 Value::String(title_text.to_owned()),
19730 ),
19731 (
19732 "header_actions_html".to_owned(),
19733 Value::String(header_actions_html.to_owned()),
19734 ),
19735 ("body_html".to_owned(), Value::String(body_html.to_owned())),
19736 ]),
19737 )
19738}
19739
19740/// Renders two workspace panels in a bounded grid.
19741///
19742/// # Parameters
19743///
19744/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_workspace_split_grid_html_business_logic_local`].
19745/// - `grid_class`: Input consumed by [`crate::runtime::json_commands::render_workspace_split_grid_html_business_logic_local`].
19746/// - `left_html`: Input consumed by [`crate::runtime::json_commands::render_workspace_split_grid_html_business_logic_local`].
19747/// - `right_html`: Input consumed by [`crate::runtime::json_commands::render_workspace_split_grid_html_business_logic_local`].
19748///
19749/// # Returns
19750///
19751/// Returns [`Some`] when [`crate::runtime::json_commands::render_workspace_split_grid_html_business_logic_local`] can produce a value and [`None`] otherwise.
19752///
19753/// # Examples
19754///
19755/// ```rust,ignore
19756/// let _result = pharos_native::runtime::json_commands::render_workspace_split_grid_html_business_logic_local(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
19757/// ```
19758///
19759/// # Related
19760///
19761/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19762pub fn render_workspace_split_grid_html_business_logic_local(
19763 app_root: &Path,
19764 grid_class: &str,
19765 left_html: &str,
19766 right_html: &str,
19767) -> Option<String> {
19768 render_fragment_with_params(
19769 app_root,
19770 "templates/shared/fragments/workspace_split_grid.html",
19771 Map::from_iter([
19772 (
19773 "grid_class".to_owned(),
19774 Value::String(grid_class.to_owned()),
19775 ),
19776 ("left_html".to_owned(), Value::String(left_html.to_owned())),
19777 (
19778 "right_html".to_owned(),
19779 Value::String(right_html.to_owned()),
19780 ),
19781 ]),
19782 )
19783}
19784
19785/// Renders a workspace dialog with app-owned inner fragments.
19786///
19787/// # Parameters
19788///
19789/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19790/// - `dialog_id`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19791/// - `dialog_class`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19792/// - `shell_class`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19793/// - `copy_class`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19794/// - `copy_html`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19795/// - `actions_class`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19796/// - `actions_html`: Input consumed by [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`].
19797///
19798/// # Returns
19799///
19800/// Returns [`Some`] when [`crate::runtime::json_commands::render_workspace_dialog_html_business_logic_local`] can produce a value and [`None`] otherwise.
19801///
19802/// # Examples
19803///
19804/// ```rust,ignore
19805/// let _result = pharos_native::runtime::json_commands::render_workspace_dialog_html_business_logic_local(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!(), todo!(), todo!(), todo!());
19806/// ```
19807///
19808/// # Related
19809///
19810/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19811pub fn render_workspace_dialog_html_business_logic_local(
19812 app_root: &Path,
19813 dialog_id: &str,
19814 dialog_class: &str,
19815 shell_class: &str,
19816 copy_class: &str,
19817 copy_html: &str,
19818 actions_class: &str,
19819 actions_html: &str,
19820) -> Option<String> {
19821 render_fragment_with_params(
19822 app_root,
19823 "templates/shared/fragments/workspace_dialog.html",
19824 Map::from_iter([
19825 ("dialog_id".to_owned(), Value::String(dialog_id.to_owned())),
19826 (
19827 "dialog_class".to_owned(),
19828 Value::String(dialog_class.to_owned()),
19829 ),
19830 (
19831 "shell_class".to_owned(),
19832 Value::String(shell_class.to_owned()),
19833 ),
19834 (
19835 "copy_class".to_owned(),
19836 Value::String(copy_class.to_owned()),
19837 ),
19838 ("copy_html".to_owned(), Value::String(copy_html.to_owned())),
19839 (
19840 "actions_class".to_owned(),
19841 Value::String(actions_class.to_owned()),
19842 ),
19843 (
19844 "actions_html".to_owned(),
19845 Value::String(actions_html.to_owned()),
19846 ),
19847 ]),
19848 )
19849}
19850
19851/// Returns the calendar color contrast text.
19852///
19853/// # Parameters
19854///
19855/// - `background`: Input consumed by [`crate::runtime::json_commands::contrast_text_color_business_logic_local`].
19856///
19857/// # Returns
19858///
19859/// Returns the computed value produced by [`crate::runtime::json_commands::contrast_text_color_business_logic_local`].
19860///
19861/// # Examples
19862///
19863/// ```rust,ignore
19864/// let _result = pharos_native::runtime::json_commands::contrast_text_color_business_logic_local(todo!());
19865/// ```
19866///
19867/// # Related
19868///
19869/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19870pub fn contrast_text_color_business_logic_local(background: &str) -> &'static str {
19871 let hex = background.trim_start_matches('#');
19872 let value = u32::from_str_radix(hex, 16).unwrap_or(0xffffff);
19873 let red = (value >> 16) & 0xff;
19874 let green = (value >> 8) & 0xff;
19875 let blue = value & 0xff;
19876 if red * 299 + green * 587 + blue * 114 >= 128000 {
19877 "#111827"
19878 } else {
19879 "#ffffff"
19880 }
19881}
19882
19883/// Returns service-name initials, treating parentheses and whitespace as word separators.
19884///
19885/// # Parameters
19886///
19887/// - `name`: Input consumed by [`crate::runtime::json_commands::dynamic_service_abbreviation`].
19888///
19889/// # Returns
19890///
19891/// Returns the computed value produced by [`crate::runtime::json_commands::dynamic_service_abbreviation`].
19892///
19893/// # Performance
19894///
19895/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_service_abbreviation`].
19896///
19897/// # Examples
19898///
19899/// ```rust,ignore
19900/// let _result = pharos_native::runtime::json_commands::dynamic_service_abbreviation("name");
19901/// ```
19902///
19903/// # Related
19904///
19905/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19906pub fn dynamic_service_abbreviation(name: &str) -> String {
19907 name.split(|character: char| character.is_whitespace() || matches!(character, '(' | ')'))
19908 .filter_map(|word| word.chars().next())
19909 .collect::<String>()
19910 .to_uppercase()
19911}
19912
19913/// Returns a safe service badge color.
19914///
19915/// # Parameters
19916///
19917/// - `primary_color`: Input consumed by [`crate::runtime::json_commands::dynamic_service_badge_color`].
19918///
19919/// # Returns
19920///
19921/// Returns the computed value produced by [`crate::runtime::json_commands::dynamic_service_badge_color`].
19922///
19923/// # Performance
19924///
19925/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_service_badge_color`].
19926///
19927/// # Examples
19928///
19929/// ```rust,ignore
19930/// let _result = pharos_native::runtime::json_commands::dynamic_service_badge_color(todo!());
19931/// ```
19932///
19933/// # Related
19934///
19935/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19936pub fn dynamic_service_badge_color(primary_color: &str) -> &'static str {
19937 let color = primary_color.trim();
19938 if color.len() == 7
19939 && color.starts_with('#')
19940 && color[1..].bytes().all(|byte| byte.is_ascii_hexdigit())
19941 {
19942 "var(--service-primary-color)"
19943 } else {
19944 "#475569"
19945 }
19946}
19947
19948/// Initializes an incremental SHA-256 context.
19949///
19950/// # Returns
19951///
19952/// Returns the computed value produced by [`crate::runtime::json_commands::sha256_init_logic_local`].
19953///
19954/// # Performance
19955///
19956/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::sha256_init_logic_local`].
19957///
19958/// # Examples
19959///
19960/// ```rust,ignore
19961/// let _result = pharos_native::runtime::json_commands::sha256_init_logic_local();
19962/// ```
19963///
19964/// # Related
19965///
19966/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19967pub fn sha256_init_logic_local() -> Sha256 {
19968 Sha256::new()
19969}
19970
19971/// Updates an incremental SHA-256 context.
19972///
19973/// # Parameters
19974///
19975/// - `context`: Input consumed by [`crate::runtime::json_commands::sha256_update_logic_local`].
19976/// - `bytes`: Input consumed by [`crate::runtime::json_commands::sha256_update_logic_local`].
19977///
19978/// # Returns
19979///
19980/// Returns after [`crate::runtime::json_commands::sha256_update_logic_local`] applies its in-place effect.
19981///
19982/// # Examples
19983///
19984/// ```rust,ignore
19985/// let _result = pharos_native::runtime::json_commands::sha256_update_logic_local(todo!(), b"");
19986/// ```
19987///
19988/// # Related
19989///
19990/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
19991pub fn sha256_update_logic_local(context: &mut Sha256, bytes: &[u8]) {
19992 context.update(bytes);
19993}
19994
19995/// Finalizes an incremental SHA-256 context.
19996///
19997/// # Parameters
19998///
19999/// - `context`: Input consumed by [`crate::runtime::json_commands::sha256_final_logic_local`].
20000///
20001/// # Returns
20002///
20003/// Returns the computed value produced by [`crate::runtime::json_commands::sha256_final_logic_local`].
20004///
20005/// # Examples
20006///
20007/// ```rust,ignore
20008/// let _result = pharos_native::runtime::json_commands::sha256_final_logic_local(todo!());
20009/// ```
20010///
20011/// # Related
20012///
20013/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20014pub fn sha256_final_logic_local(context: Sha256) -> [u8; 32] {
20015 context.finalize().into()
20016}
20017
20018/// Computes a one-block SHA-256 digest for the transform contract.
20019///
20020/// # Parameters
20021///
20022/// - `block`: Input consumed by [`crate::runtime::json_commands::sha256_transform_logic_local`].
20023///
20024/// # Returns
20025///
20026/// Returns the computed value produced by [`crate::runtime::json_commands::sha256_transform_logic_local`].
20027///
20028/// # Examples
20029///
20030/// ```rust,ignore
20031/// let _result = pharos_native::runtime::json_commands::sha256_transform_logic_local(todo!());
20032/// ```
20033///
20034/// # Related
20035///
20036/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20037pub fn sha256_transform_logic_local(block: &[u8]) -> [u8; 32] {
20038 Sha256::digest(block).into()
20039}
20040
20041/// Rotates a 32-bit word right by a masked count.
20042///
20043/// # Parameters
20044///
20045/// - `value`: Input consumed by [`crate::runtime::json_commands::sha256_rotr_logic_local`].
20046/// - `count`: Input consumed by [`crate::runtime::json_commands::sha256_rotr_logic_local`].
20047///
20048/// # Returns
20049///
20050/// Returns the computed value produced by [`crate::runtime::json_commands::sha256_rotr_logic_local`].
20051///
20052/// # Examples
20053///
20054/// ```rust,ignore
20055/// let _result = pharos_native::runtime::json_commands::sha256_rotr_logic_local("value", 0);
20056/// ```
20057///
20058/// # Related
20059///
20060/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20061pub fn sha256_rotr_logic_local(value: u32, count: u32) -> u32 {
20062 value.rotate_right(count & 31)
20063}
20064
20065/// Returns a lowercase SHA-256 hexadecimal digest.
20066///
20067/// # Parameters
20068///
20069/// - `value`: Input consumed by [`crate::runtime::json_commands::sha256_hex`].
20070///
20071/// # Returns
20072///
20073/// Returns the computed value produced by [`crate::runtime::json_commands::sha256_hex`].
20074///
20075/// # Examples
20076///
20077/// ```rust,ignore
20078/// let _result = pharos_native::runtime::json_commands::sha256_hex("value");
20079/// ```
20080///
20081/// # Related
20082///
20083/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20084pub fn sha256_hex(value: &[u8]) -> String {
20085 pharos_runtime_core::support::sha256_hex(value)
20086}
20087
20088/// Returns the weekday abbreviation for a validated ISO date.
20089///
20090/// # Parameters
20091///
20092/// - `year`: Input consumed by [`crate::runtime::json_commands::day_abbrev_for_ymd_business_logic_local`].
20093/// - `month`: Input consumed by [`crate::runtime::json_commands::day_abbrev_for_ymd_business_logic_local`].
20094/// - `day`: Input consumed by [`crate::runtime::json_commands::day_abbrev_for_ymd_business_logic_local`].
20095///
20096/// # Returns
20097///
20098/// Returns [`Some`] when [`crate::runtime::json_commands::day_abbrev_for_ymd_business_logic_local`] can produce a value and [`None`] otherwise.
20099///
20100/// # Examples
20101///
20102/// ```rust,ignore
20103/// let _result = pharos_native::runtime::json_commands::day_abbrev_for_ymd_business_logic_local(todo!(), todo!(), todo!());
20104/// ```
20105///
20106/// # Related
20107///
20108/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20109pub fn day_abbrev_for_ymd_business_logic_local(
20110 year: i32,
20111 month: u32,
20112 day: u32,
20113) -> Option<&'static str> {
20114 let days = days_from_civil(year, month, day)?;
20115 Some(["Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed"][days.rem_euclid(7) as usize])
20116}
20117
20118/// Formats a validated time range.
20119fn formatted_clock_time_business_logic_local(time_text: &str) -> String {
20120 let Some((hour_text, minute_text)) = time_text.split_once(':') else {
20121 return time_text.to_owned();
20122 };
20123 let Some(hour) = hour_text.parse::<u32>().ok() else {
20124 return time_text.to_owned();
20125 };
20126 let Some(minute) = minute_text.parse::<u32>().ok() else {
20127 return time_text.to_owned();
20128 };
20129 if hour > 23 || minute > 59 {
20130 return time_text.to_owned();
20131 }
20132 let suffix = if hour < 12 { "AM" } else { "PM" };
20133 let display_hour = match hour % 12 {
20134 0 => 12,
20135 value => value,
20136 };
20137 format!("{display_hour}:{minute:02} {suffix}")
20138}
20139
20140fn appointment_time_plus_minutes(time_text: &str, duration_minutes: i64) -> Option<String> {
20141 let (hour_text, minute_text) = time_text.split_once(':')?;
20142 let hour = hour_text.parse::<i64>().ok()?;
20143 let minute = minute_text.parse::<i64>().ok()?;
20144 if !(0..=23).contains(&hour) || !(0..=59).contains(&minute) || duration_minutes <= 0 {
20145 return None;
20146 }
20147 let total_minutes = hour
20148 .saturating_mul(60)
20149 .saturating_add(minute)
20150 .saturating_add(duration_minutes);
20151 let day_minutes = 24 * 60;
20152 let normalized_minutes = total_minutes.rem_euclid(day_minutes);
20153 Some(format!(
20154 "{:02}:{:02}",
20155 normalized_minutes / 60,
20156 normalized_minutes % 60
20157 ))
20158}
20159
20160/// Performs formatted time range business logic local.
20161///
20162/// # Parameters
20163///
20164/// - `start_time`: Input consumed by [`crate::runtime::json_commands::formatted_time_range_business_logic_local`].
20165/// - `end_time`: Input consumed by [`crate::runtime::json_commands::formatted_time_range_business_logic_local`].
20166///
20167/// # Returns
20168///
20169/// Returns the computed value produced by [`crate::runtime::json_commands::formatted_time_range_business_logic_local`].
20170///
20171/// # Examples
20172///
20173/// ```rust,ignore
20174/// let _result = pharos_native::runtime::json_commands::formatted_time_range_business_logic_local(todo!(), todo!());
20175/// ```
20176///
20177/// # Related
20178///
20179/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20180pub fn formatted_time_range_business_logic_local(start_time: &str, end_time: &str) -> String {
20181 if start_time.is_empty() {
20182 return formatted_clock_time_business_logic_local(end_time);
20183 }
20184 if end_time.is_empty() {
20185 return formatted_clock_time_business_logic_local(start_time);
20186 }
20187 format!(
20188 "{}–{}",
20189 formatted_clock_time_business_logic_local(start_time),
20190 formatted_clock_time_business_logic_local(end_time)
20191 )
20192}
20193
20194/// Performs days from civil.
20195fn days_from_civil(year: i32, month: u32, day: u32) -> Option<i64> {
20196 if !(1..=12).contains(&month)
20197 || day == 0
20198 || day > days_in_month_business_logic_local(year, month)
20199 {
20200 return None;
20201 }
20202 let y = year - i32::from(month <= 2);
20203 let era = i64::from(y).div_euclid(400);
20204 let yoe = i64::from(y).rem_euclid(400);
20205 let mp = i64::from(month) + if month > 2 { -3 } else { 9 };
20206 let doy = (153 * mp + 2) / 5 + i64::from(day) - 1;
20207 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
20208 Some(era * 146097 + doe - 719468)
20209}
20210
20211/// Returns the number of days in a Gregorian month.
20212///
20213/// # Parameters
20214///
20215/// - `year`: Input consumed by [`crate::runtime::json_commands::days_in_month_business_logic_local`].
20216/// - `month`: Input consumed by [`crate::runtime::json_commands::days_in_month_business_logic_local`].
20217///
20218/// # Returns
20219///
20220/// Returns the computed value produced by [`crate::runtime::json_commands::days_in_month_business_logic_local`].
20221///
20222/// # Examples
20223///
20224/// ```rust,ignore
20225/// let _result = pharos_native::runtime::json_commands::days_in_month_business_logic_local(todo!(), todo!());
20226/// ```
20227///
20228/// # Related
20229///
20230/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20231pub fn days_in_month_business_logic_local(year: i32, month: u32) -> u32 {
20232 match month {
20233 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
20234 4 | 6 | 9 | 11 => 30,
20235 2 if year.rem_euclid(4) == 0
20236 && (year.rem_euclid(100) != 0 || year.rem_euclid(400) == 0) =>
20237 {
20238 29
20239 }
20240 2 => 28,
20241 _ => 0,
20242 }
20243}
20244
20245/// Finds the named month.
20246///
20247/// # Parameters
20248///
20249/// - `month`: Input consumed by [`crate::runtime::json_commands::month_name_business_logic_local`].
20250///
20251/// # Returns
20252///
20253/// Returns [`Some`] when [`crate::runtime::json_commands::month_name_business_logic_local`] can produce a value and [`None`] otherwise.
20254///
20255/// # Examples
20256///
20257/// ```rust,ignore
20258/// let _result = pharos_native::runtime::json_commands::month_name_business_logic_local(todo!());
20259/// ```
20260///
20261/// # Related
20262///
20263/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20264pub fn month_name_business_logic_local(month: u32) -> Option<&'static str> {
20265 [
20266 "",
20267 "January",
20268 "February",
20269 "March",
20270 "April",
20271 "May",
20272 "June",
20273 "July",
20274 "August",
20275 "September",
20276 "October",
20277 "November",
20278 "December",
20279 ]
20280 .get(month as usize)
20281 .copied()
20282 .filter(|value| !value.is_empty())
20283}
20284
20285/// Shifts a year/month pair by a bounded number of months.
20286///
20287/// # Parameters
20288///
20289/// - `year`: Input consumed by [`crate::runtime::json_commands::month_shift_business_logic_local`].
20290/// - `month`: Input consumed by [`crate::runtime::json_commands::month_shift_business_logic_local`].
20291/// - `shift`: Input consumed by [`crate::runtime::json_commands::month_shift_business_logic_local`].
20292/// - `u32`: Input consumed by [`crate::runtime::json_commands::month_shift_business_logic_local`].
20293///
20294/// # Returns
20295///
20296/// Returns [`Some`] when [`crate::runtime::json_commands::month_shift_business_logic_local`] can produce a value and [`None`] otherwise.
20297///
20298/// # Examples
20299///
20300/// ```rust,ignore
20301/// let _result = pharos_native::runtime::json_commands::month_shift_business_logic_local(todo!(), todo!(), todo!(), todo!());
20302/// ```
20303///
20304/// # Related
20305///
20306/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20307pub fn month_shift_business_logic_local(year: i32, month: u32, shift: i32) -> Option<(i32, u32)> {
20308 if !(1..=12).contains(&month) {
20309 return None;
20310 }
20311 let index = year
20312 .checked_mul(12)?
20313 .checked_add(month as i32 - 1)?
20314 .checked_add(shift)?;
20315 Some((index.div_euclid(12), index.rem_euclid(12) as u32 + 1))
20316}
20317
20318/// Appends an ISO date to a bounded string buffer.
20319///
20320/// # Parameters
20321///
20322/// - `output`: Input consumed by [`crate::runtime::json_commands::append_date_business_logic_local`].
20323/// - `year`: Input consumed by [`crate::runtime::json_commands::append_date_business_logic_local`].
20324/// - `month`: Input consumed by [`crate::runtime::json_commands::append_date_business_logic_local`].
20325/// - `day`: Input consumed by [`crate::runtime::json_commands::append_date_business_logic_local`].
20326///
20327/// # Returns
20328///
20329/// Returns `true` when the condition checked by [`crate::runtime::json_commands::append_date_business_logic_local`] holds and `false` otherwise.
20330///
20331/// # Examples
20332///
20333/// ```rust,ignore
20334/// let _result = pharos_native::runtime::json_commands::append_date_business_logic_local(todo!(), todo!(), todo!(), todo!());
20335/// ```
20336///
20337/// # Related
20338///
20339/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20340pub fn append_date_business_logic_local(
20341 output: &mut String,
20342 year: i32,
20343 month: u32,
20344 day: u32,
20345) -> bool {
20346 if days_from_civil(year, month, day).is_none() {
20347 return false;
20348 }
20349 let text = format!("{year:04}-{month:02}-{day:02}");
20350 if output.len().saturating_add(text.len()) > MAX_TEXT_BYTES {
20351 return false;
20352 }
20353 output.push_str(&text);
20354 true
20355}
20356
20357/// Returns the Monday-based index of a weekday name.
20358///
20359/// # Parameters
20360///
20361/// - `value`: Input consumed by [`crate::runtime::json_commands::weekday_monday_index_business_logic_local`].
20362///
20363/// # Returns
20364///
20365/// Returns [`Some`] when [`crate::runtime::json_commands::weekday_monday_index_business_logic_local`] can produce a value and [`None`] otherwise.
20366///
20367/// # Performance
20368///
20369/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::weekday_monday_index_business_logic_local`].
20370///
20371/// # Examples
20372///
20373/// ```rust,ignore
20374/// let _result = pharos_native::runtime::json_commands::weekday_monday_index_business_logic_local("value");
20375/// ```
20376///
20377/// # Related
20378///
20379/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20380pub fn weekday_monday_index_business_logic_local(value: &str) -> Option<u8> {
20381 [
20382 "monday",
20383 "tuesday",
20384 "wednesday",
20385 "thursday",
20386 "friday",
20387 "saturday",
20388 "sunday",
20389 ]
20390 .iter()
20391 .position(|name| name.eq_ignore_ascii_case(value))
20392 .map(|index| index as u8)
20393}
20394
20395/// Finds a technician row scoped to one business.
20396///
20397/// # Parameters
20398///
20399/// - `root`: Input consumed by [`crate::runtime::json_commands::find_technician_for_business_logic_local`].
20400/// - `technician_id`: Input consumed by [`crate::runtime::json_commands::find_technician_for_business_logic_local`].
20401/// - `business_id`: Input consumed by [`crate::runtime::json_commands::find_technician_for_business_logic_local`].
20402///
20403/// # Returns
20404///
20405/// Returns [`Some`] when [`crate::runtime::json_commands::find_technician_for_business_logic_local`] can produce a value and [`None`] otherwise.
20406///
20407/// # Performance
20408///
20409/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_technician_for_business_logic_local`].
20410///
20411/// # Examples
20412///
20413/// ```rust,ignore
20414/// let _result = pharos_native::runtime::json_commands::find_technician_for_business_logic_local(std::path::Path::new("."), todo!(), todo!());
20415/// ```
20416///
20417/// # Related
20418///
20419/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20420pub fn find_technician_for_business_logic_local(
20421 root: &Value,
20422 technician_id: i64,
20423 business_id: i64,
20424) -> Option<Value> {
20425 bounded_state_array(root, "technicians")
20426 .iter()
20427 .find(|row| {
20428 row.get("id").and_then(Value::as_i64) == Some(technician_id)
20429 && row.get("business_id").and_then(Value::as_i64) == Some(business_id)
20430 })
20431 .cloned()
20432}
20433
20434/// Finds a user row by identifier.
20435///
20436/// # Parameters
20437///
20438/// - `root`: Input consumed by [`crate::runtime::json_commands::find_user_by_id_business_logic_local`].
20439/// - `user_id`: Input consumed by [`crate::runtime::json_commands::find_user_by_id_business_logic_local`].
20440///
20441/// # Returns
20442///
20443/// Returns [`Some`] when [`crate::runtime::json_commands::find_user_by_id_business_logic_local`] can produce a value and [`None`] otherwise.
20444///
20445/// # Performance
20446///
20447/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_user_by_id_business_logic_local`].
20448///
20449/// # Examples
20450///
20451/// ```rust,ignore
20452/// let _result = pharos_native::runtime::json_commands::find_user_by_id_business_logic_local(std::path::Path::new("."), 1);
20453/// ```
20454///
20455/// # Related
20456///
20457/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20458pub fn find_user_by_id_business_logic_local(root: &Value, user_id: i64) -> Option<Value> {
20459 bounded_state_array(root, "users")
20460 .iter()
20461 .find(|row| row.get("id").and_then(Value::as_i64) == Some(user_id))
20462 .cloned()
20463}
20464
20465/// Returns whether a signup account type is allowed by the closed set.
20466///
20467/// # Parameters
20468///
20469/// - `account_type`: Input consumed by [`crate::runtime::json_commands::dynamic_signup_account_type_allowed`].
20470///
20471/// # Returns
20472///
20473/// Returns `true` when the condition checked by [`crate::runtime::json_commands::dynamic_signup_account_type_allowed`] holds and `false` otherwise.
20474///
20475/// # Examples
20476///
20477/// ```rust,ignore
20478/// let _result = pharos_native::runtime::json_commands::dynamic_signup_account_type_allowed(todo!());
20479/// ```
20480///
20481/// # Related
20482///
20483/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20484pub fn dynamic_signup_account_type_allowed(account_type: &str) -> bool {
20485 matches!(
20486 account_type,
20487 "personal" | "business" | "technician" | "admin"
20488 )
20489}
20490
20491/// Counts slash-separated route segments.
20492///
20493/// # Parameters
20494///
20495/// - `path`: Input consumed by [`crate::runtime::json_commands::count_segments`].
20496///
20497/// # Returns
20498///
20499/// Returns the computed value produced by [`crate::runtime::json_commands::count_segments`].
20500///
20501/// # Performance
20502///
20503/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::count_segments`].
20504///
20505/// # Examples
20506///
20507/// ```rust,ignore
20508/// let _result = pharos_native::runtime::json_commands::count_segments(std::path::Path::new("fixture.json"));
20509/// ```
20510///
20511/// # Related
20512///
20513/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20514pub fn count_segments(path: &str) -> usize {
20515 path.split('/')
20516 .filter(|segment| !segment.is_empty())
20517 .count()
20518}
20519
20520/// Returns one slash-separated segment by index.
20521///
20522/// # Parameters
20523///
20524/// - `path`: Input consumed by [`crate::runtime::json_commands::get_segment`].
20525/// - `index`: Input consumed by [`crate::runtime::json_commands::get_segment`].
20526///
20527/// # Returns
20528///
20529/// Returns [`Some`] when [`crate::runtime::json_commands::get_segment`] can produce a value and [`None`] otherwise.
20530///
20531/// # Performance
20532///
20533/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::get_segment`].
20534///
20535/// # Examples
20536///
20537/// ```rust,ignore
20538/// let _result = pharos_native::runtime::json_commands::get_segment(std::path::Path::new("fixture.json"), todo!());
20539/// ```
20540///
20541/// # Related
20542///
20543/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20544pub fn get_segment(path: &str, index: usize) -> Option<String> {
20545 path.split('/')
20546 .filter(|segment| !segment.is_empty())
20547 .nth(index)
20548 .map(ToOwned::to_owned)
20549}
20550
20551/// Returns one segment's byte length.
20552///
20553/// # Parameters
20554///
20555/// - `path`: Input consumed by [`crate::runtime::json_commands::segment_length`].
20556/// - `index`: Input consumed by [`crate::runtime::json_commands::segment_length`].
20557///
20558/// # Returns
20559///
20560/// Returns the computed value produced by [`crate::runtime::json_commands::segment_length`].
20561///
20562/// # Examples
20563///
20564/// ```rust,ignore
20565/// let _result = pharos_native::runtime::json_commands::segment_length(std::path::Path::new("fixture.json"), todo!());
20566/// ```
20567///
20568/// # Related
20569///
20570/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20571pub fn segment_length(path: &str, index: usize) -> usize {
20572 get_segment(path, index)
20573 .map(|value| value.len())
20574 .unwrap_or(0)
20575}
20576
20577/// Returns one query parameter value.
20578///
20579/// # Parameters
20580///
20581/// - `query`: Input consumed by [`crate::runtime::json_commands::dynamic_query_value`].
20582/// - `key`: Input consumed by [`crate::runtime::json_commands::dynamic_query_value`].
20583///
20584/// # Returns
20585///
20586/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_query_value`] can produce a value and [`None`] otherwise.
20587///
20588/// # Examples
20589///
20590/// ```rust,ignore
20591/// let _result = pharos_native::runtime::json_commands::dynamic_query_value("page=1", "key");
20592/// ```
20593///
20594/// # Related
20595///
20596/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20597pub fn dynamic_query_value(query: &str, key: &str) -> Option<String> {
20598 request_query_value(query, key)
20599}
20600
20601/// Returns an array-valued runtime state section.
20602///
20603/// # Parameters
20604///
20605/// - `root`: Input consumed by [`crate::runtime::json_commands::state_array_runtime_local`].
20606/// - `section`: Input consumed by [`crate::runtime::json_commands::state_array_runtime_local`].
20607///
20608/// # Returns
20609///
20610/// Returns the computed value produced by [`crate::runtime::json_commands::state_array_runtime_local`].
20611///
20612/// # Examples
20613///
20614/// ```rust,ignore
20615/// let _result = pharos_native::runtime::json_commands::state_array_runtime_local(std::path::Path::new("."), "items");
20616/// ```
20617///
20618/// # Related
20619///
20620/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20621pub fn state_array_runtime_local(root: &Value, section: &str) -> Vec<Value> {
20622 bounded_state_array(root, section).to_vec()
20623}
20624
20625/// Appends bounded runtime text.
20626///
20627/// # Parameters
20628///
20629/// - `output`: Input consumed by [`crate::runtime::json_commands::text_append_runtime_local`].
20630/// - `value`: Input consumed by [`crate::runtime::json_commands::text_append_runtime_local`].
20631///
20632/// # Returns
20633///
20634/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_append_runtime_local`] holds and `false` otherwise.
20635///
20636/// # Examples
20637///
20638/// ```rust,ignore
20639/// let _result = pharos_native::runtime::json_commands::text_append_runtime_local(todo!(), "value");
20640/// ```
20641///
20642/// # Related
20643///
20644/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20645pub fn text_append_runtime_local(output: &mut String, value: &str) -> bool {
20646 text_buffer_append_format(output, value)
20647}
20648
20649/// Escapes runtime text for HTML.
20650///
20651/// # Parameters
20652///
20653/// - `value`: Input consumed by [`crate::runtime::json_commands::html_escaped`].
20654///
20655/// # Returns
20656///
20657/// Returns the computed value produced by [`crate::runtime::json_commands::html_escaped`].
20658///
20659/// # Examples
20660///
20661/// ```rust,ignore
20662/// let _result = pharos_native::runtime::json_commands::html_escaped("value");
20663/// ```
20664///
20665/// # Related
20666///
20667/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20668pub fn html_escaped(value: &str) -> String {
20669 html_escape_text(value)
20670}
20671
20672/// Adds an escaped string to a page parameter map.
20673///
20674/// # Parameters
20675///
20676/// - `map`: Input consumed by [`crate::runtime::json_commands::runtime_page_params_add_string`].
20677/// - `key`: Input consumed by [`crate::runtime::json_commands::runtime_page_params_add_string`].
20678/// - `value`: Input consumed by [`crate::runtime::json_commands::runtime_page_params_add_string`].
20679///
20680/// # Returns
20681///
20682/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_page_params_add_string`] holds and `false` otherwise.
20683///
20684/// # Examples
20685///
20686/// ```rust,ignore
20687/// let _result = pharos_native::runtime::json_commands::runtime_page_params_add_string(todo!(), "key", "value");
20688/// ```
20689///
20690/// # Related
20691///
20692/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20693pub fn runtime_page_params_add_string(
20694 map: &mut Map<String, Value>,
20695 key: &str,
20696 value: &str,
20697) -> bool {
20698 if key.is_empty() {
20699 return false;
20700 }
20701 map.insert(key.to_owned(), Value::String(value.to_owned()));
20702 true
20703}
20704
20705/// Adds an escaped string to a page parameter map.
20706///
20707/// # Parameters
20708///
20709/// - `map`: Input consumed by [`crate::runtime::json_commands::runtime_page_params_add_escaped_string`].
20710/// - `key`: Input consumed by [`crate::runtime::json_commands::runtime_page_params_add_escaped_string`].
20711/// - `value`: Input consumed by [`crate::runtime::json_commands::runtime_page_params_add_escaped_string`].
20712///
20713/// # Returns
20714///
20715/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_page_params_add_escaped_string`] holds and `false` otherwise.
20716///
20717/// # Examples
20718///
20719/// ```rust,ignore
20720/// let _result = pharos_native::runtime::json_commands::runtime_page_params_add_escaped_string(todo!(), "key", "value");
20721/// ```
20722///
20723/// # Related
20724///
20725/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20726pub fn runtime_page_params_add_escaped_string(
20727 map: &mut Map<String, Value>,
20728 key: &str,
20729 value: &str,
20730) -> bool {
20731 runtime_page_params_add_string(map, key, &html_escape_text(value))
20732}
20733
20734/// Serializes a page parameter map.
20735///
20736/// # Parameters
20737///
20738/// - `map`: Input consumed by [`crate::runtime::json_commands::runtime_page_params_write`].
20739///
20740/// # Returns
20741///
20742/// Returns [`Some`] when [`crate::runtime::json_commands::runtime_page_params_write`] can produce a value and [`None`] otherwise.
20743///
20744/// # Examples
20745///
20746/// ```rust,ignore
20747/// let _result = pharos_native::runtime::json_commands::runtime_page_params_write(todo!());
20748/// ```
20749///
20750/// # Related
20751///
20752/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20753pub fn runtime_page_params_write(map: &Map<String, Value>) -> Option<String> {
20754 compact(&Value::Object(map.clone()))
20755}
20756
20757/// Returns a validated management path segment.
20758///
20759/// # Parameters
20760///
20761/// - `value`: Input consumed by [`crate::runtime::json_commands::runtime_manage_path_segment`].
20762///
20763/// # Returns
20764///
20765/// Returns [`Some`] when [`crate::runtime::json_commands::runtime_manage_path_segment`] can produce a value and [`None`] otherwise.
20766///
20767/// # Performance
20768///
20769/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::runtime_manage_path_segment`].
20770///
20771/// # Examples
20772///
20773/// ```rust,ignore
20774/// let _result = pharos_native::runtime::json_commands::runtime_manage_path_segment("value");
20775/// ```
20776///
20777/// # Related
20778///
20779/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20780pub fn runtime_manage_path_segment(value: &str) -> Option<String> {
20781 (!value.is_empty()
20782 && value.len() <= 128
20783 && value
20784 .bytes()
20785 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_')))
20786 .then(|| value.to_owned())
20787}
20788
20789/// Ensures a runtime text buffer has bounded capacity.
20790///
20791/// # Parameters
20792///
20793/// - `output`: Input consumed by [`crate::runtime::json_commands::runtime_text_buffer_reserve`].
20794/// - `additional`: Input consumed by [`crate::runtime::json_commands::runtime_text_buffer_reserve`].
20795///
20796/// # Returns
20797///
20798/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_text_buffer_reserve`] holds and `false` otherwise.
20799///
20800/// # Examples
20801///
20802/// ```rust,ignore
20803/// let _result = pharos_native::runtime::json_commands::runtime_text_buffer_reserve(todo!(), todo!());
20804/// ```
20805///
20806/// # Related
20807///
20808/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20809pub fn runtime_text_buffer_reserve(output: &mut String, additional: usize) -> bool {
20810 output
20811 .len()
20812 .checked_add(additional)
20813 .is_some_and(|length| length <= MAX_TEXT_BYTES)
20814 && output.try_reserve(additional).is_ok()
20815}
20816
20817/// Checks whether a UI attribute name is safe.
20818///
20819/// # Parameters
20820///
20821/// - `name`: Input consumed by [`crate::runtime::json_commands::runtime_ui_attribute_is_valid`].
20822///
20823/// # Returns
20824///
20825/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_ui_attribute_is_valid`] holds and `false` otherwise.
20826///
20827/// # Performance
20828///
20829/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::runtime_ui_attribute_is_valid`].
20830///
20831/// # Examples
20832///
20833/// ```rust,ignore
20834/// let _result = pharos_native::runtime::json_commands::runtime_ui_attribute_is_valid("name");
20835/// ```
20836///
20837/// # Related
20838///
20839/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20840pub fn runtime_ui_attribute_is_valid(name: &str) -> bool {
20841 !name.is_empty()
20842 && name
20843 .bytes()
20844 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_')
20845}
20846
20847/// Compares two strings exactly.
20848///
20849/// # Parameters
20850///
20851/// - `left`: Input consumed by [`crate::runtime::json_commands::streq`].
20852/// - `right`: Input consumed by [`crate::runtime::json_commands::streq`].
20853///
20854/// # Returns
20855///
20856/// Returns `true` when the condition checked by [`crate::runtime::json_commands::streq`] holds and `false` otherwise.
20857///
20858/// # Examples
20859///
20860/// ```rust,ignore
20861/// let _result = pharos_native::runtime::json_commands::streq(todo!(), todo!());
20862/// ```
20863///
20864/// # Related
20865///
20866/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20867pub fn streq(left: &str, right: &str) -> bool {
20868 left == right
20869}
20870
20871/// Appends formatted text to a runtime buffer.
20872///
20873/// # Parameters
20874///
20875/// - `output`: Input consumed by [`crate::runtime::json_commands::text_append_format_runtime_local`].
20876/// - `value`: Input consumed by [`crate::runtime::json_commands::text_append_format_runtime_local`].
20877///
20878/// # Returns
20879///
20880/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_append_format_runtime_local`] holds and `false` otherwise.
20881///
20882/// # Examples
20883///
20884/// ```rust,ignore
20885/// let _result = pharos_native::runtime::json_commands::text_append_format_runtime_local(todo!(), "value");
20886/// ```
20887///
20888/// # Related
20889///
20890/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20891pub fn text_append_format_runtime_local(output: &mut String, value: &str) -> bool {
20892 text_buffer_append_format(output, value)
20893}
20894
20895/// Normalizes an email body to CRLF line endings.
20896///
20897/// # Parameters
20898///
20899/// - `value`: Input consumed by [`crate::runtime::json_commands::email_body_crlf_dup`].
20900///
20901/// # Returns
20902///
20903/// Returns the computed value produced by [`crate::runtime::json_commands::email_body_crlf_dup`].
20904///
20905/// # Examples
20906///
20907/// ```rust,ignore
20908/// let _result = pharos_native::runtime::json_commands::email_body_crlf_dup("value");
20909/// ```
20910///
20911/// # Related
20912///
20913/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20914pub fn email_body_crlf_dup(value: &str) -> String {
20915 value
20916 .replace("\r\n", "\n")
20917 .replace('\r', "\n")
20918 .replace('\n', "\r\n")
20919}
20920
20921/// Returns a stable RFC-style date header value.
20922///
20923/// # Returns
20924///
20925/// Returns the computed value produced by [`crate::runtime::json_commands::email_date_header_dup`].
20926///
20927/// # Examples
20928///
20929/// ```rust,ignore
20930/// let _result = pharos_native::runtime::json_commands::email_date_header_dup();
20931/// ```
20932///
20933/// # Related
20934///
20935/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20936pub fn email_date_header_dup() -> String {
20937 format!(
20938 "Date: {}",
20939 super::dynamic::now_unix_epoch_text_logic_local()
20940 )
20941}
20942
20943/// Resolves one email configuration value from app and runtime configuration.
20944///
20945/// # Parameters
20946///
20947/// - `app_root`: Input consumed by [`crate::runtime::json_commands::email_effective_value`].
20948/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::email_effective_value`].
20949/// - `prefix`: Input consumed by [`crate::runtime::json_commands::email_effective_value`].
20950/// - `suffix`: Input consumed by [`crate::runtime::json_commands::email_effective_value`].
20951///
20952/// # Returns
20953///
20954/// Returns [`Some`] when [`crate::runtime::json_commands::email_effective_value`] can produce a value and [`None`] otherwise.
20955///
20956/// # Examples
20957///
20958/// ```rust,ignore
20959/// let _result = pharos_native::runtime::json_commands::email_effective_value(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!());
20960/// ```
20961///
20962/// # Related
20963///
20964/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20965pub fn email_effective_value(
20966 app_root: &Path,
20967 runtime_conf_path: Option<&Path>,
20968 prefix: &str,
20969 suffix: &str,
20970) -> Option<String> {
20971 let key = if prefix.is_empty() {
20972 suffix.to_owned()
20973 } else {
20974 format!("{prefix}_{suffix}")
20975 };
20976 config::dynamic_config_effective_value(app_root, runtime_conf_path, &key)
20977}
20978
20979/// Looks up one bounded email template variable.
20980///
20981/// # Parameters
20982///
20983/// - `vars`: Input consumed by [`crate::runtime::json_commands::email_template_lookup_var`].
20984/// - `key`: Input consumed by [`crate::runtime::json_commands::email_template_lookup_var`].
20985///
20986/// # Returns
20987///
20988/// Returns [`Some`] when [`crate::runtime::json_commands::email_template_lookup_var`] can produce a value and [`None`] otherwise.
20989///
20990/// # Examples
20991///
20992/// ```rust,ignore
20993/// let _result = pharos_native::runtime::json_commands::email_template_lookup_var(todo!(), "key");
20994/// ```
20995///
20996/// # Related
20997///
20998/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
20999pub fn email_template_lookup_var(vars: &Value, key: &str) -> Option<String> {
21000 vars.get(key)
21001 .map(value_to_text)
21002 .filter(|value| value.len() <= MAX_TEXT_BYTES)
21003}
21004
21005/// Appends one YAML block line after removing the declared indentation.
21006///
21007/// # Parameters
21008///
21009/// - `buffer`: Input consumed by [`crate::runtime::json_commands::email_yaml_append_block_line`].
21010/// - `line`: Input consumed by [`crate::runtime::json_commands::email_yaml_append_block_line`].
21011/// - `indent_to_strip`: Input consumed by [`crate::runtime::json_commands::email_yaml_append_block_line`].
21012///
21013/// # Returns
21014///
21015/// Returns `true` when the condition checked by [`crate::runtime::json_commands::email_yaml_append_block_line`] holds and `false` otherwise.
21016///
21017/// # Examples
21018///
21019/// ```rust,ignore
21020/// let _result = pharos_native::runtime::json_commands::email_yaml_append_block_line(&mut [], todo!(), todo!());
21021/// ```
21022///
21023/// # Related
21024///
21025/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21026pub fn email_yaml_append_block_line(
21027 buffer: &mut String,
21028 line: &str,
21029 indent_to_strip: usize,
21030) -> bool {
21031 let stripped = line.get(line.len().min(indent_to_strip)..).unwrap_or("");
21032 if buffer.len().saturating_add(stripped.len() + 2) > MAX_TEXT_BYTES {
21033 return false;
21034 }
21035 buffer.push_str(stripped);
21036 buffer.push('\n');
21037 true
21038}
21039
21040/// Stores a completed YAML block in a mutable template object.
21041///
21042/// # Parameters
21043///
21044/// - `parsed`: Input consumed by [`crate::runtime::json_commands::email_yaml_finalize_block`].
21045/// - `key`: Input consumed by [`crate::runtime::json_commands::email_yaml_finalize_block`].
21046/// - `buffer`: Input consumed by [`crate::runtime::json_commands::email_yaml_finalize_block`].
21047///
21048/// # Returns
21049///
21050/// Returns `true` when the condition checked by [`crate::runtime::json_commands::email_yaml_finalize_block`] holds and `false` otherwise.
21051///
21052/// # Examples
21053///
21054/// ```rust,ignore
21055/// let _result = pharos_native::runtime::json_commands::email_yaml_finalize_block(todo!(), "key", &mut []);
21056/// ```
21057///
21058/// # Related
21059///
21060/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21061pub fn email_yaml_finalize_block(parsed: &mut Map<String, Value>, key: &str, buffer: &str) -> bool {
21062 if key.is_empty() {
21063 return false;
21064 }
21065 parsed.insert(
21066 key.to_owned(),
21067 Value::String(buffer.trim_end_matches('\n').to_owned()),
21068 );
21069 true
21070}
21071
21072/// Stores a scalar YAML field in a mutable template object.
21073///
21074/// # Parameters
21075///
21076/// - `parsed`: Input consumed by [`crate::runtime::json_commands::email_yaml_store_field`].
21077/// - `key`: Input consumed by [`crate::runtime::json_commands::email_yaml_store_field`].
21078/// - `value`: Input consumed by [`crate::runtime::json_commands::email_yaml_store_field`].
21079///
21080/// # Returns
21081///
21082/// Returns `true` when the condition checked by [`crate::runtime::json_commands::email_yaml_store_field`] holds and `false` otherwise.
21083///
21084/// # Examples
21085///
21086/// ```rust,ignore
21087/// let _result = pharos_native::runtime::json_commands::email_yaml_store_field(todo!(), "key", "value");
21088/// ```
21089///
21090/// # Related
21091///
21092/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21093pub fn email_yaml_store_field(parsed: &mut Map<String, Value>, key: &str, value: &str) -> bool {
21094 if key.is_empty() || value.len() > MAX_TEXT_BYTES {
21095 return false;
21096 }
21097 parsed.insert(
21098 key.to_owned(),
21099 Value::String(value.trim().trim_matches(['"', '\'']).to_owned()),
21100 );
21101 true
21102}
21103
21104/// Clears a parsed email YAML template.
21105///
21106/// # Parameters
21107///
21108/// - `parsed`: Input consumed by [`crate::runtime::json_commands::email_yaml_template_clear`].
21109///
21110/// # Returns
21111///
21112/// Returns after [`crate::runtime::json_commands::email_yaml_template_clear`] applies its in-place effect.
21113///
21114/// # Examples
21115///
21116/// ```rust,ignore
21117/// let _result = pharos_native::runtime::json_commands::email_yaml_template_clear(todo!());
21118/// ```
21119///
21120/// # Related
21121///
21122/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21123pub fn email_yaml_template_clear(parsed: &mut Map<String, Value>) {
21124 parsed.clear();
21125}
21126
21127/// Resolves a namespaced email configuration key.
21128///
21129/// # Parameters
21130///
21131/// - `prefix`: Input consumed by [`crate::runtime::json_commands::email_config_key_dup`].
21132/// - `suffix`: Input consumed by [`crate::runtime::json_commands::email_config_key_dup`].
21133///
21134/// # Returns
21135///
21136/// Returns [`Some`] when [`crate::runtime::json_commands::email_config_key_dup`] can produce a value and [`None`] otherwise.
21137///
21138/// # Examples
21139///
21140/// ```rust,ignore
21141/// let _result = pharos_native::runtime::json_commands::email_config_key_dup(todo!(), todo!());
21142/// ```
21143///
21144/// # Related
21145///
21146/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21147pub fn email_config_key_dup(prefix: &str, suffix: &str) -> Option<String> {
21148 (!suffix.is_empty()).then(|| {
21149 if prefix.is_empty() {
21150 suffix.to_owned()
21151 } else {
21152 format!("{prefix}_{suffix}")
21153 }
21154 })
21155}
21156
21157/// Rejects CRLF header injection in email field values.
21158///
21159/// # Parameters
21160///
21161/// - `value`: Input consumed by [`crate::runtime::json_commands::email_has_header_breaks`].
21162///
21163/// # Returns
21164///
21165/// Returns `true` when the condition checked by [`crate::runtime::json_commands::email_has_header_breaks`] holds and `false` otherwise.
21166///
21167/// # Examples
21168///
21169/// ```rust,ignore
21170/// let _result = pharos_native::runtime::json_commands::email_has_header_breaks("value");
21171/// ```
21172///
21173/// # Related
21174///
21175/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21176pub fn email_has_header_breaks(value: &str) -> bool {
21177 value.contains('\r') || value.contains('\n')
21178}
21179
21180/// Removes one layer of matching email-template quotes.
21181///
21182/// # Parameters
21183///
21184/// - `value`: Input consumed by [`crate::runtime::json_commands::email_template_unquote_dup`].
21185///
21186/// # Returns
21187///
21188/// Returns the computed value produced by [`crate::runtime::json_commands::email_template_unquote_dup`].
21189///
21190/// # Examples
21191///
21192/// ```rust,ignore
21193/// let _result = pharos_native::runtime::json_commands::email_template_unquote_dup("value");
21194/// ```
21195///
21196/// # Related
21197///
21198/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21199pub fn email_template_unquote_dup(value: &str) -> String {
21200 let value = value.trim();
21201 if value.len() >= 2
21202 && ((value.starts_with('"') && value.ends_with('"'))
21203 || (value.starts_with('\'') && value.ends_with('\'')))
21204 {
21205 value[1..value.len() - 1].to_owned()
21206 } else {
21207 value.to_owned()
21208 }
21209}
21210
21211/// Releases a parsed URL value by normal Rust ownership rules.
21212///
21213/// # Parameters
21214///
21215/// - `value`: Input consumed by [`crate::runtime::json_commands::free_parsed_http_url_logic_local`].
21216///
21217/// # Returns
21218///
21219/// Returns after [`crate::runtime::json_commands::free_parsed_http_url_logic_local`] applies its in-place effect.
21220///
21221/// # Examples
21222///
21223/// ```rust,ignore
21224/// let _result = pharos_native::runtime::json_commands::free_parsed_http_url_logic_local("value");
21225/// ```
21226///
21227/// # Related
21228///
21229/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21230pub fn free_parsed_http_url_logic_local(value: Option<(String, String, Option<u16>)>) {
21231 drop(value);
21232}
21233
21234/// Renders an app-owned shared fragment from a parameter document.
21235///
21236/// # Parameters
21237///
21238/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_shared_asset_fragment_from_doc`].
21239/// - `relative_path`: Input consumed by [`crate::runtime::json_commands::render_shared_asset_fragment_from_doc`].
21240/// - `params`: Input consumed by [`crate::runtime::json_commands::render_shared_asset_fragment_from_doc`].
21241///
21242/// # Returns
21243///
21244/// Returns [`Some`] when [`crate::runtime::json_commands::render_shared_asset_fragment_from_doc`] can produce a value and [`None`] otherwise.
21245///
21246/// # Examples
21247///
21248/// ```rust,ignore
21249/// let _result = pharos_native::runtime::json_commands::render_shared_asset_fragment_from_doc(std::path::Path::new("apps/dev_docs"), "page", todo!());
21250/// ```
21251///
21252/// # Related
21253///
21254/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21255pub fn render_shared_asset_fragment_from_doc(
21256 app_root: &Path,
21257 relative_path: &str,
21258 params: &Value,
21259) -> Option<String> {
21260 generated::render_generated_admin_fragment_from_value_runtime(
21261 app_root,
21262 relative_path,
21263 params.as_object()?,
21264 )
21265}
21266
21267/// Builds profile page parameters from a state root.
21268///
21269/// # Parameters
21270///
21271/// - `root`: Input consumed by [`crate::runtime::json_commands::profile_render_page_params_json`].
21272/// - `user_id`: Input consumed by [`crate::runtime::json_commands::profile_render_page_params_json`].
21273/// - `app_root`: Input consumed by [`crate::runtime::json_commands::profile_render_page_params_json`].
21274///
21275/// # Returns
21276///
21277/// Returns [`Some`] when [`crate::runtime::json_commands::profile_render_page_params_json`] can produce a value and [`None`] otherwise.
21278///
21279/// # Examples
21280///
21281/// ```rust,ignore
21282/// let _result = pharos_native::runtime::json_commands::profile_render_page_params_json(std::path::Path::new("."), 1, std::path::Path::new("apps/dev_docs"));
21283/// ```
21284///
21285/// # Related
21286///
21287/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21288pub fn profile_render_page_params_json(
21289 root: &Value,
21290 user_id: i64,
21291 app_root: &Path,
21292) -> Option<String> {
21293 dynamic_profile_page_params_json(root, user_id, app_root)
21294}
21295
21296/// Tests whether a path ends with a validated suffix.
21297///
21298/// # Parameters
21299///
21300/// - `path`: Input consumed by [`crate::runtime::json_commands::runtime_path_has_suffix`].
21301/// - `suffix`: Input consumed by [`crate::runtime::json_commands::runtime_path_has_suffix`].
21302///
21303/// # Returns
21304///
21305/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_path_has_suffix`] holds and `false` otherwise.
21306///
21307/// # Examples
21308///
21309/// ```rust,ignore
21310/// let _result = pharos_native::runtime::json_commands::runtime_path_has_suffix(std::path::Path::new("fixture.json"), todo!());
21311/// ```
21312///
21313/// # Related
21314///
21315/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21316pub fn runtime_path_has_suffix(path: &Path, suffix: &str) -> bool {
21317 !suffix.is_empty() && path.to_string_lossy().ends_with(suffix)
21318}
21319
21320/// Renders availability rows from persisted business state.
21321///
21322/// # Parameters
21323///
21324/// - `root`: Input consumed by [`crate::runtime::json_commands::dynamic_build_business_admin_availability_rows_html`].
21325/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_build_business_admin_availability_rows_html`].
21326/// - `app_root`: Input consumed by [`crate::runtime::json_commands::dynamic_build_business_admin_availability_rows_html`].
21327///
21328/// # Returns
21329///
21330/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_build_business_admin_availability_rows_html`] can produce a value and [`None`] otherwise.
21331///
21332/// # Examples
21333///
21334/// ```rust,ignore
21335/// let _result = pharos_native::runtime::json_commands::dynamic_build_business_admin_availability_rows_html(std::path::Path::new("."), "demo", std::path::Path::new("apps/dev_docs"));
21336/// ```
21337///
21338/// # Related
21339///
21340/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21341pub fn dynamic_build_business_admin_availability_rows_html(
21342 root: &Value,
21343 business_slug: &str,
21344 app_root: &Path,
21345) -> Option<String> {
21346 dynamic_render_business_admin_availability_rows_html(root, business_slug, app_root)
21347}
21348
21349/// Finds a Google Calendar connection scoped to a business.
21350///
21351/// # Parameters
21352///
21353/// - `root`: Input consumed by [`crate::runtime::json_commands::state_find_google_calendar_connection_for_business_logic_local`].
21354/// - `business_id`: Input consumed by [`crate::runtime::json_commands::state_find_google_calendar_connection_for_business_logic_local`].
21355///
21356/// # Returns
21357///
21358/// Returns [`Some`] when [`crate::runtime::json_commands::state_find_google_calendar_connection_for_business_logic_local`] can produce a value and [`None`] otherwise.
21359///
21360/// # Performance
21361///
21362/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::state_find_google_calendar_connection_for_business_logic_local`].
21363///
21364/// # Examples
21365///
21366/// ```rust,ignore
21367/// let _result = pharos_native::runtime::json_commands::state_find_google_calendar_connection_for_business_logic_local(std::path::Path::new("."), todo!());
21368/// ```
21369///
21370/// # Related
21371///
21372/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21373pub fn state_find_google_calendar_connection_for_business_logic_local(
21374 root: &Value,
21375 business_id: i64,
21376) -> Option<Value> {
21377 bounded_state_array(root, "calendar_connections")
21378 .iter()
21379 .find(|row| {
21380 row.get("business_id").and_then(Value::as_i64) == Some(business_id)
21381 && row
21382 .get("provider")
21383 .and_then(Value::as_str)
21384 .is_some_and(|value| value.eq_ignore_ascii_case("google"))
21385 })
21386 .cloned()
21387}
21388
21389/// Finds one payment provider account row for a business and provider.
21390pub fn state_find_payment_provider_account_for_business_logic_local(
21391 root: &Value,
21392 business_id: i64,
21393 provider: &str,
21394) -> Option<Value> {
21395 if provider.is_empty() {
21396 return None;
21397 }
21398 bounded_state_array(root, "payment_provider_accounts")
21399 .iter()
21400 .find(|row| {
21401 row.get("owner_resource_type")
21402 .and_then(Value::as_str)
21403 .unwrap_or("business")
21404 == "business"
21405 && row.get("owner_resource_id").and_then(Value::as_i64) == Some(business_id)
21406 && row
21407 .get("provider_id")
21408 .or_else(|| row.get("provider"))
21409 .and_then(Value::as_str)
21410 .is_some_and(|value| value.eq_ignore_ascii_case(provider))
21411 })
21412 .cloned()
21413}
21414
21415fn payment_provider_display_label(provider_id: &str) -> String {
21416 if provider_id.eq_ignore_ascii_case("stripe") {
21417 return "Stripe".to_owned();
21418 }
21419 let normalized = provider_id.replace(['_', '-'], " ");
21420 let mut chars = normalized.chars();
21421 let Some(first) = chars.next() else {
21422 return "Provider".to_owned();
21423 };
21424 format!("{}{}", first.to_uppercase(), chars.as_str())
21425}
21426
21427fn business_payment_provider_ui_config(app_root: &Path) -> (String, String) {
21428 let manifest = read_json_or_yaml_file(&app_root.join("pharos.app.json"))
21429 .or_else(|| read_json_or_yaml_file(&app_root.join("pharos.app.yml")));
21430 let provider_id = manifest
21431 .as_ref()
21432 .and_then(|root| root.get("payments"))
21433 .and_then(|payments| payments.get("provider_management"))
21434 .and_then(|management| management.get("supported_providers"))
21435 .and_then(Value::as_array)
21436 .into_iter()
21437 .flatten()
21438 .find_map(|provider| {
21439 provider
21440 .get("provider")
21441 .and_then(Value::as_str)
21442 .or_else(|| provider.as_str())
21443 })
21444 .filter(|provider| {
21445 !provider.is_empty()
21446 && provider.len() <= 64
21447 && provider
21448 .bytes()
21449 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
21450 })
21451 .unwrap_or("stripe")
21452 .to_owned();
21453 let label = payment_provider_display_label(&provider_id);
21454 (provider_id, label)
21455}
21456
21457fn business_payment_provider_paths(
21458 business_slug: &str,
21459 payment_provider_id: &str,
21460) -> (String, String, String) {
21461 (
21462 format!("/businesses/{business_slug}/payment-providers/{payment_provider_id}/connect/"),
21463 format!("/businesses/{business_slug}/payment-providers/{payment_provider_id}/refresh/"),
21464 format!("/businesses/{business_slug}/payment-providers/{payment_provider_id}/disconnect/"),
21465 )
21466}
21467
21468fn business_payment_provider_status(
21469 root: &Value,
21470 business_id: i64,
21471 payment_provider_id: &str,
21472 payment_provider_label: &str,
21473) -> (String, String) {
21474 let payment_provider_account = state_find_payment_provider_account_for_business_logic_local(
21475 root,
21476 business_id,
21477 payment_provider_id,
21478 );
21479 let status_label = payment_provider_account
21480 .as_ref()
21481 .and_then(|row| row.get("connection_state").or_else(|| row.get("status")))
21482 .and_then(Value::as_str)
21483 .map(|status| match status {
21484 "connected" => "Connected",
21485 "connect_requested" | "redirect_pending" | "callback_received" => "Onboarding started",
21486 "verification_pending" => "Verification pending",
21487 "restricted" => "Connected with restrictions",
21488 "revoked_externally" => "Revoked externally",
21489 "failed" => "Failed",
21490 _ => "Disconnected",
21491 })
21492 .unwrap_or("Disconnected")
21493 .to_owned();
21494 let status_detail = payment_provider_account
21495 .as_ref()
21496 .and_then(|row| {
21497 row.get("status_reason_detail")
21498 .or_else(|| row.get("last_error_message"))
21499 })
21500 .and_then(Value::as_str)
21501 .filter(|value| !value.is_empty())
21502 .map(ToOwned::to_owned)
21503 .unwrap_or_else(|| match status_label.as_str() {
21504 "Connected" => format!(
21505 "{payment_provider_label} account linked and available for business payments."
21506 ),
21507 "Onboarding started" => format!(
21508 "{payment_provider_label} onboarding is in progress and waiting for callback completion."
21509 ),
21510 "Verification pending" => {
21511 format!(
21512 "{payment_provider_label} returned successfully, but the account still needs authoritative capability verification."
21513 )
21514 }
21515 "Connected with restrictions" => {
21516 format!(
21517 "{payment_provider_label} account linked but still restricted for one or more capabilities."
21518 )
21519 }
21520 "Revoked externally" => {
21521 format!(
21522 "The provider reported that the {payment_provider_label} connection is no longer valid."
21523 )
21524 }
21525 "Failed" => format!(
21526 "The last {payment_provider_label} operation failed. Refresh or reconnect to retry."
21527 ),
21528 _ => "No business payment provider is connected yet.".to_owned(),
21529 });
21530 (status_label, status_detail)
21531}
21532
21533fn payment_provider_action_button_html(
21534 app_root: &Path,
21535 id: &str,
21536 label: &str,
21537 class_name: &str,
21538) -> Option<String> {
21539 render_fragment_with_params(
21540 app_root,
21541 "templates/shared/fragments/ui_button.html",
21542 Map::from_iter([
21543 ("id".to_owned(), Value::String(id.to_owned())),
21544 ("button_type".to_owned(), Value::String("submit".to_owned())),
21545 (
21546 "class_name".to_owned(),
21547 Value::String(class_name.to_owned()),
21548 ),
21549 ("attributes_html".to_owned(), Value::String(String::new())),
21550 ("content_html".to_owned(), Value::String(label.to_owned())),
21551 ]),
21552 )
21553}
21554
21555/// Builds payment provider setup page parameters for one business.
21556///
21557/// # Parameters
21558///
21559/// - `root`: Loaded UCAL dynamic runtime state.
21560/// - `business_slug`: Route-owned business slug.
21561/// - `app_root`: UCAL app artifact root used for declared fragments.
21562///
21563/// # Returns
21564///
21565/// Returns JSON template parameters when the business and declared fragments can
21566/// be resolved. The values intentionally include only paths, labels, and
21567/// rendered button fragments; provider secrets and OAuth credentials are never
21568/// included in the page payload.
21569pub fn dynamic_payment_provider_setup_page_params_from_root_json(
21570 root: &Value,
21571 business_slug: &str,
21572 app_root: &Path,
21573) -> Option<String> {
21574 let business = bounded_state_array(root, "businesses")
21575 .iter()
21576 .find(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))?;
21577 let business_id = business.get("id").and_then(Value::as_i64)?;
21578 let business_name = business.get("name").map(value_to_text).unwrap_or_default();
21579 let (payment_provider_id, payment_provider_label) =
21580 business_payment_provider_ui_config(app_root);
21581 let (payment_provider_status_label, payment_provider_status_detail) =
21582 business_payment_provider_status(
21583 root,
21584 business_id,
21585 &payment_provider_id,
21586 &payment_provider_label,
21587 );
21588 let (
21589 payment_provider_connect_path,
21590 payment_provider_refresh_path,
21591 payment_provider_disconnect_path,
21592 ) = business_payment_provider_paths(business_slug, &payment_provider_id);
21593 let mut params = Map::from_iter([
21594 ("business_name".to_owned(), Value::String(business_name)),
21595 (
21596 "business_admin_path".to_owned(),
21597 Value::String(format!("/businesses/{business_slug}/")),
21598 ),
21599 (
21600 "payment_provider_label".to_owned(),
21601 Value::String(payment_provider_label.clone()),
21602 ),
21603 (
21604 "payment_provider_status_label".to_owned(),
21605 Value::String(payment_provider_status_label),
21606 ),
21607 (
21608 "payment_provider_status_detail".to_owned(),
21609 Value::String(payment_provider_status_detail),
21610 ),
21611 (
21612 "payment_provider_connect_path".to_owned(),
21613 Value::String(payment_provider_connect_path),
21614 ),
21615 (
21616 "payment_provider_refresh_path".to_owned(),
21617 Value::String(payment_provider_refresh_path),
21618 ),
21619 (
21620 "payment_provider_disconnect_path".to_owned(),
21621 Value::String(payment_provider_disconnect_path),
21622 ),
21623 (
21624 "payment_provider_connect_button_ui_html".to_owned(),
21625 Value::String(payment_provider_action_button_html(
21626 app_root,
21627 "payment_provider_connect_button",
21628 &format!("Connect {payment_provider_label}"),
21629 "btn btn-primary",
21630 )?),
21631 ),
21632 (
21633 "payment_provider_refresh_button_ui_html".to_owned(),
21634 Value::String(payment_provider_action_button_html(
21635 app_root,
21636 "payment_provider_refresh_button",
21637 "Refresh status",
21638 "btn btn-secondary",
21639 )?),
21640 ),
21641 (
21642 "payment_provider_disconnect_button_ui_html".to_owned(),
21643 Value::String(payment_provider_action_button_html(
21644 app_root,
21645 "payment_provider_disconnect_button",
21646 "Disconnect",
21647 "btn btn-danger",
21648 )?),
21649 ),
21650 ]);
21651 for (key, value) in super::payment_provider::payment_provider_template_params(
21652 root,
21653 app_root,
21654 business_slug,
21655 &payment_provider_id,
21656 ) {
21657 params.insert(key, value);
21658 }
21659 for key in [
21660 "owner_payment_provider_status_label",
21661 "owner_payment_provider_status_icon",
21662 "owner_payment_provider_mode_demo_label",
21663 "owner_payment_provider_mode_live_label",
21664 "owner_payment_provider_credentials_state_label",
21665 "owner_payment_provider_setup_eyebrow",
21666 "owner_payment_provider_setup_headline",
21667 "owner_payment_provider_setup_body",
21668 "owner_payment_provider_credentials_headline",
21669 "owner_payment_provider_credentials_body",
21670 "owner_payment_provider_client_id_label",
21671 "owner_payment_provider_client_id_placeholder",
21672 "owner_payment_provider_client_secret_label",
21673 "owner_payment_provider_client_secret_placeholder",
21674 "owner_payment_provider_save_credentials_label",
21675 "owner_payment_provider_delete_credentials_label",
21676 "owner_payment_provider_connect_label",
21677 "owner_payment_provider_refresh_label",
21678 "owner_payment_provider_back_label",
21679 ] {
21680 if let Some(value) = super::payment_provider::payment_provider_copy_value(app_root, key) {
21681 params.insert(key.to_owned(), Value::String(value));
21682 }
21683 }
21684 params.insert(
21685 "owner_payment_provider_action_href".to_owned(),
21686 Value::String(format!(
21687 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/test/"
21688 )),
21689 );
21690 params.insert(
21691 "owner_payment_provider_refresh_href".to_owned(),
21692 Value::String(format!(
21693 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/refresh/"
21694 )),
21695 );
21696 params.insert(
21697 "owner_payment_provider_credentials_href".to_owned(),
21698 Value::String(format!(
21699 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/credentials/"
21700 )),
21701 );
21702 params.insert(
21703 "owner_payment_provider_credentials_delete_href".to_owned(),
21704 Value::String(format!(
21705 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/credentials/delete/"
21706 )),
21707 );
21708 params.insert(
21709 "owner_payment_provider_mode_href".to_owned(),
21710 Value::String(format!(
21711 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/mode/"
21712 )),
21713 );
21714 params.insert(
21715 "payment_provider_feedback_message".to_owned(),
21716 Value::String(String::new()),
21717 );
21718 compact(&Value::Object(params))
21719}
21720
21721pub fn dynamic_business_edit_external_integrations_html(
21722 root: &Value,
21723 business_slug: &str,
21724 app_root: &Path,
21725) -> Option<String> {
21726 let business = bounded_state_array(root, "businesses")
21727 .iter()
21728 .find(|row| row.get("slug").and_then(Value::as_str) == Some(business_slug))?;
21729 let business_id = business.get("id").and_then(Value::as_i64)?;
21730 let (payment_provider_id, _payment_provider_label) =
21731 business_payment_provider_ui_config(app_root);
21732 let connection_status =
21733 if state_find_google_calendar_connection_for_business_logic_local(root, business_id)
21734 .is_some()
21735 {
21736 "Connected"
21737 } else {
21738 "Disconnected"
21739 };
21740 let mut params = Map::from_iter([
21741 (
21742 "connection_status".to_owned(),
21743 Value::String(connection_status.to_owned()),
21744 ),
21745 (
21746 "business_admin_google_mode_input_ui_html".to_owned(),
21747 Value::String(render_hidden_input_fragment(
21748 app_root,
21749 "business_edit_google_mode_input",
21750 "mode",
21751 "business",
21752 )?),
21753 ),
21754 (
21755 "business_admin_google_slug_input_ui_html".to_owned(),
21756 Value::String(render_hidden_input_fragment(
21757 app_root,
21758 "business_edit_google_slug_input",
21759 "business_slug",
21760 business_slug,
21761 )?),
21762 ),
21763 (
21764 "business_admin_google_next_input_ui_html".to_owned(),
21765 Value::String(render_hidden_input_fragment(
21766 app_root,
21767 "business_edit_google_next_input",
21768 "next",
21769 &format!("/businesses/{business_slug}/edit/"),
21770 )?),
21771 ),
21772 (
21773 "payment_provider_feedback_message".to_owned(),
21774 Value::String(String::new()),
21775 ),
21776 ]);
21777 for (key, value) in super::payment_provider::payment_provider_template_params(
21778 root,
21779 app_root,
21780 business_slug,
21781 &payment_provider_id,
21782 ) {
21783 params.insert(key, value);
21784 }
21785 for key in [
21786 "owner_payment_provider_status_label",
21787 "owner_payment_provider_status_icon",
21788 "owner_payment_provider_mode_demo_label",
21789 "owner_payment_provider_mode_live_label",
21790 "owner_payment_provider_credentials_state_label",
21791 "owner_payment_provider_credentials_headline",
21792 "owner_payment_provider_credentials_body",
21793 "owner_payment_provider_client_id_label",
21794 "owner_payment_provider_client_id_placeholder",
21795 "owner_payment_provider_client_secret_label",
21796 "owner_payment_provider_client_secret_placeholder",
21797 "owner_payment_provider_save_credentials_label",
21798 "owner_payment_provider_delete_credentials_label",
21799 "owner_payment_provider_connect_label",
21800 "owner_payment_provider_refresh_label",
21801 ] {
21802 if let Some(value) = super::payment_provider::payment_provider_copy_value(app_root, key) {
21803 params.insert(key.to_owned(), Value::String(value));
21804 }
21805 }
21806 params.insert(
21807 "owner_payment_provider_action_href".to_owned(),
21808 Value::String(format!(
21809 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/test/"
21810 )),
21811 );
21812 params.insert(
21813 "owner_payment_provider_refresh_href".to_owned(),
21814 Value::String(format!(
21815 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/refresh/"
21816 )),
21817 );
21818 params.insert(
21819 "owner_payment_provider_credentials_href".to_owned(),
21820 Value::String(format!(
21821 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/credentials/"
21822 )),
21823 );
21824 params.insert(
21825 "owner_payment_provider_credentials_delete_href".to_owned(),
21826 Value::String(format!(
21827 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/credentials/delete/"
21828 )),
21829 );
21830 params.insert(
21831 "owner_payment_provider_mode_href".to_owned(),
21832 Value::String(format!(
21833 "/businesses/{business_slug}/payment-providers/{payment_provider_id}/mode/"
21834 )),
21835 );
21836 render_fragment_with_params(
21837 app_root,
21838 "templates/businesses/fragments/business_edit_external_integrations.html",
21839 params,
21840 )
21841}
21842
21843/// Releases a PostgreSQL runtime configuration.
21844///
21845/// # Parameters
21846///
21847/// - `_config`: Input consumed by [`crate::runtime::json_commands::free_postgresql_runtime_config_runtime_local`].
21848///
21849/// # Returns
21850///
21851/// Returns after [`crate::runtime::json_commands::free_postgresql_runtime_config_runtime_local`] applies its in-place effect.
21852///
21853/// # Examples
21854///
21855/// ```rust,ignore
21856/// let _result = pharos_native::runtime::json_commands::free_postgresql_runtime_config_runtime_local(todo!());
21857/// ```
21858///
21859/// # Related
21860///
21861/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21862pub fn free_postgresql_runtime_config_runtime_local<T>(_config: T) {}
21863
21864/// Finds a business by numeric identifier.
21865///
21866/// # Parameters
21867///
21868/// - `root`: Input consumed by [`crate::runtime::json_commands::find_business_by_id_runtime_local`].
21869/// - `business_id`: Input consumed by [`crate::runtime::json_commands::find_business_by_id_runtime_local`].
21870///
21871/// # Returns
21872///
21873/// Returns [`Some`] when [`crate::runtime::json_commands::find_business_by_id_runtime_local`] can produce a value and [`None`] otherwise.
21874///
21875/// # Performance
21876///
21877/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::find_business_by_id_runtime_local`].
21878///
21879/// # Examples
21880///
21881/// ```rust,ignore
21882/// let _result = pharos_native::runtime::json_commands::find_business_by_id_runtime_local(std::path::Path::new("."), todo!());
21883/// ```
21884///
21885/// # Related
21886///
21887/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21888pub fn find_business_by_id_runtime_local(root: &Value, business_id: i64) -> Option<Value> {
21889 bounded_state_array(root, "businesses")
21890 .iter()
21891 .find(|row| row.get("id").and_then(Value::as_i64) == Some(business_id))
21892 .cloned()
21893}
21894
21895/// Converts a JSON value to an integer without panicking.
21896///
21897/// # Parameters
21898///
21899/// - `value`: Input consumed by [`crate::runtime::json_commands::long_from_value_runtime_local`].
21900///
21901/// # Returns
21902///
21903/// Returns the computed value produced by [`crate::runtime::json_commands::long_from_value_runtime_local`].
21904///
21905/// # Examples
21906///
21907/// ```rust,ignore
21908/// let _result = pharos_native::runtime::json_commands::long_from_value_runtime_local("value");
21909/// ```
21910///
21911/// # Related
21912///
21913/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21914pub fn long_from_value_runtime_local(value: &Value) -> i64 {
21915 super::ops_bridge::long_from_value(value)
21916}
21917
21918/// Builds registration page parameters with one escaped error message.
21919///
21920/// # Parameters
21921///
21922/// - `root`: Input consumed by [`crate::runtime::json_commands::register_page_params_with_error_json`].
21923/// - `error_text`: Input consumed by [`crate::runtime::json_commands::register_page_params_with_error_json`].
21924///
21925/// # Returns
21926///
21927/// Returns [`Some`] when [`crate::runtime::json_commands::register_page_params_with_error_json`] can produce a value and [`None`] otherwise.
21928///
21929/// # Examples
21930///
21931/// ```rust,ignore
21932/// let _result = pharos_native::runtime::json_commands::register_page_params_with_error_json(std::path::Path::new("."), todo!());
21933/// ```
21934///
21935/// # Related
21936///
21937/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21938pub fn register_page_params_with_error_json(root: &Value, error_text: &str) -> Option<String> {
21939 let mut extra = Map::new();
21940 extra.insert("error".into(), Value::String(error_text.into()));
21941 page_params_value(root, "register", extra)
21942}
21943
21944/// Returns a path for a generated admin composition.
21945///
21946/// # Parameters
21947///
21948/// - `root`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_admin_composition_path`].
21949/// - `composition_id`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_admin_composition_path`].
21950///
21951/// # Returns
21952///
21953/// Returns [`Some`] when [`crate::runtime::json_commands::runtime_generated_surface_admin_composition_path`] can produce a value and [`None`] otherwise.
21954///
21955/// # Performance
21956///
21957/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::runtime_generated_surface_admin_composition_path`].
21958///
21959/// # Examples
21960///
21961/// ```rust,ignore
21962/// let _result = pharos_native::runtime::json_commands::runtime_generated_surface_admin_composition_path(std::path::Path::new("."), todo!());
21963/// ```
21964///
21965/// # Related
21966///
21967/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
21968pub fn runtime_generated_surface_admin_composition_path(
21969 root: &Value,
21970 composition_id: &str,
21971) -> Option<String> {
21972 root.get("compositions")?
21973 .as_array()?
21974 .iter()
21975 .find(|row| row.get("id").and_then(Value::as_str) == Some(composition_id))
21976 .and_then(|row| row.get("path").and_then(Value::as_str))
21977 .map(ToOwned::to_owned)
21978}
21979
21980/// Finds one named composition section.
21981///
21982/// # Parameters
21983///
21984/// - `composition`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_composition_find_section`].
21985/// - `section_id`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_composition_find_section`].
21986///
21987/// # Returns
21988///
21989/// Returns [`Some`] when [`crate::runtime::json_commands::runtime_generated_surface_composition_find_section`] can produce a value and [`None`] otherwise.
21990///
21991/// # Performance
21992///
21993/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::runtime_generated_surface_composition_find_section`].
21994///
21995/// # Examples
21996///
21997/// ```rust,ignore
21998/// let _result = pharos_native::runtime::json_commands::runtime_generated_surface_composition_find_section(todo!(), todo!());
21999/// ```
22000///
22001/// # Related
22002///
22003/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22004pub fn runtime_generated_surface_composition_find_section<'a>(
22005 composition: &'a Value,
22006 section_id: &str,
22007) -> Option<&'a Value> {
22008 composition
22009 .get("sections")?
22010 .as_array()?
22011 .iter()
22012 .find(|row| row.get("id").and_then(Value::as_str) == Some(section_id))
22013}
22014
22015/// Returns whether a composition section is enabled.
22016///
22017/// # Parameters
22018///
22019/// - `section`: Input consumed by [`crate::runtime::json_commands::runtime_generated_surface_composition_section_enabled`].
22020///
22021/// # Returns
22022///
22023/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_generated_surface_composition_section_enabled`] holds and `false` otherwise.
22024///
22025/// # Examples
22026///
22027/// ```rust,ignore
22028/// let _result = pharos_native::runtime::json_commands::runtime_generated_surface_composition_section_enabled("items");
22029/// ```
22030///
22031/// # Related
22032///
22033/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22034pub fn runtime_generated_surface_composition_section_enabled(section: &Value) -> bool {
22035 section
22036 .get("enabled")
22037 .and_then(Value::as_bool)
22038 .unwrap_or(true)
22039}
22040
22041/// Finds a named form in a generated surface document.
22042///
22043/// # Parameters
22044///
22045/// - `surface`: Input consumed by [`crate::runtime::json_commands::runtime_surface_find_form`].
22046/// - `form_id`: Input consumed by [`crate::runtime::json_commands::runtime_surface_find_form`].
22047///
22048/// # Returns
22049///
22050/// Returns [`Some`] when [`crate::runtime::json_commands::runtime_surface_find_form`] can produce a value and [`None`] otherwise.
22051///
22052/// # Performance
22053///
22054/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::runtime_surface_find_form`].
22055///
22056/// # Examples
22057///
22058/// ```rust,ignore
22059/// let _result = pharos_native::runtime::json_commands::runtime_surface_find_form(todo!(), "form");
22060/// ```
22061///
22062/// # Related
22063///
22064/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22065pub fn runtime_surface_find_form<'a>(surface: &'a Value, form_id: &str) -> Option<&'a Value> {
22066 surface
22067 .get("forms")?
22068 .as_array()?
22069 .iter()
22070 .find(|form| form.get("id").and_then(Value::as_str) == Some(form_id))
22071}
22072
22073/// Appends bounded text to a runtime buffer.
22074///
22075/// # Parameters
22076///
22077/// - `output`: Input consumed by [`crate::runtime::json_commands::runtime_text_buffer_append`].
22078/// - `text`: Input consumed by [`crate::runtime::json_commands::runtime_text_buffer_append`].
22079///
22080/// # Returns
22081///
22082/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_text_buffer_append`] holds and `false` otherwise.
22083///
22084/// # Examples
22085///
22086/// ```rust,ignore
22087/// let _result = pharos_native::runtime::json_commands::runtime_text_buffer_append(todo!(), "{}");
22088/// ```
22089///
22090/// # Related
22091///
22092/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22093pub fn runtime_text_buffer_append(output: &mut String, text: &str) -> bool {
22094 text_buffer_append_format(output, text)
22095}
22096
22097/// Clears a runtime text buffer.
22098///
22099/// # Parameters
22100///
22101/// - `output`: Input consumed by [`crate::runtime::json_commands::runtime_text_buffer_free`].
22102///
22103/// # Returns
22104///
22105/// Returns after [`crate::runtime::json_commands::runtime_text_buffer_free`] applies its in-place effect.
22106///
22107/// # Examples
22108///
22109/// ```rust,ignore
22110/// let _result = pharos_native::runtime::json_commands::runtime_text_buffer_free(todo!());
22111/// ```
22112///
22113/// # Related
22114///
22115/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22116pub fn runtime_text_buffer_free(output: &mut String) {
22117 output.clear();
22118}
22119
22120/// Renders a service media URL using the canonical media route.
22121///
22122/// # Parameters
22123///
22124/// - `business_slug`: Input consumed by [`crate::runtime::json_commands::dynamic_service_media_url`].
22125/// - `media_url`: Input consumed by [`crate::runtime::json_commands::dynamic_service_media_url`].
22126///
22127/// # Returns
22128///
22129/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_service_media_url`] can produce a value and [`None`] otherwise.
22130///
22131/// # Examples
22132///
22133/// ```rust,ignore
22134/// let _result = pharos_native::runtime::json_commands::dynamic_service_media_url("demo", todo!());
22135/// ```
22136///
22137/// # Related
22138///
22139/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22140pub fn dynamic_service_media_url(business_slug: &str, media_url: &str) -> Option<String> {
22141 super::dynamic::media_disk_path(Path::new("."), business_slug, media_url).or_else(|| {
22142 (!business_slug.is_empty() && !media_url.is_empty()).then(|| {
22143 format!(
22144 "/media/businesses/{business_slug}/{}",
22145 media_url.trim_start_matches('/')
22146 )
22147 })
22148 })
22149}
22150
22151/// Renders a generated admin fragment from an app-owned document.
22152///
22153/// # Parameters
22154///
22155/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_generated_admin_fragment_from_doc`].
22156/// - `relative_path`: Input consumed by [`crate::runtime::json_commands::render_generated_admin_fragment_from_doc`].
22157/// - `params`: Input consumed by [`crate::runtime::json_commands::render_generated_admin_fragment_from_doc`].
22158///
22159/// # Returns
22160///
22161/// Returns [`Some`] when [`crate::runtime::json_commands::render_generated_admin_fragment_from_doc`] can produce a value and [`None`] otherwise.
22162///
22163/// # Examples
22164///
22165/// ```rust,ignore
22166/// let _result = pharos_native::runtime::json_commands::render_generated_admin_fragment_from_doc(std::path::Path::new("apps/dev_docs"), "page", todo!());
22167/// ```
22168///
22169/// # Related
22170///
22171/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22172pub fn render_generated_admin_fragment_from_doc(
22173 app_root: &Path,
22174 relative_path: &str,
22175 params: &Value,
22176) -> Option<String> {
22177 render_shared_asset_fragment_from_doc(app_root, relative_path, params)
22178}
22179
22180/// Tests whether an array contains a numeric value.
22181///
22182/// # Parameters
22183///
22184/// - `array`: Input consumed by [`crate::runtime::json_commands::json_array_contains_long_runtime_local`].
22185/// - `wanted`: Input consumed by [`crate::runtime::json_commands::json_array_contains_long_runtime_local`].
22186///
22187/// # Returns
22188///
22189/// Returns `true` when the condition checked by [`crate::runtime::json_commands::json_array_contains_long_runtime_local`] holds and `false` otherwise.
22190///
22191/// # Performance
22192///
22193/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_array_contains_long_runtime_local`].
22194///
22195/// # Examples
22196///
22197/// ```rust,ignore
22198/// let _result = pharos_native::runtime::json_commands::json_array_contains_long_runtime_local(todo!(), todo!());
22199/// ```
22200///
22201/// # Related
22202///
22203/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22204pub fn json_array_contains_long_runtime_local(array: &Value, wanted: i64) -> bool {
22205 array.as_array().is_some_and(|items| {
22206 items
22207 .iter()
22208 .any(|item| item.as_i64().or_else(|| item.as_str()?.parse().ok()) == Some(wanted))
22209 })
22210}
22211
22212/// Builds login page parameters from a validated error and next path.
22213///
22214/// # Parameters
22215///
22216/// - `root`: Input consumed by [`crate::runtime::json_commands::login_page_params_json`].
22217/// - `error_text`: Input consumed by [`crate::runtime::json_commands::login_page_params_json`].
22218/// - `next_path`: Input consumed by [`crate::runtime::json_commands::login_page_params_json`].
22219///
22220/// # Returns
22221///
22222/// Returns [`Some`] when [`crate::runtime::json_commands::login_page_params_json`] can produce a value and [`None`] otherwise.
22223///
22224/// # Examples
22225///
22226/// ```rust,ignore
22227/// let _result = pharos_native::runtime::json_commands::login_page_params_json(std::path::Path::new("."), todo!(), todo!());
22228/// ```
22229///
22230/// # Related
22231///
22232/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22233pub fn login_page_params_json(root: &Value, error_text: &str, next_path: &str) -> Option<String> {
22234 let mut extra = Map::new();
22235 extra.insert("error".into(), Value::String(error_text.into()));
22236 extra.insert("next".into(), Value::String(next_path.into()));
22237 page_params_value(root, "login", extra)
22238}
22239
22240/// Builds password-reset completion page parameters.
22241///
22242/// # Parameters
22243///
22244/// - `root`: Input consumed by [`crate::runtime::json_commands::password_reset_complete_page_params_json`].
22245/// - `error_text`: Input consumed by [`crate::runtime::json_commands::password_reset_complete_page_params_json`].
22246/// - `token`: Input consumed by [`crate::runtime::json_commands::password_reset_complete_page_params_json`].
22247///
22248/// # Returns
22249///
22250/// Returns [`Some`] when [`crate::runtime::json_commands::password_reset_complete_page_params_json`] can produce a value and [`None`] otherwise.
22251///
22252/// # Examples
22253///
22254/// ```rust,ignore
22255/// let _result = pharos_native::runtime::json_commands::password_reset_complete_page_params_json(std::path::Path::new("."), todo!(), todo!());
22256/// ```
22257///
22258/// # Related
22259///
22260/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22261pub fn password_reset_complete_page_params_json(
22262 root: &Value,
22263 error_text: &str,
22264 token: &str,
22265) -> Option<String> {
22266 let mut extra = Map::new();
22267 extra.insert("error".into(), Value::String(error_text.into()));
22268 extra.insert("token".into(), Value::String(token.into()));
22269 page_params_value(root, "password-reset-complete", extra)
22270}
22271
22272/// Builds password-reset request page parameters.
22273///
22274/// # Parameters
22275///
22276/// - `root`: Input consumed by [`crate::runtime::json_commands::password_reset_request_page_params_json`].
22277/// - `error_text`: Input consumed by [`crate::runtime::json_commands::password_reset_request_page_params_json`].
22278///
22279/// # Returns
22280///
22281/// Returns [`Some`] when [`crate::runtime::json_commands::password_reset_request_page_params_json`] can produce a value and [`None`] otherwise.
22282///
22283/// # Examples
22284///
22285/// ```rust,ignore
22286/// let _result = pharos_native::runtime::json_commands::password_reset_request_page_params_json(std::path::Path::new("."), todo!());
22287/// ```
22288///
22289/// # Related
22290///
22291/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22292pub fn password_reset_request_page_params_json(root: &Value, error_text: &str) -> Option<String> {
22293 let mut extra = Map::new();
22294 extra.insert("error".into(), Value::String(error_text.into()));
22295 page_params_value(root, "password-reset-request", extra)
22296}
22297
22298/// Builds registration page parameters.
22299///
22300/// # Parameters
22301///
22302/// - `root`: Input consumed by [`crate::runtime::json_commands::register_page_params_json`].
22303/// - `error_text`: Input consumed by [`crate::runtime::json_commands::register_page_params_json`].
22304///
22305/// # Returns
22306///
22307/// Returns [`Some`] when [`crate::runtime::json_commands::register_page_params_json`] can produce a value and [`None`] otherwise.
22308///
22309/// # Examples
22310///
22311/// ```rust,ignore
22312/// let _result = pharos_native::runtime::json_commands::register_page_params_json(std::path::Path::new("."), todo!());
22313/// ```
22314///
22315/// # Related
22316///
22317/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22318pub fn register_page_params_json(root: &Value, error_text: &str) -> Option<String> {
22319 register_page_params_with_error_json(root, error_text)
22320}
22321
22322/// Returns at most `limit` UTF-8 bytes from a string without splitting characters.
22323///
22324/// # Parameters
22325///
22326/// - `value`: Input consumed by [`crate::runtime::json_commands::strndup`].
22327/// - `limit`: Input consumed by [`crate::runtime::json_commands::strndup`].
22328///
22329/// # Returns
22330///
22331/// Returns the computed value produced by [`crate::runtime::json_commands::strndup`].
22332///
22333/// # Performance
22334///
22335/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::strndup`].
22336///
22337/// # Examples
22338///
22339/// ```rust,ignore
22340/// let _result = pharos_native::runtime::json_commands::strndup("value", todo!());
22341/// ```
22342///
22343/// # Related
22344///
22345/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22346pub fn strndup(value: &str, limit: usize) -> String {
22347 value.chars().take(limit).collect()
22348}
22349
22350/// Copies a bounded string as an owned Rust value.
22351///
22352/// # Parameters
22353///
22354/// - `value`: Input consumed by [`crate::runtime::json_commands::strdup`].
22355///
22356/// # Returns
22357///
22358/// Returns the computed value produced by [`crate::runtime::json_commands::strdup`].
22359///
22360/// # Examples
22361///
22362/// ```rust,ignore
22363/// let _result = pharos_native::runtime::json_commands::strdup("value");
22364/// ```
22365///
22366/// # Related
22367///
22368/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22369pub fn strdup(value: &str) -> String {
22370 strndup(value, MAX_TEXT_BYTES)
22371}
22372
22373/// Splits a bounded string into delimiter-separated owned fields.
22374///
22375/// # Parameters
22376///
22377/// - `value`: Input consumed by [`crate::runtime::json_commands::strtok_r`].
22378/// - `delimiter`: Input consumed by [`crate::runtime::json_commands::strtok_r`].
22379///
22380/// # Returns
22381///
22382/// Returns the computed value produced by [`crate::runtime::json_commands::strtok_r`].
22383///
22384/// # Performance
22385///
22386/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::strtok_r`].
22387///
22388/// # Examples
22389///
22390/// ```rust,ignore
22391/// let _result = pharos_native::runtime::json_commands::strtok_r("value", todo!());
22392/// ```
22393///
22394/// # Related
22395///
22396/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22397pub fn strtok_r(value: &str, delimiter: &str) -> Vec<String> {
22398 if delimiter.is_empty() {
22399 return vec![value.to_owned()];
22400 }
22401 value
22402 .split(delimiter)
22403 .filter(|part| !part.is_empty())
22404 .map(ToOwned::to_owned)
22405 .take(4096)
22406 .collect()
22407}
22408
22409/// Serializes one JSON string using the canonical escaping policy.
22410///
22411/// # Parameters
22412///
22413/// - `value`: Input consumed by [`crate::runtime::json_commands::encode_json_string_literal`].
22414///
22415/// # Returns
22416///
22417/// Returns [`Some`] when [`crate::runtime::json_commands::encode_json_string_literal`] can produce a value and [`None`] otherwise.
22418///
22419/// # Examples
22420///
22421/// ```rust,ignore
22422/// let _result = pharos_native::runtime::json_commands::encode_json_string_literal("value");
22423/// ```
22424///
22425/// # Related
22426///
22427/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22428pub fn encode_json_string_literal(value: &str) -> Option<String> {
22429 serde_json::to_string(value).ok()
22430}
22431
22432/// Converts bytes to lowercase hexadecimal text.
22433///
22434/// # Parameters
22435///
22436/// - `bytes`: Input consumed by [`crate::runtime::json_commands::bytes_to_hex_logic_local`].
22437///
22438/// # Returns
22439///
22440/// Returns the computed value produced by [`crate::runtime::json_commands::bytes_to_hex_logic_local`].
22441///
22442/// # Performance
22443///
22444/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::bytes_to_hex_logic_local`].
22445///
22446/// # Examples
22447///
22448/// ```rust,ignore
22449/// let _result = pharos_native::runtime::json_commands::bytes_to_hex_logic_local(b"");
22450/// ```
22451///
22452/// # Related
22453///
22454/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22455pub fn bytes_to_hex_logic_local(bytes: &[u8]) -> String {
22456 pharos_runtime_core::support::hex_digest(bytes)
22457}
22458
22459/// Computes HMAC-SHA256 for a bounded message using the shared runtime primitive.
22460///
22461/// # Parameters
22462///
22463/// - `key`: Input consumed by [`crate::runtime::json_commands::hmac_sha256_logic_local`].
22464/// - `data`: Input consumed by [`crate::runtime::json_commands::hmac_sha256_logic_local`].
22465///
22466/// # Returns
22467///
22468/// Returns the computed value produced by [`crate::runtime::json_commands::hmac_sha256_logic_local`].
22469///
22470/// # Examples
22471///
22472/// ```rust,ignore
22473/// let _result = pharos_native::runtime::json_commands::hmac_sha256_logic_local("key", b"");
22474/// ```
22475///
22476/// # Related
22477///
22478/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22479pub fn hmac_sha256_logic_local(key: &[u8], data: &[u8]) -> [u8; 32] {
22480 super::dynamic::hmac_sha256(key, data)
22481}
22482
22483/// Finds one multipart boundary parameter from a Content-Type header.
22484///
22485/// # Parameters
22486///
22487/// - `content_type`: Input consumed by [`crate::runtime::json_commands::dynamic_multipart_boundary`].
22488///
22489/// # Returns
22490///
22491/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_multipart_boundary`] can produce a value and [`None`] otherwise.
22492///
22493/// # Performance
22494///
22495/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_multipart_boundary`].
22496///
22497/// # Examples
22498///
22499/// ```rust,ignore
22500/// let _result = pharos_native::runtime::json_commands::dynamic_multipart_boundary(todo!());
22501/// ```
22502///
22503/// # Related
22504///
22505/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22506pub fn dynamic_multipart_boundary(content_type: &str) -> Option<String> {
22507 let parameter = content_type.split(';').find_map(|part| {
22508 let (name, value) = part.trim().split_once('=')?;
22509 name.eq_ignore_ascii_case("boundary")
22510 .then_some(value.trim().trim_matches('"').to_owned())
22511 })?;
22512 (!parameter.is_empty() && parameter.len() <= 512 && !parameter.contains(['\r', '\n']))
22513 .then_some(parameter)
22514}
22515
22516/// Finds a byte sequence in a bounded byte slice.
22517///
22518/// # Parameters
22519///
22520/// - `haystack`: Input consumed by [`crate::runtime::json_commands::multipart_memmem`].
22521/// - `needle`: Input consumed by [`crate::runtime::json_commands::multipart_memmem`].
22522///
22523/// # Returns
22524///
22525/// Returns [`Some`] when [`crate::runtime::json_commands::multipart_memmem`] can produce a value and [`None`] otherwise.
22526///
22527/// # Examples
22528///
22529/// ```rust,ignore
22530/// let _result = pharos_native::runtime::json_commands::multipart_memmem(todo!(), todo!());
22531/// ```
22532///
22533/// # Related
22534///
22535/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22536pub fn multipart_memmem(haystack: &[u8], needle: &[u8]) -> Option<usize> {
22537 if needle.is_empty() {
22538 return Some(0);
22539 }
22540 haystack
22541 .windows(needle.len())
22542 .position(|window| window == needle)
22543}
22544
22545/// Copies one checked byte range.
22546///
22547/// # Parameters
22548///
22549/// - `body`: Input consumed by [`crate::runtime::json_commands::multipart_dup_range`].
22550/// - `start`: Input consumed by [`crate::runtime::json_commands::multipart_dup_range`].
22551/// - `length`: Input consumed by [`crate::runtime::json_commands::multipart_dup_range`].
22552///
22553/// # Returns
22554///
22555/// Returns [`Some`] when [`crate::runtime::json_commands::multipart_dup_range`] can produce a value and [`None`] otherwise.
22556///
22557/// # Examples
22558///
22559/// ```rust,ignore
22560/// let _result = pharos_native::runtime::json_commands::multipart_dup_range(b"", todo!(), todo!());
22561/// ```
22562///
22563/// # Related
22564///
22565/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22566pub fn multipart_dup_range(body: &[u8], start: usize, length: usize) -> Option<Vec<u8>> {
22567 let end = start.checked_add(length)?;
22568 (end <= body.len() && length <= super::http::PHAROS_HTTP_MAX_MULTIPART_BODY_BYTES)
22569 .then(|| body[start..end].to_vec())
22570}
22571
22572/// Finds a multipart field and returns filename plus content bytes.
22573///
22574/// # Parameters
22575///
22576/// - `body`: Raw multipart request body bytes.
22577/// - `content_type`: Request content type containing the multipart boundary.
22578/// - `field_name`: Multipart form field to extract.
22579///
22580/// # Returns
22581///
22582/// Returns [`Some`] when [`crate::runtime::json_commands::dynamic_multipart_find_part`] can produce a value and [`None`] otherwise.
22583///
22584/// # Performance
22585///
22586/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::dynamic_multipart_find_part`].
22587///
22588/// # Examples
22589///
22590/// ```rust,ignore
22591/// let _result = pharos_native::runtime::json_commands::dynamic_multipart_find_part(b"", todo!(), todo!(), todo!(), todo!());
22592/// ```
22593///
22594/// # Related
22595///
22596/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22597pub fn dynamic_multipart_find_part(
22598 body: &[u8],
22599 content_type: &str,
22600 field_name: &str,
22601) -> Option<(Option<String>, Vec<u8>)> {
22602 let boundary = dynamic_multipart_boundary(content_type)?;
22603 let marker = format!("--{boundary}").into_bytes();
22604 let mut cursor = multipart_memmem(body, &marker)?;
22605 while cursor < body.len() {
22606 let header_start = cursor.checked_add(marker.len() + 2)?;
22607 if header_start > body.len() {
22608 return None;
22609 }
22610 let header_end =
22611 multipart_memmem(&body[header_start..], b"\r\n\r\n")?.checked_add(header_start)?;
22612 let headers = std::str::from_utf8(&body[header_start..header_end]).ok()?;
22613 let disposition = headers.lines().find(|line| {
22614 line.to_ascii_lowercase()
22615 .starts_with("content-disposition:")
22616 })?;
22617 let name = disposition.split(';').find_map(|part| {
22618 let (key, value) = part.trim().split_once('=')?;
22619 key.eq_ignore_ascii_case("name")
22620 .then_some(value.trim().trim_matches('"'))
22621 })?;
22622 let filename = disposition.split(';').find_map(|part| {
22623 let (key, value) = part.trim().split_once('=')?;
22624 key.eq_ignore_ascii_case("filename")
22625 .then_some(value.trim().trim_matches('"').to_owned())
22626 });
22627 let content_start = header_end + 4;
22628 let next_marker = multipart_memmem(&body[content_start..], &marker)
22629 .map(|offset| offset + content_start)?;
22630 let content_end = next_marker.saturating_sub(2);
22631 if name == field_name {
22632 return Some((filename, body[content_start..content_end].to_vec()));
22633 }
22634 cursor = next_marker;
22635 }
22636 None
22637}
22638
22639/// Extracts one multipart text field from an in-memory request body as UTF-8.
22640///
22641/// # Parameters
22642///
22643/// - `body`: Input consumed by [`crate::runtime::json_commands::multipart_extract_text_from_bytes`].
22644/// - `content_type`: Input consumed by [`crate::runtime::json_commands::multipart_extract_text_from_bytes`].
22645/// - `field_name`: Input consumed by [`crate::runtime::json_commands::multipart_extract_text_from_bytes`].
22646///
22647/// # Returns
22648///
22649/// Returns [`Some`] when [`crate::runtime::json_commands::multipart_extract_text_from_bytes`] can produce a value and [`None`] otherwise.
22650///
22651/// # Examples
22652///
22653/// ```rust,ignore
22654/// let _result = pharos_native::runtime::json_commands::multipart_extract_text_from_bytes(b"", todo!(), todo!());
22655/// ```
22656///
22657/// # Related
22658///
22659/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22660pub fn multipart_extract_text_from_bytes(
22661 body: &[u8],
22662 content_type: &str,
22663 field_name: &str,
22664) -> Option<String> {
22665 let (_, bytes) = dynamic_multipart_find_part(body, content_type, field_name)?;
22666 String::from_utf8(bytes).ok()
22667}
22668
22669/// Returns the audit-event count for a state JSON document.
22670///
22671/// # Parameters
22672///
22673/// - `root`: Input consumed by [`crate::runtime::json_commands::audit_event_count`].
22674///
22675/// # Returns
22676///
22677/// Returns the computed value produced by [`crate::runtime::json_commands::audit_event_count`].
22678///
22679/// # Examples
22680///
22681/// ```rust,ignore
22682/// let _result = pharos_native::runtime::json_commands::audit_event_count(std::path::Path::new("."));
22683/// ```
22684///
22685/// # Related
22686///
22687/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22688pub fn audit_event_count(root: &Value) -> String {
22689 bounded_state_array(root, "audit_events").len().to_string()
22690}
22691
22692/// Exports relational state through the selected backend contract.
22693///
22694/// # Parameters
22695///
22696/// - `app_root`: Input consumed by [`crate::runtime::json_commands::export_runtime_state_json`].
22697/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::export_runtime_state_json`].
22698/// - `app_id`: Input consumed by [`crate::runtime::json_commands::export_runtime_state_json`].
22699///
22700/// # Returns
22701///
22702/// Returns [`Some`] when [`crate::runtime::json_commands::export_runtime_state_json`] can produce a value and [`None`] otherwise.
22703///
22704/// # Examples
22705///
22706/// ```rust,ignore
22707/// let _result = pharos_native::runtime::json_commands::export_runtime_state_json(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
22708/// ```
22709///
22710/// # Related
22711///
22712/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22713pub fn export_runtime_state_json(
22714 app_root: &Path,
22715 runtime_conf_path: Option<&Path>,
22716 app_id: Option<&str>,
22717) -> Option<String> {
22718 dynamic_runtime_state_export_json_runtime(app_root, runtime_conf_path, app_id)
22719}
22720
22721/// Imports a validated relational state file through the selected backend contract.
22722///
22723/// # Parameters
22724///
22725/// - `app_root`: Input consumed by [`crate::runtime::json_commands::import_runtime_state_file`].
22726/// - `runtime_conf_path`: Input consumed by [`crate::runtime::json_commands::import_runtime_state_file`].
22727/// - `app_id`: Input consumed by [`crate::runtime::json_commands::import_runtime_state_file`].
22728/// - `state_path`: Input consumed by [`crate::runtime::json_commands::import_runtime_state_file`].
22729/// - `expected_audit_count`: Input consumed by [`crate::runtime::json_commands::import_runtime_state_file`].
22730///
22731/// # Returns
22732///
22733/// Returns the computed value produced by [`crate::runtime::json_commands::import_runtime_state_file`].
22734///
22735/// # Examples
22736///
22737/// ```rust,ignore
22738/// let _result = pharos_native::runtime::json_commands::import_runtime_state_file(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
22739/// ```
22740///
22741/// # Related
22742///
22743/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22744pub fn import_runtime_state_file(
22745 app_root: &Path,
22746 runtime_conf_path: Option<&Path>,
22747 app_id: Option<&str>,
22748 state_path: &Path,
22749 expected_audit_count: Option<&str>,
22750) -> i32 {
22751 dynamic_runtime_state_import_json_runtime(
22752 app_root,
22753 runtime_conf_path,
22754 app_id,
22755 state_path,
22756 expected_audit_count,
22757 )
22758}
22759
22760/// Returns whether the relational backend is authoritative for this app.
22761///
22762/// # Parameters
22763///
22764/// - `app_root`: Input consumed by [`crate::runtime::json_commands::relational_runtime_authority_enabled_runtime_local`].
22765///
22766/// # Returns
22767///
22768/// Returns `true` when the condition checked by [`crate::runtime::json_commands::relational_runtime_authority_enabled_runtime_local`] holds and `false` otherwise.
22769///
22770/// # Examples
22771///
22772/// ```rust,ignore
22773/// let _result = pharos_native::runtime::json_commands::relational_runtime_authority_enabled_runtime_local(std::path::Path::new("apps/dev_docs"));
22774/// ```
22775///
22776/// # Related
22777///
22778/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22779pub fn relational_runtime_authority_enabled_runtime_local(app_root: &Path) -> bool {
22780 relational_runtime_authority_enabled(app_root)
22781}
22782
22783/// Returns a migration contract document as compact JSON.
22784///
22785/// # Parameters
22786///
22787/// - `path`: Input consumed by [`crate::runtime::json_commands::migration_contract_text_dup_runtime_local`].
22788///
22789/// # Returns
22790///
22791/// Returns [`Some`] when [`crate::runtime::json_commands::migration_contract_text_dup_runtime_local`] can produce a value and [`None`] otherwise.
22792///
22793/// # Examples
22794///
22795/// ```rust,ignore
22796/// let _result = pharos_native::runtime::json_commands::migration_contract_text_dup_runtime_local(std::path::Path::new("fixture.json"));
22797/// ```
22798///
22799/// # Related
22800///
22801/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22802pub fn migration_contract_text_dup_runtime_local(path: &Path) -> Option<String> {
22803 compact(&read_json_file(path)?)
22804}
22805
22806/// Returns a stable file signature based on metadata and content hash.
22807///
22808/// # Parameters
22809///
22810/// - `path`: Input consumed by [`crate::runtime::json_commands::native_file_signature`].
22811///
22812/// # Returns
22813///
22814/// Returns [`Some`] when [`crate::runtime::json_commands::native_file_signature`] can produce a value and [`None`] otherwise.
22815///
22816/// # Examples
22817///
22818/// ```rust,ignore
22819/// let _result = pharos_native::runtime::json_commands::native_file_signature(std::path::Path::new("fixture.json"));
22820/// ```
22821///
22822/// # Related
22823///
22824/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22825pub fn native_file_signature(path: &Path) -> Option<String> {
22826 let bytes = fs::read(path).ok()?;
22827 Some(bytes_to_hex_logic_local(&Sha256::digest(&bytes)))
22828}
22829
22830/// Loads a JSON document from disk with size and object-depth guards.
22831///
22832/// # Parameters
22833///
22834/// - `path`: Input consumed by [`crate::runtime::json_commands::json_doc_load_file`].
22835///
22836/// # Returns
22837///
22838/// Returns [`Some`] when [`crate::runtime::json_commands::json_doc_load_file`] can produce a value and [`None`] otherwise.
22839///
22840/// # Performance
22841///
22842/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_doc_load_file`].
22843///
22844/// # Examples
22845///
22846/// ```rust,ignore
22847/// let _result = pharos_native::runtime::json_commands::json_doc_load_file(std::path::Path::new("fixture.json"));
22848/// ```
22849///
22850/// # Related
22851///
22852/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22853pub fn json_doc_load_file(path: &Path) -> Option<Value> {
22854 let metadata = fs::metadata(path).ok()?;
22855 if !metadata.is_file() || metadata.len() > MAX_TEXT_BYTES as u64 {
22856 return None;
22857 }
22858 serde_json::from_str(&fs::read_to_string(path).ok()?).ok()
22859}
22860
22861/// Reads bounded file bytes for direct runtime consumers.
22862///
22863/// # Parameters
22864///
22865/// - `path`: Input consumed by [`crate::runtime::json_commands::read_file_bytes`].
22866///
22867/// # Returns
22868///
22869/// Returns [`Some`] when [`crate::runtime::json_commands::read_file_bytes`] can produce a value and [`None`] otherwise.
22870///
22871/// # Examples
22872///
22873/// ```rust,ignore
22874/// let _result = pharos_native::runtime::json_commands::read_file_bytes(std::path::Path::new("fixture.json"));
22875/// ```
22876///
22877/// # Related
22878///
22879/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22880pub fn read_file_bytes(path: &Path) -> Option<Vec<u8>> {
22881 let bytes = fs::read(path).ok()?;
22882 (bytes.len() <= MAX_TEXT_BYTES).then_some(bytes)
22883}
22884
22885/// Writes bounded binary data to an app-owned file.
22886///
22887/// # Parameters
22888///
22889/// - `path`: Input consumed by [`crate::runtime::json_commands::write_binary_file`].
22890/// - `bytes`: Input consumed by [`crate::runtime::json_commands::write_binary_file`].
22891///
22892/// # Returns
22893///
22894/// Returns the computed value produced by [`crate::runtime::json_commands::write_binary_file`].
22895///
22896/// # Examples
22897///
22898/// ```rust,ignore
22899/// let _result = pharos_native::runtime::json_commands::write_binary_file(std::path::Path::new("fixture.json"), b"");
22900/// ```
22901///
22902/// # Related
22903///
22904/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22905pub fn write_binary_file(path: &Path, bytes: &[u8]) -> i32 {
22906 if bytes.len() > MAX_TEXT_BYTES {
22907 return 64;
22908 }
22909 super::fs::write_binary_file(path, bytes, 0o640)
22910 .map(|_| 0)
22911 .unwrap_or(69)
22912}
22913
22914/// Applies a base path to root-relative HTML URLs without touching external URLs.
22915///
22916/// # Parameters
22917///
22918/// - `html`: Input consumed by [`crate::runtime::json_commands::apply_base_path_html`].
22919/// - `base_path`: Input consumed by [`crate::runtime::json_commands::apply_base_path_html`].
22920///
22921/// # Returns
22922///
22923/// Returns [`Some`] when [`crate::runtime::json_commands::apply_base_path_html`] can produce a value and [`None`] otherwise.
22924///
22925/// # Examples
22926///
22927/// ```rust,ignore
22928/// let _result = pharos_native::runtime::json_commands::apply_base_path_html(todo!(), "/app");
22929/// ```
22930///
22931/// # Related
22932///
22933/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22934pub fn apply_base_path_html(html: &str, base_path: Option<&str>) -> Option<String> {
22935 template_apply_base_path(html, base_path)
22936}
22937
22938/// Builds layout parameters from a bounded JSON document.
22939///
22940/// # Parameters
22941///
22942/// - `app_root`: Input consumed by [`crate::runtime::json_commands::build_layout_page_params`].
22943/// - `params`: Input consumed by [`crate::runtime::json_commands::build_layout_page_params`].
22944///
22945/// # Returns
22946///
22947/// Returns [`Some`] when [`crate::runtime::json_commands::build_layout_page_params`] can produce a value and [`None`] otherwise.
22948///
22949/// # Examples
22950///
22951/// ```rust,ignore
22952/// let _result = pharos_native::runtime::json_commands::build_layout_page_params(std::path::Path::new("apps/dev_docs"), todo!());
22953/// ```
22954///
22955/// # Related
22956///
22957/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22958pub fn build_layout_page_params(app_root: &Path, params: &Value) -> Option<String> {
22959 let _ = app_root;
22960 compact(params)
22961}
22962
22963/// Finds a declared page file below an app directory.
22964///
22965/// # Parameters
22966///
22967/// - `app_root`: Input consumed by [`crate::runtime::json_commands::runtime_find_declared_page_relative_path_in_directory`].
22968/// - `page_id`: Input consumed by [`crate::runtime::json_commands::runtime_find_declared_page_relative_path_in_directory`].
22969///
22970/// # Returns
22971///
22972/// Returns [`Some`] when [`crate::runtime::json_commands::runtime_find_declared_page_relative_path_in_directory`] can produce a value and [`None`] otherwise.
22973///
22974/// # Examples
22975///
22976/// ```rust,ignore
22977/// let _result = pharos_native::runtime::json_commands::runtime_find_declared_page_relative_path_in_directory(std::path::Path::new("apps/dev_docs"), "home");
22978/// ```
22979///
22980/// # Related
22981///
22982/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
22983pub fn runtime_find_declared_page_relative_path_in_directory(
22984 app_root: &Path,
22985 page_id: &str,
22986) -> Option<String> {
22987 declared_page_relative_path(app_root, page_id)
22988}
22989
22990/// Matches a declared page filename to a validated page identifier.
22991///
22992/// # Parameters
22993///
22994/// - `path`: Input consumed by [`crate::runtime::json_commands::runtime_declared_page_file_matches_id`].
22995/// - `page_id`: Input consumed by [`crate::runtime::json_commands::runtime_declared_page_file_matches_id`].
22996///
22997/// # Returns
22998///
22999/// Returns `true` when the condition checked by [`crate::runtime::json_commands::runtime_declared_page_file_matches_id`] holds and `false` otherwise.
23000///
23001/// # Examples
23002///
23003/// ```rust,ignore
23004/// let _result = pharos_native::runtime::json_commands::runtime_declared_page_file_matches_id(std::path::Path::new("fixture.json"), "home");
23005/// ```
23006///
23007/// # Related
23008///
23009/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23010pub fn runtime_declared_page_file_matches_id(path: &Path, page_id: &str) -> bool {
23011 path.file_stem()
23012 .and_then(|value| value.to_str())
23013 .is_some_and(|value| value == page_id || value.strip_suffix(".page") == Some(page_id))
23014}
23015
23016/// Appends a bounded formatted fragment to a text buffer.
23017///
23018/// # Parameters
23019///
23020/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_format`].
23021/// - `text`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_format`].
23022///
23023/// # Returns
23024///
23025/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_buffer_append_format`] holds and `false` otherwise.
23026///
23027/// # Examples
23028///
23029/// ```rust,ignore
23030/// let _result = pharos_native::runtime::json_commands::text_buffer_append_format(todo!(), "{}");
23031/// ```
23032///
23033/// # Related
23034///
23035/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23036pub fn text_buffer_append_format(output: &mut String, text: &str) -> bool {
23037 if output.len().saturating_add(text.len()) > MAX_TEXT_BYTES {
23038 return false;
23039 }
23040 output.push_str(text);
23041 true
23042}
23043
23044/// Performs JSON find string recursive.
23045fn json_find_string_recursive(value: &Value, key: &str) -> Option<String> {
23046 match value {
23047 Value::Object(map) => {
23048 if let Some(text) = map.get(key).and_then(Value::as_str) {
23049 return Some(text.to_owned());
23050 }
23051 map.values()
23052 .find_map(|entry| json_find_string_recursive(entry, key))
23053 }
23054 Value::Array(items) => items
23055 .iter()
23056 .find_map(|entry| json_find_string_recursive(entry, key)),
23057 _ => None,
23058 }
23059}
23060
23061/// Finds a string field in a bounded JSON document.
23062///
23063/// # Parameters
23064///
23065/// - `text`: Input consumed by [`crate::runtime::json_commands::json_find_string`].
23066/// - `key`: Input consumed by [`crate::runtime::json_commands::json_find_string`].
23067///
23068/// # Returns
23069///
23070/// Returns [`Some`] when [`crate::runtime::json_commands::json_find_string`] can produce a value and [`None`] otherwise.
23071///
23072/// # Performance
23073///
23074/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_find_string`].
23075///
23076/// # Examples
23077///
23078/// ```rust,ignore
23079/// let _result = pharos_native::runtime::json_commands::json_find_string("{}", "key");
23080/// ```
23081///
23082/// # Related
23083///
23084/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23085pub fn json_find_string(text: &str, key: &str) -> Option<String> {
23086 if key.is_empty() || text.len() > MAX_TEXT_BYTES {
23087 return None;
23088 }
23089 json_find_string_recursive(&serde_json::from_str(text).ok()?, key)
23090}
23091
23092/// Finds a string field after a caller-selected text offset.
23093///
23094/// # Parameters
23095///
23096/// - `text`: Input consumed by [`crate::runtime::json_commands::json_find_string_after`].
23097/// - `key`: Input consumed by [`crate::runtime::json_commands::json_find_string_after`].
23098/// - `start_at`: Input consumed by [`crate::runtime::json_commands::json_find_string_after`].
23099///
23100/// # Returns
23101///
23102/// Returns [`Some`] when [`crate::runtime::json_commands::json_find_string_after`] can produce a value and [`None`] otherwise.
23103///
23104/// # Performance
23105///
23106/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_find_string_after`].
23107///
23108/// # Examples
23109///
23110/// ```rust,ignore
23111/// let _result = pharos_native::runtime::json_commands::json_find_string_after("{}", "key", todo!());
23112/// ```
23113///
23114/// # Related
23115///
23116/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23117pub fn json_find_string_after(text: &str, key: &str, start_at: &str) -> Option<String> {
23118 let offset = text.find(start_at)?;
23119 json_find_string(&text[offset..], key)
23120}
23121
23122/// Finds a string field inside a named JSON section.
23123///
23124/// # Parameters
23125///
23126/// - `text`: Input consumed by [`crate::runtime::json_commands::json_find_string_in_section`].
23127/// - `section`: Input consumed by [`crate::runtime::json_commands::json_find_string_in_section`].
23128/// - `key`: Input consumed by [`crate::runtime::json_commands::json_find_string_in_section`].
23129///
23130/// # Returns
23131///
23132/// Returns [`Some`] when [`crate::runtime::json_commands::json_find_string_in_section`] can produce a value and [`None`] otherwise.
23133///
23134/// # Performance
23135///
23136/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::json_find_string_in_section`].
23137///
23138/// # Examples
23139///
23140/// ```rust,ignore
23141/// let _result = pharos_native::runtime::json_commands::json_find_string_in_section("{}", "items", "key");
23142/// ```
23143///
23144/// # Related
23145///
23146/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23147pub fn json_find_string_in_section(text: &str, section: &str, key: &str) -> Option<String> {
23148 if section.is_empty() || key.is_empty() {
23149 return None;
23150 }
23151 let root: Value = serde_json::from_str(text).ok()?;
23152 let section = root.get(section)?;
23153 json_find_string_recursive(section, key)
23154}
23155
23156/// Looks up a safe key in a line-oriented runtime configuration file.
23157///
23158/// # Parameters
23159///
23160/// - `path`: Input consumed by [`crate::runtime::json_commands::conf_lookup_value`].
23161/// - `key`: Input consumed by [`crate::runtime::json_commands::conf_lookup_value`].
23162///
23163/// # Returns
23164///
23165/// Returns [`Some`] when [`crate::runtime::json_commands::conf_lookup_value`] can produce a value and [`None`] otherwise.
23166///
23167/// # Performance
23168///
23169/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::conf_lookup_value`].
23170///
23171/// # Examples
23172///
23173/// ```rust,ignore
23174/// let _result = pharos_native::runtime::json_commands::conf_lookup_value(std::path::Path::new("fixture.json"), "key");
23175/// ```
23176///
23177/// # Related
23178///
23179/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23180pub fn conf_lookup_value(path: &Path, key: &str) -> Option<String> {
23181 if key.is_empty()
23182 || key
23183 .bytes()
23184 .any(|byte| !byte.is_ascii_alphanumeric() && byte != b'_')
23185 {
23186 return None;
23187 }
23188 let metadata = fs::metadata(path).ok()?;
23189 if metadata.len() > MAX_TEXT_BYTES as u64 {
23190 return None;
23191 }
23192 fs::read_to_string(path).ok()?.lines().find_map(|line| {
23193 let (candidate, raw) = line.split_once('=')?;
23194 (candidate.trim() == key).then(|| raw.trim().trim_matches(['"', '\'']).to_owned())
23195 })
23196}
23197
23198/// Appends text to a bounded template buffer.
23199///
23200/// # Parameters
23201///
23202/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_append`].
23203/// - `text`: Input consumed by [`crate::runtime::json_commands::text_buffer_append`].
23204///
23205/// # Returns
23206///
23207/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_buffer_append`] holds and `false` otherwise.
23208///
23209/// # Examples
23210///
23211/// ```rust,ignore
23212/// let _result = pharos_native::runtime::json_commands::text_buffer_append(todo!(), "{}");
23213/// ```
23214///
23215/// # Related
23216///
23217/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23218pub fn text_buffer_append(output: &mut String, text: &str) -> bool {
23219 text_buffer_append_format(output, text)
23220}
23221
23222/// Appends one character to a bounded template buffer.
23223///
23224/// # Parameters
23225///
23226/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_char`].
23227/// - `value`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_char`].
23228///
23229/// # Returns
23230///
23231/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_buffer_append_char`] holds and `false` otherwise.
23232///
23233/// # Examples
23234///
23235/// ```rust,ignore
23236/// let _result = pharos_native::runtime::json_commands::text_buffer_append_char(todo!(), "value");
23237/// ```
23238///
23239/// # Related
23240///
23241/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23242pub fn text_buffer_append_char(output: &mut String, value: char) -> bool {
23243 let mut encoded = [0u8; 4];
23244 text_buffer_append(output, value.encode_utf8(&mut encoded))
23245}
23246
23247/// Escapes and appends one template value.
23248///
23249/// # Parameters
23250///
23251/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_html_escaped`].
23252/// - `text`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_html_escaped`].
23253///
23254/// # Returns
23255///
23256/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_buffer_append_html_escaped`] holds and `false` otherwise.
23257///
23258/// # Examples
23259///
23260/// ```rust,ignore
23261/// let _result = pharos_native::runtime::json_commands::text_buffer_append_html_escaped(todo!(), "{}");
23262/// ```
23263///
23264/// # Related
23265///
23266/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23267pub fn text_buffer_append_html_escaped(output: &mut String, text: &str) -> bool {
23268 text_buffer_append(output, &html_escape_text(text))
23269}
23270
23271/// Releases a template buffer.
23272///
23273/// # Parameters
23274///
23275/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_free`].
23276///
23277/// # Returns
23278///
23279/// Returns after [`crate::runtime::json_commands::text_buffer_free`] applies its in-place effect.
23280///
23281/// # Examples
23282///
23283/// ```rust,ignore
23284/// let _result = pharos_native::runtime::json_commands::text_buffer_free(todo!());
23285/// ```
23286///
23287/// # Related
23288///
23289/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23290pub fn text_buffer_free(output: &mut String) {
23291 output.clear();
23292 output.shrink_to_fit();
23293}
23294
23295/// Reserves bounded template buffer capacity.
23296///
23297/// # Parameters
23298///
23299/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_reserve`].
23300/// - `needed_length`: Input consumed by [`crate::runtime::json_commands::text_buffer_reserve`].
23301///
23302/// # Returns
23303///
23304/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_buffer_reserve`] holds and `false` otherwise.
23305///
23306/// # Examples
23307///
23308/// ```rust,ignore
23309/// let _result = pharos_native::runtime::json_commands::text_buffer_reserve(todo!(), todo!());
23310/// ```
23311///
23312/// # Related
23313///
23314/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23315pub fn text_buffer_reserve(output: &mut String, needed_length: usize) -> bool {
23316 needed_length <= MAX_TEXT_BYTES
23317 && output
23318 .try_reserve(needed_length.saturating_sub(output.len()))
23319 .is_ok()
23320}
23321
23322/// Takes a template buffer's contents.
23323///
23324/// # Parameters
23325///
23326/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_take`].
23327///
23328/// # Returns
23329///
23330/// Returns [`Some`] when [`crate::runtime::json_commands::text_buffer_take`] can produce a value and [`None`] otherwise.
23331///
23332/// # Examples
23333///
23334/// ```rust,ignore
23335/// let _result = pharos_native::runtime::json_commands::text_buffer_take(todo!());
23336/// ```
23337///
23338/// # Related
23339///
23340/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23341pub fn text_buffer_take(output: &mut String) -> Option<String> {
23342 (!output.is_empty()).then(|| std::mem::take(output))
23343}
23344
23345/// Loads a JSON root from bounded text.
23346///
23347/// # Parameters
23348///
23349/// - `text`: Input consumed by [`crate::runtime::json_commands::load_root_from`].
23350///
23351/// # Returns
23352///
23353/// Returns [`Some`] when [`crate::runtime::json_commands::load_root_from`] can produce a value and [`None`] otherwise.
23354///
23355/// # Performance
23356///
23357/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::load_root_from`].
23358///
23359/// # Examples
23360///
23361/// ```rust,ignore
23362/// let _result = pharos_native::runtime::json_commands::load_root_from("{}");
23363/// ```
23364///
23365/// # Related
23366///
23367/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23368pub fn load_root_from(text: &str) -> Option<Value> {
23369 (text.len() <= MAX_TEXT_BYTES)
23370 .then(|| serde_json::from_str(text).ok())
23371 .flatten()
23372}
23373
23374/// Converts a JSON value to its template text representation.
23375///
23376/// # Parameters
23377///
23378/// - `value`: Input consumed by [`crate::runtime::json_commands::template_value_text`].
23379///
23380/// # Returns
23381///
23382/// Returns the computed value produced by [`crate::runtime::json_commands::template_value_text`].
23383///
23384/// # Examples
23385///
23386/// ```rust,ignore
23387/// let _result = pharos_native::runtime::json_commands::template_value_text("value");
23388/// ```
23389///
23390/// # Related
23391///
23392/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23393pub fn template_value_text(value: &Value) -> String {
23394 match value {
23395 Value::Null => String::new(),
23396 Value::String(text) => text.clone(),
23397 Value::Bool(flag) => flag.to_string(),
23398 Value::Number(number) => number.to_string(),
23399 other => serde_json::to_string(other).unwrap_or_default(),
23400 }
23401}
23402
23403/// Validates whether a template reference uses one approved URL scheme.
23404///
23405/// # Parameters
23406///
23407/// - `text`: Input consumed by [`crate::runtime::json_commands::template_reference_has_allowed_url_scheme`].
23408///
23409/// # Returns
23410///
23411/// Returns `true` when the condition checked by [`crate::runtime::json_commands::template_reference_has_allowed_url_scheme`] holds and `false` otherwise.
23412///
23413/// # Examples
23414///
23415/// ```rust,ignore
23416/// let _result = pharos_native::runtime::json_commands::template_reference_has_allowed_url_scheme("{}");
23417/// ```
23418///
23419/// # Related
23420///
23421/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23422pub fn template_reference_has_allowed_url_scheme(text: &str) -> bool {
23423 template_url_reference_allowed(text)
23424}
23425
23426/// Performs template value usd minor text.
23427fn template_value_usd_minor_text(value: &Value) -> Option<String> {
23428 let minor = match value {
23429 Value::Number(number) => number.as_i64()?,
23430 Value::String(text) => text.parse::<i64>().ok()?,
23431 _ => return None,
23432 };
23433 currency_minor_text(minor, "USD")
23434}
23435
23436/// Formats exact integer minor units for supported currencies without floating-point rounding.
23437pub(super) fn currency_minor_text(amount: i64, currency: &str) -> Option<String> {
23438 let prefix = match currency {
23439 "USD" => "$",
23440 "JPY" => "¥",
23441 "EUR" => "€",
23442 "GBP" => "£",
23443 "CAD" => "CAD ",
23444 "AUD" => "AUD ",
23445 _ => return None,
23446 };
23447 let sign = if amount < 0 { "-" } else { "" };
23448 let absolute = amount.unsigned_abs();
23449 Some(if currency == "JPY" {
23450 format!("{sign}{prefix}{absolute}")
23451 } else {
23452 format!("{sign}{prefix}{}.{:02}", absolute / 100, absolute % 100)
23453 })
23454}
23455
23456/// Classifies a placeholder as text, path, URL, HTML, or formatted USD minor units.
23457///
23458/// # Parameters
23459///
23460/// - `name`: Input consumed by [`crate::runtime::json_commands::template_placeholder_kind`].
23461///
23462/// # Returns
23463///
23464/// Returns the computed value produced by [`crate::runtime::json_commands::template_placeholder_kind`].
23465///
23466/// # Examples
23467///
23468/// ```rust,ignore
23469/// let _result = pharos_native::runtime::json_commands::template_placeholder_kind("name");
23470/// ```
23471///
23472/// # Related
23473///
23474/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23475pub fn template_placeholder_kind(name: &str) -> String {
23476 if name.starts_with("html:") {
23477 "html".to_owned()
23478 } else if name.starts_with("path:") {
23479 "path".to_owned()
23480 } else if name.starts_with("url:") {
23481 "url".to_owned()
23482 } else if name.starts_with("usd_minor:") {
23483 "usd_minor".to_owned()
23484 } else if name.starts_with("currency_minor:") {
23485 "currency_minor".to_owned()
23486 } else {
23487 "text".to_owned()
23488 }
23489}
23490
23491/// Appends a JSON value after applying the placeholder kind security policy.
23492///
23493/// # Parameters
23494///
23495/// - `output`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_template_value`].
23496/// - `value`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_template_value`].
23497/// - `kind`: Input consumed by [`crate::runtime::json_commands::text_buffer_append_template_value`].
23498///
23499/// # Returns
23500///
23501/// Returns `true` when the condition checked by [`crate::runtime::json_commands::text_buffer_append_template_value`] holds and `false` otherwise.
23502///
23503/// # Examples
23504///
23505/// ```rust,ignore
23506/// let _result = pharos_native::runtime::json_commands::text_buffer_append_template_value(todo!(), "value", todo!());
23507/// ```
23508///
23509/// # Related
23510///
23511/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23512pub fn text_buffer_append_template_value(output: &mut String, value: &Value, kind: &str) -> bool {
23513 match kind {
23514 "html" => text_buffer_append(output, &template_value_text(value)),
23515 "path" if template_path_reference_allowed(&template_value_text(value)) => {
23516 text_buffer_append_html_escaped(output, &template_value_text(value))
23517 }
23518 "url" if template_url_reference_allowed(&template_value_text(value)) => {
23519 text_buffer_append_html_escaped(output, &template_value_text(value))
23520 }
23521 "usd_minor" => template_value_usd_minor_text(value)
23522 .is_some_and(|text| text_buffer_append_html_escaped(output, &text)),
23523 "text" => text_buffer_append_html_escaped(output, &template_value_text(value)),
23524 _ => false,
23525 }
23526}
23527
23528/// Renders template text with an explicit default placeholder kind.
23529///
23530/// # Parameters
23531///
23532/// - `template_text`: Input consumed by [`crate::runtime::json_commands::render_template_text_with_default_kind`].
23533/// - `params_root`: Input consumed by [`crate::runtime::json_commands::render_template_text_with_default_kind`].
23534/// - `default_kind`: Input consumed by [`crate::runtime::json_commands::render_template_text_with_default_kind`].
23535///
23536/// # Returns
23537///
23538/// Returns [`Some`] when [`crate::runtime::json_commands::render_template_text_with_default_kind`] can produce a value and [`None`] otherwise.
23539///
23540/// # Performance
23541///
23542/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::render_template_text_with_default_kind`].
23543///
23544/// # Examples
23545///
23546/// ```rust,ignore
23547/// let _result = pharos_native::runtime::json_commands::render_template_text_with_default_kind(todo!(), &serde_json::json!({}), todo!());
23548/// ```
23549///
23550/// # Related
23551///
23552/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23553pub fn render_template_text_with_default_kind(
23554 template_text: &str,
23555 params_root: &Value,
23556 default_kind: &str,
23557) -> Option<String> {
23558 let params = params_root.as_object()?;
23559 let mut output = String::new();
23560 let mut cursor = 0usize;
23561 while let Some(offset) = template_text[cursor..].find('{') {
23562 let open = cursor + offset;
23563 text_buffer_append(&mut output, &template_text[cursor..open]).then_some(())?;
23564 let close = template_text[open + 1..].find('}')? + open + 1;
23565 let raw = &template_text[open + 1..close];
23566 let (kind, key) = raw
23567 .split_once(':')
23568 .map_or((default_kind, raw), |(kind, key)| (kind, key));
23569 if kind == "currency_minor" {
23570 let value = params.get(key)?;
23571 let amount = value.as_i64().or_else(|| value.as_str()?.parse().ok())?;
23572 let text = currency_minor_text(amount, params.get("currency")?.as_str()?)?;
23573 text_buffer_append_html_escaped(&mut output, &text).then_some(())?;
23574 } else {
23575 text_buffer_append_template_value(&mut output, params.get(key)?, kind).then_some(())?;
23576 }
23577 cursor = close + 1;
23578 }
23579 text_buffer_append(&mut output, &template_text[cursor..]).then_some(output)
23580}
23581
23582/// Resolves a shared JSON template fragment.
23583///
23584/// # Parameters
23585///
23586/// - `fragment_name`: Input consumed by [`crate::runtime::json_commands::shared_asset_fragment_path_json`].
23587///
23588/// # Returns
23589///
23590/// Returns `true` when the condition checked by [`crate::runtime::json_commands::shared_asset_fragment_path_json`] holds and `false` otherwise.
23591///
23592/// # Examples
23593///
23594/// ```rust,ignore
23595/// let _result = pharos_native::runtime::json_commands::shared_asset_fragment_path_json(todo!());
23596/// ```
23597///
23598/// # Related
23599///
23600/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23601pub fn shared_asset_fragment_path_json(fragment_name: &str) -> bool {
23602 fragment_name.starts_with("templates/shared/")
23603 && !fragment_name.contains("..")
23604 && !fragment_name.contains('\0')
23605}
23606
23607/// Returns a shared fragment's rendered HTML.
23608///
23609/// # Parameters
23610///
23611/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_shared_asset_fragment_json`].
23612/// - `fragment_name`: Input consumed by [`crate::runtime::json_commands::render_shared_asset_fragment_json`].
23613/// - `params_root`: Input consumed by [`crate::runtime::json_commands::render_shared_asset_fragment_json`].
23614///
23615/// # Returns
23616///
23617/// Returns [`Some`] when [`crate::runtime::json_commands::render_shared_asset_fragment_json`] can produce a value and [`None`] otherwise.
23618///
23619/// # Examples
23620///
23621/// ```rust,ignore
23622/// let _result = pharos_native::runtime::json_commands::render_shared_asset_fragment_json(std::path::Path::new("apps/dev_docs"), todo!(), &serde_json::json!({}));
23623/// ```
23624///
23625/// # Related
23626///
23627/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23628pub fn render_shared_asset_fragment_json(
23629 app_root: &str,
23630 fragment_name: &str,
23631 params_root: &Value,
23632) -> Option<String> {
23633 shared_asset_fragment_path_json(fragment_name)
23634 .then(|| render_app_fragment_from_doc(app_root, fragment_name, params_root))
23635 .flatten()
23636}
23637
23638/// Renders an app-owned JSON template fragment.
23639///
23640/// # Parameters
23641///
23642/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_app_fragment`].
23643/// - `fragment_name`: Input consumed by [`crate::runtime::json_commands::render_app_fragment`].
23644/// - `params_root`: Input consumed by [`crate::runtime::json_commands::render_app_fragment`].
23645///
23646/// # Returns
23647///
23648/// Returns [`Some`] when [`crate::runtime::json_commands::render_app_fragment`] can produce a value and [`None`] otherwise.
23649///
23650/// # Examples
23651///
23652/// ```rust,ignore
23653/// let _result = pharos_native::runtime::json_commands::render_app_fragment(std::path::Path::new("apps/dev_docs"), todo!(), &serde_json::json!({}));
23654/// ```
23655///
23656/// # Related
23657///
23658/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23659pub fn render_app_fragment(
23660 app_root: &str,
23661 fragment_name: &str,
23662 params_root: &Value,
23663) -> Option<String> {
23664 render_app_fragment_from_doc(app_root, fragment_name, params_root)
23665}
23666
23667/// Renders an app-owned fragment from an in-memory parameter document.
23668///
23669/// # Parameters
23670///
23671/// - `app_root`: Input consumed by [`crate::runtime::json_commands::render_app_fragment_from_doc`].
23672/// - `fragment_name`: Input consumed by [`crate::runtime::json_commands::render_app_fragment_from_doc`].
23673/// - `params_root`: Input consumed by [`crate::runtime::json_commands::render_app_fragment_from_doc`].
23674///
23675/// # Returns
23676///
23677/// Returns [`Some`] when [`crate::runtime::json_commands::render_app_fragment_from_doc`] can produce a value and [`None`] otherwise.
23678///
23679/// # Performance
23680///
23681/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::json_commands::render_app_fragment_from_doc`].
23682///
23683/// # Examples
23684///
23685/// ```rust,ignore
23686/// let _result = pharos_native::runtime::json_commands::render_app_fragment_from_doc(std::path::Path::new("apps/dev_docs"), todo!(), &serde_json::json!({}));
23687/// ```
23688///
23689/// # Related
23690///
23691/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23692pub fn render_app_fragment_from_doc(
23693 app_root: &str,
23694 fragment_name: &str,
23695 params_root: &Value,
23696) -> Option<String> {
23697 if app_root.is_empty()
23698 || fragment_name.is_empty()
23699 || fragment_name.contains("..")
23700 || Path::new(fragment_name).is_absolute()
23701 || !params_root.is_object()
23702 {
23703 return None;
23704 }
23705 render_template_text_with_default_kind(
23706 &fs::read_to_string(Path::new(app_root).join(fragment_name)).ok()?,
23707 params_root,
23708 "text",
23709 )
23710}
23711
23712/// Reads one JSON document from bounded text.
23713///
23714/// # Parameters
23715///
23716/// - `text`: Input consumed by [`crate::runtime::json_commands::json_doc_load_text`].
23717///
23718/// # Returns
23719///
23720/// Returns [`Some`] when [`crate::runtime::json_commands::json_doc_load_text`] can produce a value and [`None`] otherwise.
23721///
23722/// # Examples
23723///
23724/// ```rust,ignore
23725/// let _result = pharos_native::runtime::json_commands::json_doc_load_text("{}");
23726/// ```
23727///
23728/// # Related
23729///
23730/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23731pub fn json_doc_load_text(text: &str) -> Option<Value> {
23732 load_root_from(text)
23733}
23734
23735/// Releases an optional JSON document.
23736///
23737/// # Parameters
23738///
23739/// - `document`: Input consumed by [`crate::runtime::json_commands::json_doc_free`].
23740///
23741/// # Returns
23742///
23743/// Returns after [`crate::runtime::json_commands::json_doc_free`] applies its in-place effect.
23744///
23745/// # Examples
23746///
23747/// ```rust,ignore
23748/// let _result = pharos_native::runtime::json_commands::json_doc_free(todo!());
23749/// ```
23750///
23751/// # Related
23752///
23753/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23754pub fn json_doc_free(document: &mut Option<Value>) {
23755 *document = None;
23756}
23757
23758/// Returns one object member without cloning it.
23759///
23760/// # Parameters
23761///
23762/// - `object`: Input consumed by [`crate::runtime::json_commands::json_obj_get`].
23763/// - `key`: Input consumed by [`crate::runtime::json_commands::json_obj_get`].
23764///
23765/// # Returns
23766///
23767/// Returns [`Some`] when [`crate::runtime::json_commands::json_obj_get`] can produce a value and [`None`] otherwise.
23768///
23769/// # Examples
23770///
23771/// ```rust,ignore
23772/// let _result = pharos_native::runtime::json_commands::json_obj_get(todo!(), "key");
23773/// ```
23774///
23775/// # Related
23776///
23777/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23778pub fn json_obj_get<'a>(object: &'a Value, key: &str) -> Option<&'a Value> {
23779 object.as_object()?.get(key)
23780}
23781
23782/// Returns one object array member.
23783///
23784/// # Parameters
23785///
23786/// - `object`: Input consumed by [`crate::runtime::json_commands::json_obj_get_array`].
23787/// - `key`: Input consumed by [`crate::runtime::json_commands::json_obj_get_array`].
23788///
23789/// # Returns
23790///
23791/// Returns [`Some`] when [`crate::runtime::json_commands::json_obj_get_array`] can produce a value and [`None`] otherwise.
23792///
23793/// # Examples
23794///
23795/// ```rust,ignore
23796/// let _result = pharos_native::runtime::json_commands::json_obj_get_array(todo!(), "key");
23797/// ```
23798///
23799/// # Related
23800///
23801/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23802pub fn json_obj_get_array<'a>(object: &'a Value, key: &str) -> Option<&'a Vec<Value>> {
23803 json_obj_get(object, key)?.as_array()
23804}
23805
23806/// Duplicates one object string member.
23807///
23808/// # Parameters
23809///
23810/// - `object`: Input consumed by [`crate::runtime::json_commands::json_obj_get_string`].
23811/// - `key`: Input consumed by [`crate::runtime::json_commands::json_obj_get_string`].
23812///
23813/// # Returns
23814///
23815/// Returns [`Some`] when [`crate::runtime::json_commands::json_obj_get_string`] can produce a value and [`None`] otherwise.
23816///
23817/// # Examples
23818///
23819/// ```rust,ignore
23820/// let _result = pharos_native::runtime::json_commands::json_obj_get_string(todo!(), "key");
23821/// ```
23822///
23823/// # Related
23824///
23825/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23826pub fn json_obj_get_string(object: &Value, key: &str) -> Option<String> {
23827 json_obj_get(object, key)?.as_str().map(ToOwned::to_owned)
23828}
23829
23830/// Reads one object integer member with a default.
23831///
23832/// # Parameters
23833///
23834/// - `object`: Input consumed by [`crate::runtime::json_commands::json_obj_get_long_default`].
23835/// - `key`: Input consumed by [`crate::runtime::json_commands::json_obj_get_long_default`].
23836/// - `default_value`: Input consumed by [`crate::runtime::json_commands::json_obj_get_long_default`].
23837///
23838/// # Returns
23839///
23840/// Returns the computed value produced by [`crate::runtime::json_commands::json_obj_get_long_default`].
23841///
23842/// # Examples
23843///
23844/// ```rust,ignore
23845/// let _result = pharos_native::runtime::json_commands::json_obj_get_long_default(todo!(), "key", todo!());
23846/// ```
23847///
23848/// # Related
23849///
23850/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23851pub fn json_obj_get_long_default(object: &Value, key: &str, default_value: i64) -> i64 {
23852 json_obj_get(object, key)
23853 .and_then(Value::as_i64)
23854 .unwrap_or(default_value)
23855}
23856
23857/// Reads a JSON integer value.
23858///
23859/// # Parameters
23860///
23861/// - `value`: Input consumed by [`crate::runtime::json_commands::json_long_from_value`].
23862///
23863/// # Returns
23864///
23865/// Returns [`Some`] when [`crate::runtime::json_commands::json_long_from_value`] can produce a value and [`None`] otherwise.
23866///
23867/// # Examples
23868///
23869/// ```rust,ignore
23870/// let _result = pharos_native::runtime::json_commands::json_long_from_value("value");
23871/// ```
23872///
23873/// # Related
23874///
23875/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23876pub fn json_long_from_value(value: &Value) -> Option<i64> {
23877 value.as_i64()
23878}
23879
23880/// Serializes one JSON value compactly.
23881///
23882/// # Parameters
23883///
23884/// - `value`: Input consumed by [`crate::runtime::json_commands::json_serialize_compact`].
23885///
23886/// # Returns
23887///
23888/// Returns [`Some`] when [`crate::runtime::json_commands::json_serialize_compact`] can produce a value and [`None`] otherwise.
23889///
23890/// # Examples
23891///
23892/// ```rust,ignore
23893/// let _result = pharos_native::runtime::json_commands::json_serialize_compact("value");
23894/// ```
23895///
23896/// # Related
23897///
23898/// - [`crate::runtime::json_commands`] for adjacent helpers that share the same runtime surface.
23899pub fn json_serialize_compact(value: &Value) -> Option<String> {
23900 serde_json::to_string(value).ok()
23901}
23902
23903#[cfg(test)]
23904mod tests {
23905 /// Checks shared template binding, cycle limits and filesystem confinement.
23906 #[test]
23907 fn shared_template_includes_are_bounded_and_confined() {
23908 let workspace = std::path::Path::new("tmp/0.8.4-testing")
23909 .join(format!("template-includes-{}", std::process::id()));
23910 let root = workspace.join("templates");
23911 std::fs::create_dir_all(root.join("fragments")).unwrap();
23912 let page = root.join("page.html");
23913 let fragment = root.join("fragments/form.html");
23914 std::fs::write(&page, "{include:fragments/form.html}").unwrap();
23915 std::fs::write(&fragment, "<label>{name}</label>").unwrap();
23916 assert_eq!(
23917 super::render_file_template(&page, &serde_json::json!({"name":"<script>"})),
23918 Some("<label><script></label>".to_owned())
23919 );
23920 std::fs::write(&fragment, "{include:fragments/form.html}").unwrap();
23921 assert!(super::render_file_template(&page, &serde_json::json!({})).is_none());
23922 std::fs::write(&page, "{include:../outside.html}").unwrap();
23923 assert!(super::render_file_template(&page, &serde_json::json!({})).is_none());
23924 #[cfg(unix)]
23925 {
23926 std::fs::write(workspace.join("outside.html"), "outside").unwrap();
23927 std::os::unix::fs::symlink(
23928 "../../outside.html",
23929 fragment.with_file_name("escape.html"),
23930 )
23931 .unwrap();
23932 std::fs::write(&page, "{include:fragments/escape.html}").unwrap();
23933 assert!(super::render_file_template(&page, &serde_json::json!({})).is_none());
23934 }
23935 std::fs::remove_dir_all(workspace).unwrap();
23936 }
23937 #[test]
23938 fn service_initials_include_every_word() {
23939 for name in [
23940 "Initial Consultation (Online)",
23941 "Initial Consultation(Online)",
23942 "() Initial (Consultation) ((Online)) ()",
23943 ] {
23944 assert_eq!(super::dynamic_service_abbreviation(name), "ICO");
23945 }
23946 assert_eq!(super::dynamic_service_abbreviation(" () () "), "");
23947 assert_eq!(
23948 super::dynamic_service_abbreviation(" deep tissue massage therapy "),
23949 "DTMT"
23950 );
23951 assert_eq!(
23952 super::dynamic_service_abbreviation("énergie relaxante"),
23953 "ÉR"
23954 );
23955 let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("apps/ucal");
23956 let visual = super::service_visual_html(
23957 &app_root,
23958 None,
23959 "deep tissue massage therapy",
23960 Some("#123456"),
23961 )
23962 .unwrap();
23963 assert!(visual.contains("DTMT"));
23964 let online = super::service_visual_html(
23965 &app_root,
23966 None,
23967 "Initial Consultation (Online)",
23968 Some("#123456"),
23969 )
23970 .unwrap();
23971 assert!(online.contains("<span>ICO</span>"));
23972 }
23973 #[test]
23974 fn service_order_picker_counts_only_active_services() {
23975 let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("apps/ucal");
23976 let state = serde_json::json!({"services": [
23977 {"business_id": 1, "is_active": true, "sort_order": 1},
23978 {"business_id": 1, "is_active": false, "sort_order": 99},
23979 {"business_id": 2, "is_active": true, "sort_order": 1}
23980 ]});
23981 let adding =
23982 super::service_sort_order_options_html(&app_root, &state, 1, 99, true).unwrap();
23983 let editing =
23984 super::service_sort_order_options_html(&app_root, &state, 1, 99, false).unwrap();
23985 assert_eq!(adding.matches("<option").count(), 2);
23986 assert_eq!(editing.matches("<option").count(), 1);
23987 assert!(adding.contains("value=\"2\" selected"));
23988 assert!(editing.contains("value=\"1\" selected"));
23989 }
23990 use super::*;
23991
23992 #[test]
23993 fn appointments_page_params_render_user_appointment_rows() {
23994 let root = serde_json::json!({
23995 "businesses": [{"id": 7, "slug": "lotus-studio", "name": "Lotus Thai Massage"}],
23996 "services": [{"id": 3, "business_id": 7, "name": "Thai Massage", "duration_minutes": 60}],
23997 "appointments": [{
23998 "id": 11,
23999 "user_id": 42,
24000 "business_id": 7,
24001 "service_id": 3,
24002 "date": "2026-09-12",
24003 "time": "13:30",
24004 "status": "confirmed",
24005 "payment_status": "demo"
24006 }]
24007 });
24008 let params = dynamic_appointments_page_params_from_root_json(
24009 &root,
24010 42,
24011 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/ucal")),
24012 )
24013 .expect("appointments params");
24014 let params: Value = serde_json::from_str(¶ms).expect("json params");
24015 let html = params
24016 .get("appointment_items")
24017 .and_then(Value::as_str)
24018 .unwrap_or_default();
24019 assert!(html.contains("Thai Massage"));
24020 assert!(html.contains("Lotus Thai Massage"));
24021 assert!(html.contains("2026-09-12 at 1:30 PM"));
24022 assert!(!html.contains("No appointments"));
24023 }
24024
24025 #[test]
24026 fn user_bookings_page_params_render_persisted_booking_rows() {
24027 let root = serde_json::json!({
24028 "bookings": [{
24029 "id": 4,
24030 "user_id": 9,
24031 "title": "Internal booking",
24032 "date": "2026-09-15",
24033 "time": "10:00",
24034 "status": "confirmed"
24035 }]
24036 });
24037 let params = dynamic_user_bookings_page_params_from_root_json(
24038 &root,
24039 9,
24040 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/ucal")),
24041 )
24042 .expect("bookings params");
24043 let params: Value = serde_json::from_str(¶ms).expect("json params");
24044 let html = params
24045 .get("booking_rows_html")
24046 .and_then(Value::as_str)
24047 .unwrap_or_default();
24048 assert!(html.contains("Internal booking"));
24049 assert!(html.contains("2026-09-15"));
24050 assert!(html.contains("10:00 AM"));
24051 assert!(!html.contains("No bookings yet"));
24052 }
24053
24054 #[test]
24055 fn profile_page_params_render_signed_in_user_state() {
24056 let root = serde_json::json!({
24057 "users": [{
24058 "id": 2,
24059 "email": "customer@example.test",
24060 "display_name": "Customer One",
24061 "full_name": "Customer One",
24062 "phone_number": "555-0102",
24063 "address": "1 Market Road",
24064 "payment_preference": "credit_card"
24065 }],
24066 "businesses": [{"id": 1, "slug": "lotus-studio", "name": "Lotus Thai Massage"}],
24067 "subscriptions": [{"id": 1, "user_id": 2, "business_id": 1}],
24068 "google_calendar_connections": [{"id": 1, "user_id": 2, "connection_status": "connected"}]
24069 });
24070 let params = dynamic_profile_page_params_json(
24071 &root,
24072 2,
24073 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/ucal")),
24074 )
24075 .expect("profile");
24076 let params: Value = serde_json::from_str(¶ms).expect("json params");
24077 assert_eq!(
24078 params.get("full_name").and_then(Value::as_str),
24079 Some("Customer One")
24080 );
24081 assert_eq!(
24082 params
24083 .get("selected_payment_preference")
24084 .and_then(Value::as_str),
24085 Some("credit_card")
24086 );
24087 assert_eq!(
24088 params.get("google_status").and_then(Value::as_str),
24089 Some("connected")
24090 );
24091 assert!(
24092 params
24093 .get("subscription_rows_html")
24094 .and_then(Value::as_str)
24095 .unwrap_or_default()
24096 .contains("Lotus Thai Massage")
24097 );
24098 }
24099
24100 #[test]
24101 fn appointment_detail_params_render_related_records() {
24102 let root = serde_json::json!({
24103 "users": [{"id": 2, "email": "customer@example.test", "full_name": "Customer One"}],
24104 "businesses": [{"id": 7, "owner_id": 1, "slug": "lotus-studio", "name": "Lotus Thai Massage"}],
24105 "services": [{"id": 3, "business_id": 7, "name": "Thai Massage"}],
24106 "appointments": [{
24107 "id": 11,
24108 "user_id": 2,
24109 "business_id": 7,
24110 "service_id": 3,
24111 "date": "2026-09-12",
24112 "time": "13:30",
24113 "status": "confirmed",
24114 "service_cost": "55",
24115 "notes": "Prefers afternoon"
24116 }]
24117 });
24118 let params = dynamic_appointment_detail_page_params_from_root_json(
24119 &root,
24120 2,
24121 11,
24122 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/ucal")),
24123 )
24124 .expect("appointment detail");
24125 let params: Value = serde_json::from_str(¶ms).expect("json params");
24126 assert_eq!(
24127 params.get("service_name").and_then(Value::as_str),
24128 Some("Thai Massage")
24129 );
24130 assert_eq!(
24131 params.get("business_name").and_then(Value::as_str),
24132 Some("Lotus Thai Massage")
24133 );
24134 assert_eq!(
24135 params.get("customer_name").and_then(Value::as_str),
24136 Some("Customer One")
24137 );
24138 assert!(
24139 params
24140 .get("notes_block")
24141 .and_then(Value::as_str)
24142 .unwrap_or_default()
24143 .contains("Prefers afternoon")
24144 );
24145 }
24146
24147 /// Verifies both public states update their accessible label and visual class.
24148 #[test]
24149 fn business_public_toggle_matches_current_state() {
24150 let app_root = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/ucal"));
24151 for (public, label, class) in [
24152 (true, "Public", "business-is-public"),
24153 (false, "Private", "business-is-private"),
24154 ] {
24155 let html = dynamic_render_business_admin_public_state_button_html(
24156 app_root,
24157 "lotus-studio",
24158 public,
24159 )
24160 .expect("toggle");
24161 assert!(html.contains(&format!("aria-label=\"{label}\"")));
24162 assert!(html.contains(&format!("aria-pressed=\"{public}\"")));
24163 assert!(html.contains(class));
24164 assert!(!html.contains("style=\""));
24165 }
24166 }
24167
24168 #[test]
24169 fn business_calendar_params_render_clickable_appointment_dialogs() {
24170 let root = serde_json::json!({
24171 "users": [{"id": 2, "email": "customer@example.test", "full_name": "Customer One"}],
24172 "businesses": [{"id": 7, "owner_id": 1, "slug": "lotus-studio", "name": "Lotus Thai Massage"}],
24173 "services": [{
24174 "id": 3,
24175 "business_id": 7,
24176 "name": "Thai Massage",
24177 "duration_minutes": 45,
24178 "booking_options": [
24179 {
24180 "label": "Extended Thai",
24181 "duration_minutes": 75,
24182 "cost": "90.00"
24183 }
24184 ]
24185 }],
24186 "technicians": [{"id": 5, "business_id": 7, "name": "Tech One"}],
24187 "business_availability": [{"id": 1, "business_id": 7, "day_of_week": "Sat", "start_time": "08:00", "end_time": "16:00"}],
24188 "appointments": [{
24189 "id": 11,
24190 "user_id": 2,
24191 "business_id": 7,
24192 "service_id": 3,
24193 "technician_id": 5,
24194 "date": "2026-09-12",
24195 "time": "13:30",
24196 "end_time": "2026-09-12T14:15:00Z",
24197 "service_duration_minutes": 45,
24198 "status": "confirmed",
24199 "payment_status": "paid",
24200 "notes": "Prefers afternoon"
24201 },
24202 {
24203 "id": 12,
24204 "user_id": 2,
24205 "business_id": 7,
24206 "service_id": 3,
24207 "technician_id": 5,
24208 "date": "2026-09-13",
24209 "time": "13:30",
24210 "status": "confirmed",
24211 "payment_status": "paid",
24212 "notes": "Second day"
24213 }]
24214 });
24215 let before = pharos_runtime_core::time::current_timestamp_utc();
24216 let default_params = dynamic_business_calendar_template_params_from_root_json(
24217 &root,
24218 "lotus-studio",
24219 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/ucal")),
24220 None,
24221 None,
24222 None,
24223 2,
24224 "csrf-test-token",
24225 )
24226 .expect("default calendar params");
24227 let after = pharos_runtime_core::time::current_timestamp_utc();
24228 let default_params: Value =
24229 serde_json::from_str(&default_params).expect("default json params");
24230 let selected = default_params["selected_date"]
24231 .as_str()
24232 .expect("selected date");
24233 assert!(selected == &before[..10] || selected == &after[..10]);
24234 let params = dynamic_business_calendar_template_params_from_root_json(
24235 &root,
24236 "lotus-studio",
24237 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/ucal")),
24238 None,
24239 None,
24240 Some("2026-09-12"),
24241 2,
24242 "csrf-test-token",
24243 )
24244 .expect("calendar params");
24245 let params: Value = serde_json::from_str(¶ms).expect("json params");
24246 let html = params
24247 .get("appointment_rows_html")
24248 .and_then(Value::as_str)
24249 .unwrap_or_default();
24250 assert!(html.contains("data-dialog-open=\"business-calendar-appointment-11\""));
24251 assert!(html.contains("Thai Massage"));
24252 assert!(html.contains("Thai Massage · 45 min"));
24253 assert!(html.contains("Assigned: Tech One"));
24254 assert!(html.contains("<strong>1:30 PM–2:15 PM • 2026-09-12</strong> • Tech One"));
24255 assert!(html.contains("<span>Customer One • customer@example.test</span><span class=\"business-calendar-dialog-payment-status\">paid</span>"));
24256 assert!(html.contains("<strong>Notes:</strong> Prefers afternoon"));
24257 assert!(html.contains("csrf-test-token"));
24258 assert!(html.contains("/appointments/11/cancel/"));
24259 assert!(html.contains("/appointments/11/complete/"));
24260 assert!(html.contains("business-calendar-dialog-title-row"));
24261 assert!(html.contains("aria-label=\"Assign Technician\""));
24262 assert!(!html.contains("title=\"Assign Technician\""));
24263 let internal_dialog_html = params
24264 .get("internal_booking_dialog_html")
24265 .and_then(Value::as_str)
24266 .unwrap_or_default();
24267 assert!(internal_dialog_html.contains("business-calendar-internal-booking"));
24268 assert!(internal_dialog_html.contains("/business/lotus-studio/calendar/appointments/new/"));
24269 assert!(internal_dialog_html.contains("value=\"3\" data-booking-option-index=\"\""));
24270 assert!(internal_dialog_html.contains("Thai Massage · 45 min"));
24271 assert!(internal_dialog_html.contains("data-booking-option-index=\"0\""));
24272 assert!(internal_dialog_html.contains("Thai Massage · Extended Thai · 75 min"));
24273 assert!(internal_dialog_html.contains("data-calendar-booking-option-index"));
24274 assert!(internal_dialog_html.contains("data-calendar-booking-technician"));
24275 let calendar_html = params
24276 .get("calendar_rows_html")
24277 .and_then(Value::as_str)
24278 .unwrap_or_default();
24279 assert!(
24280 calendar_html.contains("background:var(--primary);color:#fff;"),
24281 "selected day remains blue"
24282 );
24283 assert!(
24284 calendar_html.contains("background:#dcfce7;color:#166534;"),
24285 "non-selected booked days render light green"
24286 );
24287 assert!(
24288 calendar_html.contains("has-appointments"),
24289 "booked days include an appointment state class"
24290 );
24291 assert!(
24292 calendar_html.contains("background:var(--gray-200);color:var(--gray-700);"),
24293 "unbooked days remain grey"
24294 );
24295 }
24296
24297 #[test]
24298 fn architect_layout_hides_builder_settings_until_authenticated() {
24299 let app_root = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/apps/architect"));
24300 let anonymous = template_layout_page_html(
24301 app_root,
24302 Some("/architect"),
24303 Some("architect_shell"),
24304 Some("en"),
24305 "Architect Sign In",
24306 "<section>login</section>",
24307 Some("/accounts/login/"),
24308 false,
24309 0,
24310 )
24311 .expect("anonymous architect layout");
24312 assert!(anonymous.contains("Pharos Architect"));
24313 assert!(!anonymous.contains(r#"aria-label="Home""#));
24314 assert!(!anonymous.contains(r#"aria-label="Settings""#));
24315 assert!(!anonymous.contains(r#"aria-label="File""#));
24316 assert!(!anonymous.contains("/architect/settings"));
24317 assert!(!anonymous.contains("/architect/editor"));
24318
24319 let build_user = template_layout_page_html(
24320 app_root,
24321 Some("/architect"),
24322 Some("architect_shell"),
24323 Some("en"),
24324 "Pharos Architect",
24325 "<section>builder</section>",
24326 Some("/"),
24327 true,
24328 1,
24329 )
24330 .expect("build user architect layout");
24331 assert!(build_user.contains(r#"class="nav-icon-button architect-nav-file-link""#));
24332 assert!(build_user.contains(r#"aria-label="Home""#));
24333 assert!(build_user.contains(r#"aria-label="File""#));
24334 assert!(build_user.contains(r#"aria-label="Settings""#));
24335 assert!(build_user.contains(r#"hx-get="/architect/settings""#));
24336 assert!(build_user.contains(r#"hx-get="/architect/editor""#));
24337 assert!(build_user.contains(r#"<span class="nav-button__tooltip">Home</span>"#));
24338 assert!(build_user.contains(r#"<span class="nav-button__tooltip">File</span>"#));
24339 assert!(build_user.contains(r#"<span class="nav-button__tooltip">Settings</span>"#));
24340 assert!(build_user.contains("/architect/settings"));
24341 assert!(build_user.contains("/architect/editor"));
24342 assert!(build_user.contains(r#"aria-label="Log out""#));
24343 assert!(build_user.contains(r#"class="nav-button__icon""#));
24344 assert!(build_user.contains(r#"bi-box-arrow-right"#));
24345 assert!(build_user.contains(r#"action="/architect/accounts/logout/""#));
24346
24347 let admin = template_layout_page_html(
24348 app_root,
24349 Some("/architect"),
24350 Some("architect_shell"),
24351 Some("en"),
24352 "Pharos Architect",
24353 "<section>builder</section>",
24354 Some("/"),
24355 true,
24356 1,
24357 )
24358 .expect("admin architect layout");
24359 assert!(admin.contains(r#"aria-label="Home""#));
24360 assert!(admin.contains(r#"aria-label="File""#));
24361 assert!(admin.contains(r#"aria-label="Settings""#));
24362 assert!(admin.contains(r#"hx-get="/architect/settings""#));
24363 assert!(admin.contains(r#"hx-get="/architect/editor""#));
24364 assert!(admin.contains(r#"<span class="nav-button__tooltip">Settings</span>"#));
24365 assert!(admin.contains("/architect/settings"));
24366 }
24367
24368 #[test]
24369 fn layout_browser_title_prefixes_app_name_without_duplicate_branding() {
24370 let app_root =
24371 std::env::temp_dir().join(format!("pharos-title-test-{}", std::process::id()));
24372 std::fs::create_dir_all(&app_root).expect("create title fixture app");
24373 std::fs::write(
24374 app_root.join("pharos.app.json"),
24375 r#"{"app_id":"commerce_test","name":"Beacon Beats"}"#,
24376 )
24377 .expect("write title fixture manifest");
24378
24379 assert_eq!(
24380 layout_browser_title(&app_root, "Checkout"),
24381 "Beacon Beats - Checkout"
24382 );
24383 assert_eq!(
24384 layout_browser_title(&app_root, "Checkout — Beacon Beats"),
24385 "Beacon Beats - Checkout"
24386 );
24387 assert_eq!(
24388 layout_browser_title(&app_root, "Beacon Beats — Audio that moves the room"),
24389 "Beacon Beats - Audio that moves the room"
24390 );
24391
24392 std::fs::remove_dir_all(app_root).ok();
24393 }
24394}