pharos_native/runtime/ui.rs
1#![allow(clippy::too_many_arguments)]
2
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use serde_json::{Map, Value};
7
8#[allow(non_snake_case)]
9/// Performs ADD UI PARAM.
10///
11/// # Parameters
12///
13/// - `params`: Input consumed by [`crate::runtime::ui::ADD_UI_PARAM`].
14/// - `key`: Input consumed by [`crate::runtime::ui::ADD_UI_PARAM`].
15/// - `text`: Input consumed by [`crate::runtime::ui::ADD_UI_PARAM`].
16///
17/// # Returns
18///
19/// Returns `true` when the condition checked by [`crate::runtime::ui::ADD_UI_PARAM`] holds and `false` otherwise.
20///
21/// # Examples
22///
23/// ```rust,ignore
24/// let _result = pharos_native::runtime::ui::ADD_UI_PARAM(todo!(), "key", "{}");
25/// ```
26///
27/// # Related
28///
29/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
30pub fn ADD_UI_PARAM(params: &mut Map<String, Value>, key: &str, text: &str) -> bool {
31 if key.is_empty() {
32 return false;
33 }
34 params.insert(key.to_owned(), Value::String(text.to_owned()));
35 true
36}
37
38/// Performs UI render param map.
39///
40/// # Parameters
41///
42/// - `id`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
43/// - `input_type`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
44/// - `button_type`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
45/// - `name`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
46/// - `label`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
47/// - `label_html`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
48/// - `value`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
49/// - `class_name`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
50/// - `label_class`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
51/// - `attributes_html`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
52/// - `options_html`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
53/// - `help_html`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
54/// - `content_html`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
55/// - `element_type`: Input consumed by [`crate::runtime::ui::ui_render_param_map`].
56///
57/// # Returns
58///
59/// Returns [`Some`] when [`crate::runtime::ui::ui_render_param_map`] can produce a value and [`None`] otherwise.
60///
61/// # Examples
62///
63/// ```rust,ignore
64/// let _result = pharos_native::runtime::ui::ui_render_param_map(todo!(), todo!(), todo!(), "name", todo!(), todo!(), "value", todo!(), todo!(), todo!(), todo!(), todo!(), todo!(), todo!());
65/// ```
66///
67/// # Related
68///
69/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
70pub fn ui_render_param_map(
71 id: &str,
72 input_type: Option<&str>,
73 button_type: Option<&str>,
74 name: &str,
75 label: &str,
76 label_html: &str,
77 value: &str,
78 class_name: Option<&str>,
79 label_class: Option<&str>,
80 attributes_html: &str,
81 options_html: &str,
82 help_html: &str,
83 content_html: &str,
84 element_type: &str,
85) -> Option<Map<String, Value>> {
86 if id.is_empty() || name.is_empty() || label.is_empty() || element_type.is_empty() {
87 return None;
88 }
89 let mut params = Map::new();
90 let resolved_class_name = class_name.unwrap_or(if element_type == "button" {
91 ""
92 } else {
93 "form-control"
94 });
95 let resolved_label_class = label_class.unwrap_or("");
96 ADD_UI_PARAM(&mut params, "id", id).then_some(())?;
97 ADD_UI_PARAM(&mut params, "input_type", input_type.unwrap_or("text")).then_some(())?;
98 ADD_UI_PARAM(&mut params, "button_type", button_type.unwrap_or("button")).then_some(())?;
99 ADD_UI_PARAM(&mut params, "name", name).then_some(())?;
100 ADD_UI_PARAM(&mut params, "label", label).then_some(())?;
101 ADD_UI_PARAM(&mut params, "label_html", label_html).then_some(())?;
102 ADD_UI_PARAM(&mut params, "value", value).then_some(())?;
103 ADD_UI_PARAM(&mut params, "class_name", resolved_class_name).then_some(())?;
104 ADD_UI_PARAM(&mut params, "label_class", resolved_label_class).then_some(())?;
105 ADD_UI_PARAM(&mut params, "attributes_html", attributes_html).then_some(())?;
106 ADD_UI_PARAM(&mut params, "options_html", options_html).then_some(())?;
107 ADD_UI_PARAM(&mut params, "help_html", help_html).then_some(())?;
108 ADD_UI_PARAM(&mut params, "content_html", content_html).then_some(())?;
109 Some(params)
110}
111
112const UI_BUFFER_LIMIT: usize = 2 * 1024 * 1024;
113const UI_IDENTIFIER_LIMIT: usize = 128;
114
115/// Performs HTML escape.
116fn html_escape(value: &str) -> String {
117 value
118 .replace('&', "&")
119 .replace('<', "<")
120 .replace('>', ">")
121 .replace('"', """)
122 .replace('\'', "'")
123}
124
125/// Appends a bounded slice to a canonical UI buffer.
126///
127/// # Parameters
128///
129/// - `buffer`: Input consumed by [`crate::runtime::ui::ui_buffer_append_n`].
130/// - `text`: Input consumed by [`crate::runtime::ui::ui_buffer_append_n`].
131/// - `length`: Input consumed by [`crate::runtime::ui::ui_buffer_append_n`].
132///
133/// # Returns
134///
135/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_buffer_append_n`] holds and `false` otherwise.
136///
137/// # Examples
138///
139/// ```rust,ignore
140/// let _result = pharos_native::runtime::ui::ui_buffer_append_n(&mut [], "{}", todo!());
141/// ```
142///
143/// # Related
144///
145/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
146pub fn ui_buffer_append_n(buffer: &mut String, text: &str, length: usize) -> bool {
147 let Some(end) = length.checked_add(0) else {
148 return false;
149 };
150 let Some(fragment) = text.get(..end.min(text.len())) else {
151 return false;
152 };
153 if buffer.len().saturating_add(fragment.len()) > UI_BUFFER_LIMIT {
154 return false;
155 }
156 buffer.push_str(fragment);
157 true
158}
159
160/// Appends one complete canonical UI fragment to a bounded buffer.
161///
162/// # Parameters
163///
164/// - `buffer`: Input consumed by [`crate::runtime::ui::ui_buffer_append`].
165/// - `text`: Input consumed by [`crate::runtime::ui::ui_buffer_append`].
166///
167/// # Returns
168///
169/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_buffer_append`] holds and `false` otherwise.
170///
171/// # Examples
172///
173/// ```rust,ignore
174/// let _result = pharos_native::runtime::ui::ui_buffer_append(&mut [], "{}");
175/// ```
176///
177/// # Related
178///
179/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
180pub fn ui_buffer_append(buffer: &mut String, text: &str) -> bool {
181 ui_buffer_append_n(buffer, text, text.len())
182}
183
184/// Takes ownership of a canonical UI buffer and leaves it empty.
185///
186/// # Parameters
187///
188/// - `buffer`: Input consumed by [`crate::runtime::ui::ui_buffer_take`].
189///
190/// # Returns
191///
192/// Returns [`Some`] when [`crate::runtime::ui::ui_buffer_take`] can produce a value and [`None`] otherwise.
193///
194/// # Examples
195///
196/// ```rust,ignore
197/// let _result = pharos_native::runtime::ui::ui_buffer_take(&mut []);
198/// ```
199///
200/// # Related
201///
202/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
203pub fn ui_buffer_take(buffer: &mut String) -> Option<String> {
204 (!buffer.is_empty()).then(|| std::mem::take(buffer))
205}
206
207/// Validates an identifier used by the canonical UI ledger.
208///
209/// # Parameters
210///
211/// - `value`: Input consumed by [`crate::runtime::ui::ui_identifier_valid`].
212///
213/// # Returns
214///
215/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_identifier_valid`] holds and `false` otherwise.
216///
217/// # Performance
218///
219/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_identifier_valid`].
220///
221/// # Examples
222///
223/// ```rust,ignore
224/// let _result = pharos_native::runtime::ui::ui_identifier_valid("value");
225/// ```
226///
227/// # Related
228///
229/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
230pub fn ui_identifier_valid(value: &str) -> bool {
231 !value.is_empty()
232 && value.len() <= UI_IDENTIFIER_LIMIT
233 && value
234 .bytes()
235 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
236 && value.as_bytes()[0].is_ascii_alphabetic()
237}
238
239/// Restricts input widgets to the HTML types supported by the declarative contract.
240///
241/// # Parameters
242///
243/// - `value`: Input consumed by [`crate::runtime::ui::ui_input_type_allowed`].
244///
245/// # Returns
246///
247/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_input_type_allowed`] holds and `false` otherwise.
248///
249/// # Examples
250///
251/// ```rust,ignore
252/// let _result = pharos_native::runtime::ui::ui_input_type_allowed("value");
253/// ```
254///
255/// # Related
256///
257/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
258pub fn ui_input_type_allowed(value: &str) -> bool {
259 matches!(
260 value,
261 "text"
262 | "email"
263 | "password"
264 | "number"
265 | "date"
266 | "time"
267 | "url"
268 | "tel"
269 | "search"
270 | "hidden"
271 )
272}
273
274/// Validates an app-relative fragment path and rejects traversal or control bytes.
275///
276/// # Parameters
277///
278/// - `relative`: Input consumed by [`crate::runtime::ui::ui_fragment_path_valid`].
279///
280/// # Returns
281///
282/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_fragment_path_valid`] holds and `false` otherwise.
283///
284/// # Performance
285///
286/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_fragment_path_valid`].
287///
288/// # Examples
289///
290/// ```rust,ignore
291/// let _result = pharos_native::runtime::ui::ui_fragment_path_valid(todo!());
292/// ```
293///
294/// # Related
295///
296/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
297pub fn ui_fragment_path_valid(relative: &str) -> bool {
298 !relative.is_empty()
299 && relative.len() <= 512
300 && !relative.starts_with('/')
301 && !relative.contains("..")
302 && !relative.bytes().any(|byte| byte < 0x20 || byte == 0x7f)
303}
304
305/// Resolves a validated app-relative UI path while preserving containment.
306///
307/// # Parameters
308///
309/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_path`].
310/// - `relative`: Input consumed by [`crate::runtime::ui::ui_path`].
311///
312/// # Returns
313///
314/// Returns [`Some`] when [`crate::runtime::ui::ui_path`] can produce a value and [`None`] otherwise.
315///
316/// # Examples
317///
318/// ```rust,ignore
319/// let _result = pharos_native::runtime::ui::ui_path(std::path::Path::new("apps/dev_docs"), todo!());
320/// ```
321///
322/// # Related
323///
324/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
325pub fn ui_path(app_root: &str, relative: &str) -> Option<String> {
326 if app_root.is_empty() || !ui_fragment_path_valid(relative) {
327 return None;
328 }
329 let root = Path::new(app_root).canonicalize().ok()?;
330 let candidate = root.join(relative);
331 let resolved = candidate.canonicalize().ok()?;
332 resolved
333 .starts_with(&root)
334 .then(|| resolved.to_string_lossy().into_owned())
335}
336
337/// Identifies shared framework fragments.
338///
339/// # Parameters
340///
341/// - `relative`: Input consumed by [`crate::runtime::ui::ui_shared_asset_fragment`].
342///
343/// # Returns
344///
345/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_shared_asset_fragment`] holds and `false` otherwise.
346///
347/// # Examples
348///
349/// ```rust,ignore
350/// let _result = pharos_native::runtime::ui::ui_shared_asset_fragment(todo!());
351/// ```
352///
353/// # Related
354///
355/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
356pub fn ui_shared_asset_fragment(relative: &str) -> bool {
357 relative.starts_with("templates/shared/") && ui_fragment_path_valid(relative)
358}
359
360/// Resolves a shared framework fragment under the app root.
361///
362/// # Parameters
363///
364/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_shared_asset_path`].
365/// - `relative`: Input consumed by [`crate::runtime::ui::ui_shared_asset_path`].
366///
367/// # Returns
368///
369/// Returns [`Some`] when [`crate::runtime::ui::ui_shared_asset_path`] can produce a value and [`None`] otherwise.
370///
371/// # Examples
372///
373/// ```rust,ignore
374/// let _result = pharos_native::runtime::ui::ui_shared_asset_path(std::path::Path::new("apps/dev_docs"), todo!());
375/// ```
376///
377/// # Related
378///
379/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
380pub fn ui_shared_asset_path(app_root: &str, relative: &str) -> Option<String> {
381 ui_shared_asset_fragment(relative)
382 .then(|| ui_path(app_root, relative))
383 .flatten()
384}
385
386/// Appends HTML-escaped attribute text to a canonical buffer.
387///
388/// # Parameters
389///
390/// - `buffer`: Input consumed by [`crate::runtime::ui::ui_append_attr_escaped`].
391/// - `value`: Input consumed by [`crate::runtime::ui::ui_append_attr_escaped`].
392///
393/// # Returns
394///
395/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_append_attr_escaped`] holds and `false` otherwise.
396///
397/// # Examples
398///
399/// ```rust,ignore
400/// let _result = pharos_native::runtime::ui::ui_append_attr_escaped(&mut [], "value");
401/// ```
402///
403/// # Related
404///
405/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
406pub fn ui_append_attr_escaped(buffer: &mut String, value: &str) -> bool {
407 ui_buffer_append(buffer, &html_escape(value))
408}
409
410/// Builds one optional escaped HTML attribute.
411///
412/// # Parameters
413///
414/// - `attribute`: Input consumed by [`crate::runtime::ui::ui_optional_attribute`].
415/// - `value`: Input consumed by [`crate::runtime::ui::ui_optional_attribute`].
416///
417/// # Returns
418///
419/// Returns [`Some`] when [`crate::runtime::ui::ui_optional_attribute`] can produce a value and [`None`] otherwise.
420///
421/// # Examples
422///
423/// ```rust,ignore
424/// let _result = pharos_native::runtime::ui::ui_optional_attribute(todo!(), "value");
425/// ```
426///
427/// # Related
428///
429/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
430pub fn ui_optional_attribute(attribute: &str, value: Option<&str>) -> Option<String> {
431 if attribute.is_empty() || !ui_identifier_valid(attribute) {
432 return None;
433 }
434 value
435 .filter(|text| !text.is_empty())
436 .map(|text| format!(" {attribute}=\"{}\"", html_escape(text)))
437}
438
439/// Appends a boolean HTML attribute when enabled.
440///
441/// # Parameters
442///
443/// - `buffer`: Input consumed by [`crate::runtime::ui::ui_append_boolean_attribute`].
444/// - `attribute`: Input consumed by [`crate::runtime::ui::ui_append_boolean_attribute`].
445/// - `enabled`: Input consumed by [`crate::runtime::ui::ui_append_boolean_attribute`].
446///
447/// # Returns
448///
449/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_append_boolean_attribute`] holds and `false` otherwise.
450///
451/// # Examples
452///
453/// ```rust,ignore
454/// let _result = pharos_native::runtime::ui::ui_append_boolean_attribute(&mut [], todo!(), todo!());
455/// ```
456///
457/// # Related
458///
459/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
460pub fn ui_append_boolean_attribute(buffer: &mut String, attribute: &str, enabled: bool) -> bool {
461 if !enabled {
462 return true;
463 }
464 if attribute.is_empty() || !ui_identifier_valid(attribute) {
465 return false;
466 }
467 ui_buffer_append(buffer, &format!(" {attribute}"))
468}
469
470/// Rejects event literals that could inject markup or script.
471///
472/// # Parameters
473///
474/// - `value`: Input consumed by [`crate::runtime::ui::ui_event_literal_allowed`].
475///
476/// # Returns
477///
478/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_event_literal_allowed`] holds and `false` otherwise.
479///
480/// # Performance
481///
482/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_event_literal_allowed`].
483///
484/// # Examples
485///
486/// ```rust,ignore
487/// let _result = pharos_native::runtime::ui::ui_event_literal_allowed("value");
488/// ```
489///
490/// # Related
491///
492/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
493pub fn ui_event_literal_allowed(value: &str) -> bool {
494 !value.is_empty()
495 && value.len() <= 256
496 && !value
497 .chars()
498 .any(|ch| ch.is_control() || matches!(ch, '<' | '>' | '"' | '\''))
499}
500
501/// Performs value text.
502fn value_text(value: Option<&Value>) -> String {
503 match value {
504 Some(Value::String(text)) => text.clone(),
505 Some(Value::Bool(value)) => value.to_string(),
506 Some(Value::Number(value)) => value.to_string(),
507 Some(Value::Null) | None => String::new(),
508 Some(value) => serde_json::to_string(value).unwrap_or_default(),
509 }
510}
511
512/// Performs resolved value.
513fn resolved_value<'a>(value: Option<&'a Value>, params: &'a Value) -> Option<&'a Value> {
514 match value {
515 Some(Value::String(text)) if text.starts_with("{") && text.ends_with('}') => {
516 params.get(&text[1..text.len() - 1])
517 }
518 other => other,
519 }
520}
521
522/// Resolves a trusted HTML value from a declaration or page parameters.
523///
524/// # Parameters
525///
526/// - `value`: Input consumed by [`crate::runtime::ui::ui_resolved_trusted_html`].
527/// - `params`: Input consumed by [`crate::runtime::ui::ui_resolved_trusted_html`].
528///
529/// # Returns
530///
531/// Returns [`Some`] when [`crate::runtime::ui::ui_resolved_trusted_html`] can produce a value and [`None`] otherwise.
532///
533/// # Examples
534///
535/// ```rust,ignore
536/// let _result = pharos_native::runtime::ui::ui_resolved_trusted_html("value", todo!());
537/// ```
538///
539/// # Related
540///
541/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
542pub fn ui_resolved_trusted_html<'a>(value: Option<&'a Value>, params: &'a Value) -> Option<String> {
543 resolved_value(value, params)
544 .and_then(Value::as_str)
545 .map(ToOwned::to_owned)
546}
547
548/// Expands bounded parameter placeholders without executing authored code.
549///
550/// # Parameters
551///
552/// - `template_text`: Input consumed by [`crate::runtime::ui::ui_render_mutable_params`].
553/// - `params`: Input consumed by [`crate::runtime::ui::ui_render_mutable_params`].
554///
555/// # Returns
556///
557/// Returns [`Some`] when [`crate::runtime::ui::ui_render_mutable_params`] can produce a value and [`None`] otherwise.
558///
559/// # Performance
560///
561/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_render_mutable_params`].
562///
563/// # Examples
564///
565/// ```rust,ignore
566/// let _result = pharos_native::runtime::ui::ui_render_mutable_params(todo!(), todo!());
567/// ```
568///
569/// # Related
570///
571/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
572pub fn ui_render_mutable_params(template_text: &str, params: &Value) -> Option<String> {
573 let object = params.as_object()?;
574 let mut output = String::with_capacity(template_text.len());
575 let mut cursor = 0usize;
576 while let Some(offset) = template_text[cursor..].find('{') {
577 let open = cursor + offset;
578 output.push_str(&template_text[cursor..open]);
579 let close = template_text[open + 1..].find('}')? + open + 1;
580 let key = &template_text[open + 1..close];
581 let value = object
582 .get(key)
583 .map(|value| value_text(Some(value)))
584 .unwrap_or_default();
585 output.push_str(&html_escape(&value));
586 cursor = close + 1;
587 }
588 output.push_str(&template_text[cursor..]);
589 (output.len() <= UI_BUFFER_LIMIT).then_some(output)
590}
591
592/// Loads and renders one validated app fragment.
593///
594/// # Parameters
595///
596/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_render_fragment`].
597/// - `relative`: Input consumed by [`crate::runtime::ui::ui_render_fragment`].
598/// - `params`: Input consumed by [`crate::runtime::ui::ui_render_fragment`].
599///
600/// # Returns
601///
602/// Returns [`Some`] when [`crate::runtime::ui::ui_render_fragment`] can produce a value and [`None`] otherwise.
603///
604/// # Performance
605///
606/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_render_fragment`].
607///
608/// # Examples
609///
610/// ```rust,ignore
611/// let _result = pharos_native::runtime::ui::ui_render_fragment(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
612/// ```
613///
614/// # Related
615///
616/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
617pub fn ui_render_fragment(app_root: &str, relative: &str, params: &Value) -> Option<String> {
618 let path = if ui_shared_asset_fragment(relative) {
619 ui_shared_asset_path(app_root, relative)
620 } else {
621 ui_path(app_root, relative)
622 }?;
623 ui_render_mutable_params(&fs::read_to_string(path).ok()?, params)
624}
625
626/// Converts one scalar value to its canonical display text.
627///
628/// # Parameters
629///
630/// - `value`: Input consumed by [`crate::runtime::ui::ui_scalar_text`].
631///
632/// # Returns
633///
634/// Returns the computed value produced by [`crate::runtime::ui::ui_scalar_text`].
635///
636/// # Examples
637///
638/// ```rust,ignore
639/// let _result = pharos_native::runtime::ui::ui_scalar_text("value");
640/// ```
641///
642/// # Related
643///
644/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
645pub fn ui_scalar_text(value: Option<&Value>) -> String {
646 value_text(value)
647}
648
649/// Resolves a string value through page parameters.
650///
651/// # Parameters
652///
653/// - `value`: Input consumed by [`crate::runtime::ui::ui_resolved_string`].
654/// - `params`: Input consumed by [`crate::runtime::ui::ui_resolved_string`].
655///
656/// # Returns
657///
658/// Returns the computed value produced by [`crate::runtime::ui::ui_resolved_string`].
659///
660/// # Examples
661///
662/// ```rust,ignore
663/// let _result = pharos_native::runtime::ui::ui_resolved_string("value", todo!());
664/// ```
665///
666/// # Related
667///
668/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
669pub fn ui_resolved_string(value: Option<&Value>, params: &Value) -> String {
670 value_text(resolved_value(value, params))
671}
672
673/// Interpolates `{key}` placeholders in a scalar string.
674///
675/// # Parameters
676///
677/// - `value`: Input consumed by [`crate::runtime::ui::ui_interpolated_string`].
678/// - `params`: Input consumed by [`crate::runtime::ui::ui_interpolated_string`].
679///
680/// # Returns
681///
682/// Returns [`Some`] when [`crate::runtime::ui::ui_interpolated_string`] can produce a value and [`None`] otherwise.
683///
684/// # Examples
685///
686/// ```rust,ignore
687/// let _result = pharos_native::runtime::ui::ui_interpolated_string("value", todo!());
688/// ```
689///
690/// # Related
691///
692/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
693pub fn ui_interpolated_string(value: Option<&Value>, params: &Value) -> Option<String> {
694 ui_render_mutable_params(value?.as_str()?, params)
695}
696
697/// Resolves a boolean value through page parameters.
698///
699/// # Parameters
700///
701/// - `value`: Input consumed by [`crate::runtime::ui::ui_resolved_bool`].
702/// - `params`: Input consumed by [`crate::runtime::ui::ui_resolved_bool`].
703///
704/// # Returns
705///
706/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_resolved_bool`] holds and `false` otherwise.
707///
708/// # Examples
709///
710/// ```rust,ignore
711/// let _result = pharos_native::runtime::ui::ui_resolved_bool("value", todo!());
712/// ```
713///
714/// # Related
715///
716/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
717pub fn ui_resolved_bool(value: Option<&Value>, params: &Value) -> bool {
718 match resolved_value(value, params) {
719 Some(Value::Bool(value)) => *value,
720 Some(Value::String(value)) => matches!(value.as_str(), "true" | "1" | "yes" | "on"),
721 Some(Value::Number(value)) => value.as_i64().is_some_and(|number| number != 0),
722 _ => false,
723 }
724}
725
726/// Finds fragment value.
727fn find_fragment_value<'a>(root: &'a Value, id: &str) -> Option<&'a Value> {
728 root.get("elements")
729 .and_then(Value::as_array)?
730 .iter()
731 .find(|item| item.get("id").and_then(Value::as_str) == Some(id))
732}
733
734/// Renders choice options from a declarative array.
735///
736/// # Parameters
737///
738/// - `options`: Input consumed by [`crate::runtime::ui::ui_render_options`].
739/// - `params`: Input consumed by [`crate::runtime::ui::ui_render_options`].
740/// - `selected`: Input consumed by [`crate::runtime::ui::ui_render_options`].
741///
742/// # Returns
743///
744/// Returns [`Some`] when [`crate::runtime::ui::ui_render_options`] can produce a value and [`None`] otherwise.
745///
746/// # Performance
747///
748/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_render_options`].
749///
750/// # Examples
751///
752/// ```rust,ignore
753/// let _result = pharos_native::runtime::ui::ui_render_options(todo!(), todo!(), todo!());
754/// ```
755///
756/// # Related
757///
758/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
759pub fn ui_render_options(
760 options: Option<&Value>,
761 params: &Value,
762 selected: &str,
763) -> Option<String> {
764 let mut output = String::new();
765 for option in options?.as_array()? {
766 let value = ui_resolved_string(option.get("value"), params);
767 let label = ui_resolved_string(option.get("label"), params);
768 let selected_attr = if value == selected { " selected" } else { "" };
769 if !ui_buffer_append(
770 &mut output,
771 &format!(
772 "<option value=\"{}\"{}>{}</option>",
773 html_escape(&value),
774 selected_attr,
775 html_escape(&label)
776 ),
777 ) {
778 return None;
779 }
780 }
781 Some(output)
782}
783
784/// Renders escaped help copy as a fragment.
785///
786/// # Parameters
787///
788/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_render_help`].
789/// - `help`: Input consumed by [`crate::runtime::ui::ui_render_help`].
790///
791/// # Returns
792///
793/// Returns [`Some`] when [`crate::runtime::ui::ui_render_help`] can produce a value and [`None`] otherwise.
794///
795/// # Examples
796///
797/// ```rust,ignore
798/// let _result = pharos_native::runtime::ui::ui_render_help(std::path::Path::new("apps/dev_docs"), todo!());
799/// ```
800///
801/// # Related
802///
803/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
804pub fn ui_render_help(app_root: &str, help: &str) -> Option<String> {
805 if help.is_empty() {
806 return Some(String::new());
807 }
808 let relative = help.strip_prefix("fragment:").unwrap_or(help);
809 if relative != help {
810 ui_render_fragment(app_root, relative, &Value::Object(Map::new()))
811 } else {
812 Some(format!(
813 "<small class=\"form-text\">{}</small>",
814 html_escape(help)
815 ))
816 }
817}
818
819/// Finds a canonical UI declaration by identifier.
820///
821/// # Parameters
822///
823/// - `ledger_root`: Input consumed by [`crate::runtime::ui::ui_find_element`].
824/// - `canonical_id`: Input consumed by [`crate::runtime::ui::ui_find_element`].
825///
826/// # Returns
827///
828/// Returns [`Some`] when [`crate::runtime::ui::ui_find_element`] can produce a value and [`None`] otherwise.
829///
830/// # Examples
831///
832/// ```rust,ignore
833/// let _result = pharos_native::runtime::ui::ui_find_element(todo!(), todo!());
834/// ```
835///
836/// # Related
837///
838/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
839pub fn ui_find_element<'a>(ledger_root: &'a Value, canonical_id: &str) -> Option<&'a Value> {
840 find_fragment_value(ledger_root, canonical_id)
841}
842
843/// Duplicates one scalar display value after parameter resolution.
844///
845/// # Parameters
846///
847/// - `value`: Input consumed by [`crate::runtime::ui::ui_scalar_dup`].
848/// - `params`: Input consumed by [`crate::runtime::ui::ui_scalar_dup`].
849///
850/// # Returns
851///
852/// Returns [`Some`] when [`crate::runtime::ui::ui_scalar_dup`] can produce a value and [`None`] otherwise.
853///
854/// # Examples
855///
856/// ```rust,ignore
857/// let _result = pharos_native::runtime::ui::ui_scalar_dup("value", todo!());
858/// ```
859///
860/// # Related
861///
862/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
863pub fn ui_scalar_dup(value: Option<&Value>, params: &Value) -> Option<String> {
864 Some(ui_resolved_string(value, params))
865}
866
867/// Renders a declared fragment with page parameters.
868///
869/// # Parameters
870///
871/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_render_page_params_fragment`].
872/// - `field`: Input consumed by [`crate::runtime::ui::ui_render_page_params_fragment`].
873/// - `params`: Input consumed by [`crate::runtime::ui::ui_render_page_params_fragment`].
874///
875/// # Returns
876///
877/// Returns [`Some`] when [`crate::runtime::ui::ui_render_page_params_fragment`] can produce a value and [`None`] otherwise.
878///
879/// # Examples
880///
881/// ```rust,ignore
882/// let _result = pharos_native::runtime::ui::ui_render_page_params_fragment(std::path::Path::new("apps/dev_docs"), "field", todo!());
883/// ```
884///
885/// # Related
886///
887/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
888pub fn ui_render_page_params_fragment(
889 app_root: &str,
890 field: &Value,
891 params: &Value,
892) -> Option<String> {
893 let relative = field
894 .get("path")
895 .or_else(|| field.get("fragment"))
896 .and_then(Value::as_str)?;
897 ui_render_fragment(app_root, relative, params)
898}
899
900/// Adds a ledger-backed attribute after resolving its value.
901///
902/// # Parameters
903///
904/// - `buffer`: Input consumed by [`crate::runtime::ui::ui_append_ledger_attribute`].
905/// - `element`: Input consumed by [`crate::runtime::ui::ui_append_ledger_attribute`].
906/// - `key`: Input consumed by [`crate::runtime::ui::ui_append_ledger_attribute`].
907/// - `attribute`: Input consumed by [`crate::runtime::ui::ui_append_ledger_attribute`].
908/// - `params`: Input consumed by [`crate::runtime::ui::ui_append_ledger_attribute`].
909/// - `event_policy`: Input consumed by [`crate::runtime::ui::ui_append_ledger_attribute`].
910///
911/// # Returns
912///
913/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_append_ledger_attribute`] holds and `false` otherwise.
914///
915/// # Examples
916///
917/// ```rust,ignore
918/// let _result = pharos_native::runtime::ui::ui_append_ledger_attribute(&mut [], todo!(), "key", todo!(), todo!(), todo!());
919/// ```
920///
921/// # Related
922///
923/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
924pub fn ui_append_ledger_attribute(
925 buffer: &mut String,
926 element: &Value,
927 key: &str,
928 attribute: &str,
929 params: &Value,
930 event_policy: bool,
931) -> bool {
932 let value = ui_resolved_string(element.get(key), params);
933 if value.is_empty() {
934 return true;
935 }
936 if event_policy && !ui_event_literal_allowed(&value) {
937 return false;
938 }
939 ui_buffer_append(buffer, &format!(" {attribute}=\"{}\"", html_escape(&value)))
940}
941
942/// Validates structured data and HTMX attribute keys.
943///
944/// # Parameters
945///
946/// - `group`: Input consumed by [`crate::runtime::ui::ui_structured_key_allowed`].
947/// - `key`: Input consumed by [`crate::runtime::ui::ui_structured_key_allowed`].
948///
949/// # Returns
950///
951/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_structured_key_allowed`] holds and `false` otherwise.
952///
953/// # Examples
954///
955/// ```rust,ignore
956/// let _result = pharos_native::runtime::ui::ui_structured_key_allowed(todo!(), "key");
957/// ```
958///
959/// # Related
960///
961/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
962pub fn ui_structured_key_allowed(group: &str, key: &str) -> bool {
963 matches!(group, "data" | "hx") && ui_identifier_valid(key)
964}
965
966/// Appends a structured attribute group from a declaration.
967///
968/// # Parameters
969///
970/// - `buffer`: Input consumed by [`crate::runtime::ui::ui_append_structured_attributes`].
971/// - `element`: Input consumed by [`crate::runtime::ui::ui_append_structured_attributes`].
972/// - `group`: Input consumed by [`crate::runtime::ui::ui_append_structured_attributes`].
973/// - `params`: Input consumed by [`crate::runtime::ui::ui_append_structured_attributes`].
974///
975/// # Returns
976///
977/// Returns `true` when the condition checked by [`crate::runtime::ui::ui_append_structured_attributes`] holds and `false` otherwise.
978///
979/// # Performance
980///
981/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_append_structured_attributes`].
982///
983/// # Examples
984///
985/// ```rust,ignore
986/// let _result = pharos_native::runtime::ui::ui_append_structured_attributes(&mut [], todo!(), todo!(), todo!());
987/// ```
988///
989/// # Related
990///
991/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
992pub fn ui_append_structured_attributes(
993 buffer: &mut String,
994 element: &Value,
995 group: &str,
996 params: &Value,
997) -> bool {
998 let Some(values) = element.get(group).and_then(Value::as_object) else {
999 return true;
1000 };
1001 for (key, value) in values {
1002 if !ui_structured_key_allowed(group, key)
1003 || !ui_append_ledger_attribute(
1004 buffer,
1005 &Value::Object(Map::from_iter([(key.clone(), value.clone())])),
1006 key,
1007 &format!("{group}-{key}"),
1008 params,
1009 group == "hx",
1010 )
1011 {
1012 return false;
1013 }
1014 }
1015 true
1016}
1017
1018/// Renders a choice group as safe option or radio markup.
1019///
1020/// # Parameters
1021///
1022/// - `_app_root`: Input consumed by [`crate::runtime::ui::ui_render_choice_options`].
1023/// - `element`: Input consumed by [`crate::runtime::ui::ui_render_choice_options`].
1024/// - `params`: Input consumed by [`crate::runtime::ui::ui_render_choice_options`].
1025/// - `group_name`: Input consumed by [`crate::runtime::ui::ui_render_choice_options`].
1026/// - `group_id`: Input consumed by [`crate::runtime::ui::ui_render_choice_options`].
1027/// - `radio_mode`: Input consumed by [`crate::runtime::ui::ui_render_choice_options`].
1028///
1029/// # Returns
1030///
1031/// Returns [`Some`] when [`crate::runtime::ui::ui_render_choice_options`] can produce a value and [`None`] otherwise.
1032///
1033/// # Performance
1034///
1035/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_render_choice_options`].
1036///
1037/// # Examples
1038///
1039/// ```rust,ignore
1040/// let _result = pharos_native::runtime::ui::ui_render_choice_options(todo!(), todo!(), todo!(), todo!(), todo!(), todo!());
1041/// ```
1042///
1043/// # Related
1044///
1045/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
1046pub fn ui_render_choice_options(
1047 _app_root: &str,
1048 element: &Value,
1049 params: &Value,
1050 group_name: &str,
1051 group_id: &str,
1052 radio_mode: bool,
1053) -> Option<String> {
1054 let options = element.get("options")?.as_array()?;
1055 let selected = ui_resolved_string(element.get("value"), params);
1056 let mut output = String::new();
1057 for option in options {
1058 let value = ui_resolved_string(option.get("value"), params);
1059 let label = ui_resolved_string(option.get("label"), params);
1060 if radio_mode {
1061 output.push_str(&format!(
1062 "<label><input type=\"radio\" name=\"{}\" value=\"{}\"{}> {}</label>",
1063 html_escape(group_name),
1064 html_escape(&value),
1065 if value == selected { " checked" } else { "" },
1066 html_escape(&label)
1067 ));
1068 } else {
1069 output.push_str(&format!(
1070 "<option value=\"{}\"{}>{}</option>",
1071 html_escape(&value),
1072 if value == selected { " selected" } else { "" },
1073 html_escape(&label)
1074 ));
1075 }
1076 }
1077 (!group_id.is_empty()).then_some(output)
1078}
1079
1080/// Renders a button row from a declaration.
1081///
1082/// # Parameters
1083///
1084/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_render_button_row`].
1085/// - `element`: Input consumed by [`crate::runtime::ui::ui_render_button_row`].
1086/// - `params`: Input consumed by [`crate::runtime::ui::ui_render_button_row`].
1087/// - `group_id`: Input consumed by [`crate::runtime::ui::ui_render_button_row`].
1088/// - `class_name`: Input consumed by [`crate::runtime::ui::ui_render_button_row`].
1089///
1090/// # Returns
1091///
1092/// Returns [`Some`] when [`crate::runtime::ui::ui_render_button_row`] can produce a value and [`None`] otherwise.
1093///
1094/// # Performance
1095///
1096/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_render_button_row`].
1097///
1098/// # Examples
1099///
1100/// ```rust,ignore
1101/// let _result = pharos_native::runtime::ui::ui_render_button_row(std::path::Path::new("apps/dev_docs"), todo!(), todo!(), todo!(), todo!());
1102/// ```
1103///
1104/// # Related
1105///
1106/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
1107pub fn ui_render_button_row(
1108 app_root: &str,
1109 element: &Value,
1110 params: &Value,
1111 group_id: &str,
1112 class_name: &str,
1113) -> Option<String> {
1114 let buttons = element.get("buttons")?.as_array()?;
1115 let mut output = format!(
1116 "<div id=\"{}\" class=\"{}\">",
1117 html_escape(group_id),
1118 html_escape(class_name)
1119 );
1120 for button in buttons {
1121 let label = ui_resolved_string(button.get("label"), params);
1122 let path = ui_resolved_string(button.get("path"), params);
1123 if !ui_event_literal_allowed(&path) {
1124 return None;
1125 }
1126 output.push_str(&format!(
1127 "<a href=\"{}\">{}</a>",
1128 html_escape(&path),
1129 html_escape(&label)
1130 ));
1131 }
1132 output.push_str("</div>");
1133 let _ = app_root;
1134 Some(output)
1135}
1136
1137/// Renders one complete declaration using the canonical UI contract.
1138///
1139/// # Parameters
1140///
1141/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_render_declaration_html`].
1142/// - `element`: Input consumed by [`crate::runtime::ui::ui_render_declaration_html`].
1143/// - `params`: Input consumed by [`crate::runtime::ui::ui_render_declaration_html`].
1144///
1145/// # Returns
1146///
1147/// Returns [`Some`] when [`crate::runtime::ui::ui_render_declaration_html`] can produce a value and [`None`] otherwise.
1148///
1149/// # Examples
1150///
1151/// ```rust,ignore
1152/// let _result = pharos_native::runtime::ui::ui_render_declaration_html(std::path::Path::new("apps/dev_docs"), todo!(), todo!());
1153/// ```
1154///
1155/// # Related
1156///
1157/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
1158pub fn ui_render_declaration_html(
1159 app_root: &str,
1160 element: &Value,
1161 params: &Value,
1162) -> Option<String> {
1163 let element_type = element
1164 .get("element_type")
1165 .or_else(|| element.get("type"))
1166 .and_then(Value::as_str)?;
1167 let id = element.get("id").and_then(Value::as_str)?;
1168 if !ui_identifier_valid(id) {
1169 return None;
1170 }
1171 if element_type == "button_row" {
1172 return ui_render_button_row(
1173 app_root,
1174 element,
1175 params,
1176 id,
1177 element
1178 .get("class")
1179 .and_then(Value::as_str)
1180 .unwrap_or("ui-button-row"),
1181 );
1182 }
1183 let name = element.get("name").and_then(Value::as_str).unwrap_or(id);
1184 let label = ui_resolved_string(element.get("label"), params);
1185 let value = ui_resolved_string(element.get("value"), params);
1186 let input_type = element
1187 .get("input_type")
1188 .and_then(Value::as_str)
1189 .unwrap_or("text");
1190 if element_type == "input" && !ui_input_type_allowed(input_type) {
1191 return None;
1192 }
1193 let mut attrs = format!(
1194 " id=\"{}\" name=\"{}\" type=\"{}\" value=\"{}\"",
1195 html_escape(id),
1196 html_escape(name),
1197 html_escape(input_type),
1198 html_escape(&value)
1199 );
1200 let mut attr_buffer = String::new();
1201 if !ui_append_structured_attributes(&mut attr_buffer, element, "data", params)
1202 || !ui_append_structured_attributes(&mut attr_buffer, element, "hx", params)
1203 {
1204 return None;
1205 }
1206 attrs.push_str(&attr_buffer);
1207 Some(format!(
1208 "<label for=\"{}\">{}</label><input{}>",
1209 html_escape(id),
1210 html_escape(&label),
1211 attrs
1212 ))
1213}
1214
1215/// Loads a UI ledger JSON document from an app root.
1216///
1217/// # Parameters
1218///
1219/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_load_ledger`].
1220///
1221/// # Returns
1222///
1223/// Returns [`Some`] when [`crate::runtime::ui::ui_load_ledger`] can produce a value and [`None`] otherwise.
1224///
1225/// # Performance
1226///
1227/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_load_ledger`].
1228///
1229/// # Examples
1230///
1231/// ```rust,ignore
1232/// let _result = pharos_native::runtime::ui::ui_load_ledger(std::path::Path::new("apps/dev_docs"));
1233/// ```
1234///
1235/// # Related
1236///
1237/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
1238pub fn ui_load_ledger(app_root: &str) -> Option<Value> {
1239 let path = PathBuf::from(app_root).join("data/contracts/canonical-ui.json");
1240 serde_json::from_str(&fs::read_to_string(path).ok()?).ok()
1241}
1242
1243/// Computes a validated placeholder identifier.
1244///
1245/// # Parameters
1246///
1247/// - `name`: Input consumed by [`crate::runtime::ui::ui_placeholder_id`].
1248///
1249/// # Returns
1250///
1251/// Returns [`Some`] when [`crate::runtime::ui::ui_placeholder_id`] can produce a value and [`None`] otherwise.
1252///
1253/// # Examples
1254///
1255/// ```rust,ignore
1256/// let _result = pharos_native::runtime::ui::ui_placeholder_id("name");
1257/// ```
1258///
1259/// # Related
1260///
1261/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
1262pub fn ui_placeholder_id(name: &str) -> Option<String> {
1263 let name = name.trim();
1264 ui_identifier_valid(name).then(|| name.to_owned())
1265}
1266
1267/// Renders a page template after ledger declarations are resolved.
1268///
1269/// # Parameters
1270///
1271/// - `app_root`: Input consumed by [`crate::runtime::ui::ui_render_page_file`].
1272/// - `template_file`: Input consumed by [`crate::runtime::ui::ui_render_page_file`].
1273/// - `page_params`: Input consumed by [`crate::runtime::ui::ui_render_page_file`].
1274///
1275/// # Returns
1276///
1277/// Returns [`Some`] when [`crate::runtime::ui::ui_render_page_file`] can produce a value and [`None`] otherwise.
1278///
1279/// # Performance
1280///
1281/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_render_page_file`].
1282///
1283/// # Examples
1284///
1285/// ```rust,ignore
1286/// let _result = pharos_native::runtime::ui::ui_render_page_file(std::path::Path::new("apps/dev_docs"), std::path::Path::new("template.html"), todo!());
1287/// ```
1288///
1289/// # Related
1290///
1291/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
1292pub fn ui_render_page_file(
1293 app_root: &str,
1294 template_file: &str,
1295 page_params: &Value,
1296) -> Option<String> {
1297 let template_path = ui_path(app_root, template_file)?;
1298 let template = fs::read_to_string(template_path).ok()?;
1299 ui_render_mutable_params(&template, page_params)
1300}
1301
1302/// Builds the validated attribute string for one UI declaration.
1303///
1304/// # Parameters
1305///
1306/// - `element`: Input consumed by [`crate::runtime::ui::ui_declaration_attributes`].
1307/// - `params`: Input consumed by [`crate::runtime::ui::ui_declaration_attributes`].
1308///
1309/// # Returns
1310///
1311/// Returns [`Some`] when [`crate::runtime::ui::ui_declaration_attributes`] can produce a value and [`None`] otherwise.
1312///
1313/// # Performance
1314///
1315/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::runtime::ui::ui_declaration_attributes`].
1316///
1317/// # Examples
1318///
1319/// ```rust,ignore
1320/// let _result = pharos_native::runtime::ui::ui_declaration_attributes(todo!(), todo!());
1321/// ```
1322///
1323/// # Related
1324///
1325/// - [`crate::runtime::ui`] for adjacent helpers that share the same runtime surface.
1326pub fn ui_declaration_attributes(element: &Value, params: &Value) -> Option<String> {
1327 let mut output = String::new();
1328 for (key, attribute, event_policy) in [
1329 ("placeholder", "placeholder", false),
1330 ("aria_label", "aria-label", false),
1331 ("hx_get", "hx-get", true),
1332 ("hx_post", "hx-post", true),
1333 ] {
1334 if !ui_append_ledger_attribute(&mut output, element, key, attribute, params, event_policy) {
1335 return None;
1336 }
1337 }
1338 if !ui_append_boolean_attribute(
1339 &mut output,
1340 "required",
1341 ui_resolved_bool(element.get("required"), params),
1342 ) || !ui_append_boolean_attribute(
1343 &mut output,
1344 "checked",
1345 ui_resolved_bool(element.get("checked"), params),
1346 ) {
1347 return None;
1348 }
1349 Some(output)
1350}