Skip to main content

tablo_test/protocol/
choice_create.rs

1//! Reads a creatable choice's link to the related create page, and the page a create framed by
2//! its dialog lands on.
3
4use super::dom::{attr_value, open_tags};
5
6/// A creatable choice's link: the select it creates a record for, and the create page it opens.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct CreateOptionLink {
9    /// The DOM id of the select whose choice the created record becomes.
10    pub select: String,
11    /// The related resource's create page.
12    pub href: String,
13}
14
15/// What the page a create framed by a creatable choice's dialog lands on offers.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Picked {
18    /// The record the choice takes: its key and its title.
19    Option {
20        /// The record's key, the option's value.
21        value: String,
22        /// The record's title, the option's label.
23        label: String,
24    },
25    /// The record was created, but the user may not choose it.
26    Unchoosable,
27}
28
29/// Each creatable choice's link on the page.
30pub fn create_option_links(html: &str) -> Vec<CreateOptionLink> {
31    let tags = open_tags(html);
32    tags.iter()
33        .filter(|(tag, _, _)| tag == "a")
34        .filter_map(|(_, attrs, _)| {
35            let dialog = attr_value(attrs, "data-create-option")?;
36            let select = tags.iter().find_map(|(tag, attrs, _)| {
37                (tag == "dialog" && attr_value(attrs, "id").as_deref() == Some(dialog.as_str()))
38                    .then(|| attr_value(attrs, "data-create-option-select"))
39                    .flatten()
40            })?;
41            Some(CreateOptionLink {
42                select,
43                href: attr_value(attrs, "href")?,
44            })
45        })
46        .collect()
47}
48
49/// What the landing page of a framed create offers, or `None` on any other page.
50pub fn picked(html: &str) -> Option<Picked> {
51    open_tags(html).into_iter().find_map(|(_, attrs, _)| {
52        if attr_value(&attrs, "data-picked-hidden").is_some() {
53            return Some(Picked::Unchoosable);
54        }
55        attr_value(&attrs, "data-picked")?;
56        Some(Picked::Option {
57            value: attr_value(&attrs, "data-picked-value")?,
58            label: attr_value(&attrs, "data-picked-label")?,
59        })
60    })
61}
62
63/// Whether the page's guarded form posts back in pick mode, so a refused save of a create framed
64/// by a creatable choice's dialog stays in the dialog.
65pub fn posts_picking(html: &str) -> bool {
66    open_tags(html).iter().any(|(tag, attrs, _)| {
67        tag == "form"
68            && attr_value(attrs, "data-form-guard").is_some()
69            && attr_value(attrs, "action").is_some_and(|action| {
70                action
71                    .split_once('?')
72                    .is_some_and(|(_, query)| query.split('&').any(|pair| pair == "pick=1"))
73            })
74    })
75}