1use tablo_ui::field_group as ui_field_group;
38use toasty::stmt::Path;
39use topcoat::{
40 Result,
41 context::Cx,
42 runtime::{Event, signal},
43 view::*,
44};
45
46use super::{
47 Schema,
48 condition::Condition,
49 fields::Field,
50 lenses::{self, Binding},
51 tree::{Node, Source},
52};
53use crate::{
54 Text,
55 topcoat_compat::async_page,
56 values::{FieldError, FormField, FormValues},
57};
58
59pub trait EmbeddedForm: Sized {
62 fn write_form<M>(&self, parent: impl Into<Path<M, Self>>, out: &mut FormValues)
65 where
66 M: toasty::schema::Model,
67 {
68 let schema = Self::build_schema(parent.into());
69 self.write_node(schema.embedded_root(), out);
70 }
71
72 fn read_form<M>(
80 parent: impl Into<Path<M, Self>>,
81 values: &FormValues,
82 ) -> std::result::Result<Self, Vec<FieldError>>
83 where
84 M: toasty::schema::Model,
85 {
86 let schema = Self::build_schema(parent.into());
87 Self::read_node(schema.embedded_root(), values)
88 }
89
90 #[doc(hidden)]
92 fn build_schema<M>(parent: Path<M, Self>) -> Schema
93 where
94 M: toasty::schema::Model;
95
96 #[doc(hidden)]
97 fn write_node(&self, node: &Embedded, out: &mut FormValues);
98
99 #[doc(hidden)]
100 fn read_node(
101 node: &Embedded,
102 values: &FormValues,
103 ) -> std::result::Result<Self, Vec<FieldError>>;
104}
105
106#[doc(hidden)]
108#[derive(Debug)]
109pub struct Embedded {
110 shape: Shape,
111}
112
113#[derive(Debug)]
114enum Shape {
115 Struct(Vec<Member>),
116 Enum(EnumNode),
117}
118
119#[derive(Debug)]
120struct EnumNode {
121 key: String,
122 discriminant: usize,
123 shared: Vec<usize>,
124 variants: Vec<Variant>,
125}
126
127#[derive(Debug)]
128struct Variant {
129 value: String,
130 members: Vec<Member>,
131}
132
133#[derive(Debug)]
134enum Member {
135 Leaf { key: String, field: Option<usize> },
136 Nested(Embedded),
137}
138
139impl Embedded {
140 fn members(&self, variant: Option<usize>) -> &[Member] {
141 match (&self.shape, variant) {
142 (Shape::Struct(members), None) => members,
143 (Shape::Enum(e), Some(index)) => &e.variants[index].members,
144 _ => panic!("a struct member is addressed without a variant, an enum's with one"),
145 }
146 }
147
148 pub fn key(&self, variant: Option<usize>, index: usize) -> &str {
149 match &self.members(variant)[index] {
150 Member::Leaf { key, .. } => key,
151 Member::Nested(_) => panic!("member {index} is an embedded value, not a leaf"),
152 }
153 }
154
155 pub fn nested(&self, variant: Option<usize>, index: usize) -> &Embedded {
156 match &self.members(variant)[index] {
157 Member::Nested(nested) => nested,
158 Member::Leaf { .. } => panic!("member {index} is a leaf, not an embedded value"),
159 }
160 }
161
162 fn enum_node(&self) -> &EnumNode {
163 match &self.shape {
164 Shape::Enum(e) => e,
165 Shape::Struct(_) => panic!("a struct value has no variant"),
166 }
167 }
168
169 pub fn write_variant(&self, index: usize, out: &mut FormValues) {
170 let e = self.enum_node();
171 out.insert(e.key.clone(), e.variants[index].value.as_str());
172 }
173
174 pub fn variant_index(
177 &self,
178 values: &FormValues,
179 ) -> std::result::Result<usize, Vec<FieldError>> {
180 let e = self.enum_node();
181 let submitted = values.text(&e.key).map(str::trim).unwrap_or_default();
182 if submitted.is_empty() {
183 let own = |member: &Member| match member {
184 Member::Leaf { key, field } => field.is_some() && is_present(values, key),
185 Member::Nested(nested) => nested.any_present(values),
186 };
187 let inferred = e.variants.iter().position(|v| v.members.iter().any(own));
188 return Ok(inferred.unwrap_or(0));
189 }
190 e.variants
191 .iter()
192 .position(|v| v.value == submitted)
193 .ok_or_else(|| {
194 vec![FieldError::invalid(
195 e.key.clone(),
196 format!("`{submitted}` is not a valid variant"),
197 )]
198 })
199 }
200
201 fn any_present(&self, values: &FormValues) -> bool {
203 let member = |member: &Member| match member {
204 Member::Leaf { key, .. } => is_present(values, key),
205 Member::Nested(nested) => nested.any_present(values),
206 };
207 match &self.shape {
208 Shape::Struct(members) => members.iter().any(member),
209 Shape::Enum(e) => {
210 is_present(values, &e.key)
211 || e.variants.iter().any(|v| v.members.iter().any(member))
212 }
213 }
214 }
215
216 pub(crate) fn keys(&self) -> Vec<String> {
218 let mut out = Vec::new();
219 self.collect_keys(&mut out);
220 out
221 }
222
223 fn collect_keys(&self, out: &mut Vec<String>) {
224 let push = |members: &[Member], out: &mut Vec<String>| {
225 for member in members {
226 match member {
227 Member::Leaf { key, .. } if !out.contains(key) => out.push(key.clone()),
228 Member::Leaf { .. } => {}
229 Member::Nested(nested) => nested.collect_keys(out),
230 }
231 }
232 };
233 match &self.shape {
234 Shape::Struct(members) => push(members, out),
235 Shape::Enum(e) => {
236 out.push(e.key.clone());
237 for variant in &e.variants {
238 push(&variant.members, out);
239 }
240 }
241 }
242 }
243
244 pub(crate) fn offset(&mut self, by: usize) {
245 let members = |members: &mut Vec<Member>| {
246 for member in members {
247 match member {
248 Member::Leaf { field, .. } => {
249 if let Some(index) = field {
250 *index += by;
251 }
252 }
253 Member::Nested(nested) => nested.offset(by),
254 }
255 }
256 };
257 match &mut self.shape {
258 Shape::Struct(list) => members(list),
259 Shape::Enum(e) => {
260 e.discriminant += by;
261 for index in &mut e.shared {
262 *index += by;
263 }
264 for variant in &mut e.variants {
265 members(&mut variant.members);
266 }
267 }
268 }
269 }
270
271 pub(crate) fn visit_fields(&self, f: &mut impl FnMut(usize)) {
273 fn members(members: &[Member], f: &mut impl FnMut(usize)) {
274 for member in members {
275 match member {
276 Member::Leaf {
277 field: Some(index), ..
278 } => f(*index),
279 Member::Leaf { field: None, .. } => {}
280 Member::Nested(nested) => nested.visit_fields(f),
281 }
282 }
283 }
284 match &self.shape {
285 Shape::Struct(list) => members(list, f),
286 Shape::Enum(e) => {
287 f(e.discriminant);
288 for index in &e.shared {
289 f(*index);
290 }
291 for variant in &e.variants {
292 members(&variant.members, f);
293 }
294 }
295 }
296 }
297
298 pub(crate) fn hidden_fields(&self, values: &FormValues, out: &mut Vec<usize>) {
301 let nested = |members: &[Member], out: &mut Vec<usize>| {
302 for member in members {
303 if let Member::Nested(nested) = member {
304 nested.hidden_fields(values, out);
305 }
306 }
307 };
308 match &self.shape {
309 Shape::Struct(members) => nested(members, out),
310 Shape::Enum(e) => {
311 let chosen = values.text(&e.key).map(str::trim).unwrap_or_default();
312 for variant in &e.variants {
313 if !chosen.is_empty() && chosen != variant.value {
314 for member in &variant.members {
315 match member {
316 Member::Leaf {
317 field: Some(index), ..
318 } => out.push(*index),
319 Member::Leaf { field: None, .. } => {}
320 Member::Nested(nested) => {
321 nested.visit_fields(&mut |index| out.push(index))
322 }
323 }
324 }
325 } else {
326 nested(&variant.members, out);
327 }
328 }
329 }
330 }
331 }
332
333 pub(crate) async fn render<'a>(
335 &self,
336 cx: &'a Cx,
337 fields: &[Field],
338 source: &Source<'_>,
339 ) -> Result<BoxView<'a>> {
340 match &self.shape {
341 Shape::Struct(members) => render_members(cx, members, fields, source).await,
342 Shape::Enum(e) => {
343 let discriminant = Node::Field(e.discriminant)
344 .render(cx, fields, source)
345 .await?;
346 let stored = source.text(&e.key).map(str::trim);
347 let mut shared = Vec::with_capacity(e.shared.len());
348 for index in &e.shared {
349 shared.push(Node::Field(*index).render(cx, fields, source).await?);
350 }
351 let mut groups = Vec::with_capacity(e.variants.len());
352 for variant in &e.variants {
353 let members = render_members(cx, &variant.members, fields, source).await?;
354 groups.push((variant.value.clone(), members));
355 }
356 let key = e.key.clone();
357 let stored = stored.unwrap_or_default().to_string();
358 let page = topcoat::context::try_request_context::<http::request::Parts>(cx)
366 .map(|parts| parts.uri.path().to_string())
367 .unwrap_or_default();
368 let scope = source.scope().to_string();
369 Ok(async_page(async move {
370 let variant = signal(
371 &cx.keyed(("tablo-variant", page, scope, key.as_str())),
372 move || stored,
373 );
374 let chosen = variant.clone();
375 let groups: Vec<BoxView<'a>> = groups
376 .into_iter()
377 .map(|(value, members)| {
378 let (shown, enabled) = (variant.clone(), variant.clone());
379 let named = value.clone();
380 view! {
381 cx =>
382 <fieldset
383 class="contents"
384 :disabled=$(enabled.get() != named)
385 >
386 ui_field_group(
387 attrs: attributes! {
388 data-variant=(value.clone())
389 :hidden=$(shown.get() != value)
390 },
391 (members)
392 )
393 </fieldset>
394 }
395 .boxed()
396 })
397 .collect();
398 Ok(view! {
399 cx =>
400 <div
401 class="contents"
402 @change=$(|e: Event| chosen.set(e.target.value))
403 >
404 (discriminant)
405 </div>
406 for v in shared {
407 (v)
408 }
409 for g in groups {
410 (g)
411 }
412 })
413 }))
414 }
415 }
416 }
417}
418
419impl Embedded {
420 pub(crate) fn display<'a>(
423 &self,
424 cx: &'a Cx,
425 fields: &[Field],
426 values: &FormValues,
427 ) -> BoxView<'a> {
428 let views: Vec<BoxView<'a>> = self
429 .shown(fields, values)
430 .into_iter()
431 .map(|index| {
432 let field = &fields[index];
433 field.display(cx, values.text(field.name()).unwrap_or_default())
434 })
435 .collect();
436 view! {
437 cx =>
438 for v in views {
439 (v)
440 }
441 }
442 .boxed()
443 }
444
445 pub(crate) fn shown(&self, fields: &[Field], values: &FormValues) -> Vec<usize> {
449 let mut out = Vec::new();
450 self.shown_into(fields, values, &mut out);
451 out
452 }
453
454 fn shown_into(&self, fields: &[Field], values: &FormValues, out: &mut Vec<usize>) {
455 let members = |members: &[Member], out: &mut Vec<usize>| {
456 for member in members {
457 match member {
458 Member::Leaf {
459 field: Some(index), ..
460 } => out.push(*index),
461 Member::Leaf { field: None, .. } => {}
462 Member::Nested(nested) => nested.shown_into(fields, values, out),
463 }
464 }
465 };
466 match &self.shape {
467 Shape::Struct(list) => members(list, out),
468 Shape::Enum(e) => {
469 let stored = values.text(&e.key).map(str::trim);
470 let Some(variant) = e
471 .variants
472 .iter()
473 .find(|variant| stored == Some(variant.value.as_str()))
474 else {
475 return;
476 };
477 out.push(e.discriminant);
478 out.extend(e.shared.iter().copied().filter(|index| {
481 let key = fields[*index].name();
482 variant
483 .members
484 .iter()
485 .any(|member| matches!(member, Member::Leaf { key: k, .. } if k == key))
486 }));
487 members(&variant.members, out);
488 }
489 }
490 }
491}
492
493async fn render_members<'a>(
495 cx: &'a Cx,
496 members: &[Member],
497 fields: &[Field],
498 source: &Source<'_>,
499) -> Result<BoxView<'a>> {
500 let mut views = Vec::with_capacity(members.len());
501 for member in members {
502 match member {
503 Member::Leaf {
504 field: Some(index), ..
505 } => views.push(Node::Field(*index).render(cx, fields, source).await?),
506 Member::Leaf { field: None, .. } => {}
507 Member::Nested(nested) => {
508 views.push(Box::pin(nested.render(cx, fields, source)).await?)
509 }
510 }
511 }
512 Ok(view! {
513 cx =>
514 for v in views {
515 (v)
516 }
517 }
518 .boxed())
519}
520
521fn is_present(values: &FormValues, key: &str) -> bool {
522 values
523 .text(key)
524 .is_some_and(|value| !value.trim().is_empty())
525}
526
527#[doc(hidden)]
530pub struct EmbeddedBuilder {
531 fields: Vec<Field>,
532 shape: Shape,
533 variant: Option<usize>,
534 refused: bool,
537}
538
539impl EmbeddedBuilder {
540 pub fn structure() -> Self {
541 Self {
542 fields: Vec::new(),
543 shape: Shape::Struct(Vec::new()),
544 variant: None,
545 refused: false,
546 }
547 }
548
549 pub fn enumeration<M, T>(parent: &Path<M, T>) -> Self
554 where
555 M: toasty::schema::Model,
556 {
557 let (binding, variants, refused) = match lenses::resolve_enum(parent) {
558 Ok(Some(shape)) => {
559 let label = Text::field(&shape.discriminant);
560 (
561 Binding::named(shape.discriminant, label),
562 shape.variants,
563 false,
564 )
565 }
566 Ok(None) => panic!(
567 "{} is not an embedded enum of {}",
568 std::any::type_name::<T>(),
569 std::any::type_name::<M>()
570 ),
571 Err(error) => (Binding::refused(parent, error), Vec::new(), true),
572 };
573 let discriminant = Field::discriminant(binding, variants.clone());
574 Self {
575 shape: Shape::Enum(EnumNode {
576 key: discriminant.name().to_string(),
577 discriminant: 0,
578 shared: Vec::new(),
579 variants: variants
580 .into_iter()
581 .map(|(value, _)| Variant {
582 value,
583 members: Vec::new(),
584 })
585 .collect(),
586 }),
587 fields: vec![discriminant],
588 variant: None,
589 refused,
590 }
591 }
592
593 pub fn variant(&mut self) {
595 let next = self.variant.map_or(0, |index| index + 1);
596 let Shape::Enum(e) = &mut self.shape else {
597 panic!("a struct value has no variant");
598 };
599 if self.refused {
600 e.variants.push(Variant {
601 value: next.to_string(),
602 members: Vec::new(),
603 });
604 }
605 assert!(
606 next < e.variants.len(),
607 "the type declares more variants than the app schema"
608 );
609 self.variant = Some(next);
610 }
611
612 fn members_mut(&mut self) -> &mut Vec<Member> {
613 match (&mut self.shape, self.variant) {
614 (Shape::Struct(members), _) => members,
615 (Shape::Enum(e), Some(index)) => &mut e.variants[index].members,
616 (Shape::Enum(_), None) => panic!("an enum member needs `variant()` first"),
617 }
618 }
619
620 pub fn leaf(&mut self, field: impl Into<Field>, required: bool) {
622 let mut field = field.into();
623 field.set_required(required);
624 let key = field.name().to_string();
625 let index = self.fields.len();
626 self.fields.push(field);
627 self.members_mut().push(Member::Leaf {
628 key,
629 field: Some(index),
630 });
631 }
632
633 pub fn shared(&mut self, field: impl Into<Field>, required: bool) {
636 let mut field = field.into();
637 field.set_required(required);
638 let key = field.name().to_string();
639 let Shape::Enum(e) = &mut self.shape else {
640 panic!("a struct value has no shared column");
641 };
642 if !e
643 .shared
644 .iter()
645 .any(|index| self.fields[*index].name() == key)
646 {
647 e.shared.push(self.fields.len());
648 self.fields.push(field);
649 }
650 self.members_mut().push(Member::Leaf { key, field: None });
651 }
652
653 pub fn nested(&mut self, schema: Schema) {
655 let Schema { nodes, fields, .. } = schema;
656 let Ok([Node::Embedded(mut nested)]) = <[Node; 1]>::try_from(nodes) else {
657 panic!("a nested value's schema is its one embedded node");
658 };
659 nested.offset(self.fields.len());
660 self.fields.extend(fields);
661 self.members_mut().push(Member::Nested(*nested));
662 }
663
664 pub fn finish(mut self) -> Schema {
666 if let Shape::Enum(e) = &self.shape {
667 assert_eq!(
668 self.variant.map_or(0, |index| index + 1),
669 e.variants.len(),
670 "the type declares fewer variants than the app schema"
671 );
672 for index in &e.shared {
677 let key = self.fields[*index].name().to_string();
678 let declares = |variant: &Variant| {
679 variant
680 .members
681 .iter()
682 .any(|member| matches!(member, Member::Leaf { key: k, .. } if *k == key))
683 };
684 let declaring: Vec<String> = e
685 .variants
686 .iter()
687 .filter(|variant| declares(variant))
688 .map(|variant| variant.value.clone())
689 .collect();
690 if declaring.len() < e.variants.len() {
691 assert!(
692 self.fields[*index].condition().is_none(),
693 "a shared column's visibility comes from the variants that declare it"
694 );
695 let values = std::iter::once(String::new()).chain(declaring);
696 self.fields[*index].set_condition(Condition::watching(e.key.clone(), values));
697 }
698 }
699 }
700 Schema::from_parts(
701 vec![Node::Embedded(Box::new(Embedded { shape: self.shape }))],
702 self.fields,
703 )
704 }
705}
706
707#[doc(hidden)]
710pub fn embedded_field<M, T, K>(
711 parent: impl Into<Path<M, T>>,
712 field: K,
713 name: &'static str,
714) -> FormField<K>
715where
716 M: toasty::schema::Model,
717 T: EmbeddedForm,
718{
719 let schema = T::build_schema(parent.into());
720 FormField {
721 field,
722 name,
723 keys: schema.embedded_root().keys(),
724 required: schema
725 .fields()
726 .filter(|field| field.is_required())
727 .map(|field| field.name().to_string())
728 .collect(),
729 }
730}
731
732#[doc(hidden)]
734pub fn take_leaf<T>(
735 result: std::result::Result<T, FieldError>,
736 errors: &mut Vec<FieldError>,
737) -> Option<T> {
738 result.map_err(|error| errors.push(error)).ok()
739}
740
741#[doc(hidden)]
742pub fn take_value<T>(
743 result: std::result::Result<T, Vec<FieldError>>,
744 errors: &mut Vec<FieldError>,
745) -> Option<T> {
746 result.map_err(|nested| errors.extend(nested)).ok()
747}
748
749#[cfg(test)]
750mod tests;