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_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 <fieldset
382 class="contents"
383 :disabled=$(enabled.get() != named)
384 >
385 ui_field_group(
386 attrs: attributes! {
387 data-variant=(value.clone())
388 :hidden=$(shown.get() != value)
389 },
390 (members)
391 )
392 </fieldset>
393 }
394 .boxed()
395 })
396 .collect();
397 Ok(view! {
398 <div
399 class="contents"
400 @change=$(|e: Event| chosen.set(e.target.value))
401 >
402 (discriminant)
403 </div>
404 for v in shared {
405 (v)
406 }
407 for g in groups {
408 (g)
409 }
410 })
411 }))
412 }
413 }
414 }
415}
416
417impl Embedded {
418 pub(crate) fn display<'a>(
421 &self,
422 cx: &'a Cx,
423 fields: &[Field],
424 values: &FormValues,
425 ) -> BoxView<'a> {
426 let views: Vec<BoxView<'a>> = self
427 .shown(fields, values)
428 .into_iter()
429 .map(|index| {
430 let field = &fields[index];
431 field.display(cx, values.text(field.name()).unwrap_or_default())
432 })
433 .collect();
434 view! {
435 for v in views {
436 (v)
437 }
438 }
439 .boxed()
440 }
441
442 pub(crate) fn shown(&self, fields: &[Field], values: &FormValues) -> Vec<usize> {
446 let mut out = Vec::new();
447 self.shown_into(fields, values, &mut out);
448 out
449 }
450
451 fn shown_into(&self, fields: &[Field], values: &FormValues, out: &mut Vec<usize>) {
452 let members = |members: &[Member], out: &mut Vec<usize>| {
453 for member in members {
454 match member {
455 Member::Leaf {
456 field: Some(index), ..
457 } => out.push(*index),
458 Member::Leaf { field: None, .. } => {}
459 Member::Nested(nested) => nested.shown_into(fields, values, out),
460 }
461 }
462 };
463 match &self.shape {
464 Shape::Struct(list) => members(list, out),
465 Shape::Enum(e) => {
466 let stored = values.text(&e.key).map(str::trim);
467 let Some(variant) = e
468 .variants
469 .iter()
470 .find(|variant| stored == Some(variant.value.as_str()))
471 else {
472 return;
473 };
474 out.push(e.discriminant);
475 out.extend(e.shared.iter().copied().filter(|index| {
478 let key = fields[*index].name();
479 variant
480 .members
481 .iter()
482 .any(|member| matches!(member, Member::Leaf { key: k, .. } if k == key))
483 }));
484 members(&variant.members, out);
485 }
486 }
487 }
488}
489
490async fn render_members<'a>(
492 cx: &'a Cx,
493 members: &[Member],
494 fields: &[Field],
495 source: &Source<'_>,
496) -> Result<BoxView<'a>> {
497 let mut views = Vec::with_capacity(members.len());
498 for member in members {
499 match member {
500 Member::Leaf {
501 field: Some(index), ..
502 } => views.push(Node::Field(*index).render(cx, fields, source).await?),
503 Member::Leaf { field: None, .. } => {}
504 Member::Nested(nested) => {
505 views.push(Box::pin(nested.render(cx, fields, source)).await?)
506 }
507 }
508 }
509 Ok(view! {
510 for v in views {
511 (v)
512 }
513 }
514 .boxed())
515}
516
517fn is_present(values: &FormValues, key: &str) -> bool {
518 values
519 .text(key)
520 .is_some_and(|value| !value.trim().is_empty())
521}
522
523#[doc(hidden)]
526pub struct EmbeddedBuilder {
527 fields: Vec<Field>,
528 shape: Shape,
529 variant: Option<usize>,
530 refused: bool,
533}
534
535impl EmbeddedBuilder {
536 pub fn structure() -> Self {
537 Self {
538 fields: Vec::new(),
539 shape: Shape::Struct(Vec::new()),
540 variant: None,
541 refused: false,
542 }
543 }
544
545 pub fn enumeration<M, T>(parent: &Path<M, T>) -> Self
550 where
551 M: toasty::schema::Model,
552 {
553 let (binding, variants, refused) = match lenses::resolve_enum(parent) {
554 Ok(Some(shape)) => {
555 let label = Text::field(&shape.discriminant);
556 (
557 Binding::named(shape.discriminant, label),
558 shape.variants,
559 false,
560 )
561 }
562 Ok(None) => panic!(
563 "{} is not an embedded enum of {}",
564 std::any::type_name::<T>(),
565 std::any::type_name::<M>()
566 ),
567 Err(error) => (Binding::refused(parent, error), Vec::new(), true),
568 };
569 let discriminant = Field::discriminant(binding, variants.clone());
570 Self {
571 shape: Shape::Enum(EnumNode {
572 key: discriminant.name().to_string(),
573 discriminant: 0,
574 shared: Vec::new(),
575 variants: variants
576 .into_iter()
577 .map(|(value, _)| Variant {
578 value,
579 members: Vec::new(),
580 })
581 .collect(),
582 }),
583 fields: vec![discriminant],
584 variant: None,
585 refused,
586 }
587 }
588
589 pub fn variant(&mut self) {
591 let next = self.variant.map_or(0, |index| index + 1);
592 let Shape::Enum(e) = &mut self.shape else {
593 panic!("a struct value has no variant");
594 };
595 if self.refused {
596 e.variants.push(Variant {
597 value: next.to_string(),
598 members: Vec::new(),
599 });
600 }
601 assert!(
602 next < e.variants.len(),
603 "the type declares more variants than the app schema"
604 );
605 self.variant = Some(next);
606 }
607
608 fn members_mut(&mut self) -> &mut Vec<Member> {
609 match (&mut self.shape, self.variant) {
610 (Shape::Struct(members), _) => members,
611 (Shape::Enum(e), Some(index)) => &mut e.variants[index].members,
612 (Shape::Enum(_), None) => panic!("an enum member needs `variant()` first"),
613 }
614 }
615
616 pub fn leaf(&mut self, field: impl Into<Field>, required: bool) {
618 let mut field = field.into();
619 field.set_required(required);
620 let key = field.name().to_string();
621 let index = self.fields.len();
622 self.fields.push(field);
623 self.members_mut().push(Member::Leaf {
624 key,
625 field: Some(index),
626 });
627 }
628
629 pub fn shared(&mut self, field: impl Into<Field>, required: bool) {
632 let mut field = field.into();
633 field.set_required(required);
634 let key = field.name().to_string();
635 let Shape::Enum(e) = &mut self.shape else {
636 panic!("a struct value has no shared column");
637 };
638 if !e
639 .shared
640 .iter()
641 .any(|index| self.fields[*index].name() == key)
642 {
643 e.shared.push(self.fields.len());
644 self.fields.push(field);
645 }
646 self.members_mut().push(Member::Leaf { key, field: None });
647 }
648
649 pub fn nested(&mut self, schema: Schema) {
651 let Schema { nodes, fields, .. } = schema;
652 let Ok([Node::Embedded(mut nested)]) = <[Node; 1]>::try_from(nodes) else {
653 panic!("a nested value's schema is its one embedded node");
654 };
655 nested.offset(self.fields.len());
656 self.fields.extend(fields);
657 self.members_mut().push(Member::Nested(*nested));
658 }
659
660 pub fn finish(mut self) -> Schema {
662 if let Shape::Enum(e) = &self.shape {
663 assert_eq!(
664 self.variant.map_or(0, |index| index + 1),
665 e.variants.len(),
666 "the type declares fewer variants than the app schema"
667 );
668 for index in &e.shared {
673 let key = self.fields[*index].name().to_string();
674 let declares = |variant: &Variant| {
675 variant
676 .members
677 .iter()
678 .any(|member| matches!(member, Member::Leaf { key: k, .. } if *k == key))
679 };
680 let declaring: Vec<String> = e
681 .variants
682 .iter()
683 .filter(|variant| declares(variant))
684 .map(|variant| variant.value.clone())
685 .collect();
686 if declaring.len() < e.variants.len() {
687 assert!(
688 self.fields[*index].condition().is_none(),
689 "a shared column's visibility comes from the variants that declare it"
690 );
691 let values = std::iter::once(String::new()).chain(declaring);
692 self.fields[*index].set_condition(Condition::watching(e.key.clone(), values));
693 }
694 }
695 }
696 Schema::from_parts(
697 vec![Node::Embedded(Box::new(Embedded { shape: self.shape }))],
698 self.fields,
699 )
700 }
701}
702
703#[doc(hidden)]
706pub fn embedded_field<M, T, K>(
707 parent: impl Into<Path<M, T>>,
708 field: K,
709 name: &'static str,
710) -> FormField<K>
711where
712 M: toasty::schema::Model,
713 T: EmbeddedForm,
714{
715 let schema = T::build_schema(parent.into());
716 FormField {
717 field,
718 name,
719 keys: schema.embedded_root().keys(),
720 required: schema
721 .fields()
722 .filter(|field| field.is_required())
723 .map(|field| field.name().to_string())
724 .collect(),
725 }
726}
727
728#[doc(hidden)]
730pub fn take_leaf<T>(
731 result: std::result::Result<T, FieldError>,
732 errors: &mut Vec<FieldError>,
733) -> Option<T> {
734 result.map_err(|error| errors.push(error)).ok()
735}
736
737#[doc(hidden)]
738pub fn take_value<T>(
739 result: std::result::Result<T, Vec<FieldError>>,
740 errors: &mut Vec<FieldError>,
741) -> Option<T> {
742 result.map_err(|nested| errors.extend(nested)).ok()
743}
744
745#[cfg(test)]
746mod tests;