microrm 0.6.3

Lightweight ORM using sqlite as a backend
Documentation
use super::{Autogenerate, EntityInterface, ValueRole};
use crate::schema::{
    datum::{Datum, DatumDiscriminator},
    entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor},
    relation::Relation,
};
use clap::{FromArgMatches, Subcommand};

#[derive(Debug, Clone)]
pub enum EntityKey {
    Placeholder {
        entity: &'static str,
        field: &'static str,
        role: ValueRole,
    },
    UserInput(String),
}

impl EntityKey {
    pub(crate) fn to_string_vec<EI: EntityInterface>(
        vec: &[Self],
        ctx: &EI::Context,
    ) -> Vec<String> {
        vec.iter()
            .map(|v| match v {
                EntityKey::UserInput(s) => s.to_owned(),
                EntityKey::Placeholder {
                    entity,
                    field,
                    role,
                } => EI::override_for(ctx, entity, field, *role),
            })
            .collect()
    }
}

// --------------------------------------------------------------------------
// helper functions
// --------------------------------------------------------------------------
/// iterate across the list of key parts (E::Keys) and add args for each
fn add_keys<E: Entity, EI: EntityInterface>(
    mut cmd: clap::Command,
    role: ValueRole,
) -> clap::Command {
    struct UVisitor<'a, E: Entity, EI: EntityInterface>(
        &'a mut clap::Command,
        ValueRole,
        std::marker::PhantomData<(E, EI)>,
    );
    impl<E: Entity, EI: EntityInterface> EntityPartVisitor for UVisitor<'_, E, EI> {
        type Entity = E;
        fn visit<EP: EntityPart>(&mut self) {
            if !EI::should_override(EP::Entity::entity_name(), EP::part_name(), self.1) {
                let arg = clap::Arg::new(EP::part_name())
                    .required(true)
                    .help(EP::desc());
                *self.0 = self.0.clone().arg(arg);
            }
        }
    }

    <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<E, EI>(
        &mut cmd,
        role,
        Default::default(),
    ));

    cmd
}

fn collect_keys<E: Entity, EI: EntityInterface>(
    matches: &clap::ArgMatches,
    role: ValueRole,
) -> Vec<EntityKey> {
    struct UVisitor<'a, E: Entity, EI: EntityInterface>(
        &'a clap::ArgMatches,
        &'a mut Vec<EntityKey>,
        ValueRole,
        std::marker::PhantomData<(E, EI)>,
    );
    impl<E: Entity, EI: EntityInterface> EntityPartVisitor for UVisitor<'_, E, EI> {
        type Entity = E;
        fn visit<EP: EntityPart>(&mut self) {
            if !EI::should_override(EP::Entity::entity_name(), EP::part_name(), self.2) {
                self.1.push(EntityKey::UserInput(
                    self.0
                        .get_one::<std::string::String>(EP::part_name())
                        .unwrap()
                        .clone(),
                ));
            } else {
                self.1.push(EntityKey::Placeholder {
                    entity: EP::Entity::entity_name(),
                    field: EP::part_name(),
                    role: self.2,
                });
            }
        }
    }

    let mut key_values = vec![];
    <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<E, EI>(
        matches,
        &mut key_values,
        role,
        Default::default(),
    ));
    key_values
}

#[derive(Clone, Debug)]
pub(crate) enum Verb<EI: EntityInterface> {
    Attach {
        local_keys: Vec<EntityKey>,
        relation: String,
        remote_keys: Vec<EntityKey>,
    },
    Delete(Vec<EntityKey>),
    Detach {
        local_keys: Vec<EntityKey>,
        relation: String,
        remote_keys: Vec<EntityKey>,
    },
    ListAll,
    Inspect(Vec<EntityKey>),
    Custom(EI::CustomCommand),
}

impl<EI: EntityInterface> Verb<EI> {
    fn parse_attachment(
        matches: &clap::ArgMatches,
    ) -> Result<(Vec<EntityKey>, String, Vec<EntityKey>), clap::Error> {
        let local_keys = collect_keys::<EI::Entity, EI>(matches, ValueRole::BaseTarget);

        let (subcommand, submatches) = matches
            .subcommand()
            .ok_or(clap::Error::new(clap::error::ErrorKind::MissingSubcommand))?;

        // find the relevant relation
        struct RelationFinder<'l, EI: EntityInterface> {
            subcommand: &'l str,
            submatches: &'l clap::ArgMatches,
            keys: &'l mut Vec<EntityKey>,
            _ghost: std::marker::PhantomData<(EI,)>,
        }
        impl<EI: EntityInterface> EntityPartVisitor for RelationFinder<'_, EI> {
            type Entity = EI::Entity;
            fn visit<EP: EntityPart>(&mut self) {
                if EP::part_name() != self.subcommand {
                    return;
                }

                EP::Datum::accept_entity_visitor(self);
            }
        }

        impl<EI: EntityInterface> EntityVisitor for RelationFinder<'_, EI> {
            fn visit<E: Entity>(&mut self) {
                *self.keys = collect_keys::<E, EI>(self.submatches, ValueRole::AttachmentTarget);
            }
        }

        let mut remote_keys = vec![];
        EI::Entity::accept_part_visitor(&mut RelationFinder::<EI> {
            subcommand,
            submatches,
            keys: &mut remote_keys,
            _ghost: Default::default(),
        });

        Ok((local_keys, subcommand.into(), remote_keys))
    }

    fn from_matches(parent_matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
        let (subcommand, matches) = parent_matches
            .subcommand()
            .ok_or(clap::Error::new(clap::error::ErrorKind::MissingSubcommand))?;

        Ok(match subcommand {
            "attach" => {
                let (local_keys, relation, remote_keys) = Self::parse_attachment(matches)?;
                Self::Attach {
                    local_keys,
                    relation,
                    remote_keys,
                }
            },
            "delete" => Self::Delete(collect_keys::<EI::Entity, EI>(
                matches,
                ValueRole::BaseTarget,
            )),
            "detach" => {
                let (local_keys, relation, remote_keys) = Self::parse_attachment(matches)?;
                Self::Detach {
                    local_keys,
                    relation,
                    remote_keys,
                }
            },
            "list" => Verb::ListAll,
            "inspect" => Self::Inspect(collect_keys::<EI::Entity, EI>(
                matches,
                ValueRole::BaseTarget,
            )),
            cmd => {
                if EI::CustomCommand::has_subcommand(cmd) {
                    Self::Custom(EI::CustomCommand::from_arg_matches(parent_matches)?)
                } else {
                    unreachable!()
                }
            },
        })
    }
}

impl<EI: EntityInterface> Autogenerate<EI> {
    fn make_relation_subcommands() -> impl Iterator<Item = clap::Command> {
        let mut out = vec![];

        struct PartVisitor<'l, EI: EntityInterface>(
            &'l mut Vec<clap::Command>,
            std::marker::PhantomData<(EI,)>,
        );
        impl<EI: EntityInterface> EntityPartVisitor for PartVisitor<'_, EI> {
            type Entity = EI::Entity;
            fn visit<EP: EntityPart>(&mut self) {
                struct Discriminator<'l, EI: EntityInterface>(
                    &'l mut Vec<clap::Command>,
                    &'static str,
                    std::marker::PhantomData<(EI,)>,
                );

                impl<EI: EntityInterface> DatumDiscriminator for Discriminator<'_, EI> {
                    fn visit_entity_id<E: Entity>(&mut self) {}
                    fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(
                        &mut self,
                    ) {
                    }
                    fn visit_value<T: serde::de::DeserializeOwned>(&mut self) {}
                    fn visit_bare_field<T: Datum>(&mut self) {}
                    fn visit_relation_map<E: Entity>(&mut self) {
                        self.0.push(add_keys::<E, EI>(
                            clap::Command::new(self.1),
                            ValueRole::AttachmentTarget,
                        ));
                    }
                    fn visit_relation_domain<R: Relation>(&mut self) {
                        self.0.push(add_keys::<R::Range, EI>(
                            clap::Command::new(self.1),
                            ValueRole::AttachmentTarget,
                        ));
                    }
                    fn visit_relation_range<R: Relation>(&mut self) {
                        self.0.push(add_keys::<R::Domain, EI>(
                            clap::Command::new(self.1),
                            ValueRole::AttachmentTarget,
                        ));
                    }
                }

                <EP::Datum as Datum>::accept_discriminator(&mut Discriminator::<EI>(
                    self.0,
                    EP::part_name(),
                    Default::default(),
                ));
            }
        }

        EI::Entity::accept_part_visitor(&mut PartVisitor::<EI>(&mut out, Default::default()));

        out.into_iter()
    }
}

impl<EI: EntityInterface> clap::FromArgMatches for Autogenerate<EI> {
    fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
        let verb = Verb::from_matches(matches);

        Ok(Self {
            verb: verb?,
            _ghost: Default::default(),
        })
    }

    fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> {
        todo!()
    }
}

impl<EI: EntityInterface> clap::Subcommand for Autogenerate<EI> {
    fn has_subcommand(_name: &str) -> bool {
        todo!()
    }

    fn augment_subcommands(cmd: clap::Command) -> clap::Command {
        let cmd = cmd
            .subcommand(
                add_keys::<EI::Entity, EI>(clap::Command::new("attach"), ValueRole::BaseTarget)
                    .subcommands(Self::make_relation_subcommands())
                    .subcommand_required(true),
            )
            .subcommand(
                add_keys::<EI::Entity, EI>(clap::Command::new("detach"), ValueRole::BaseTarget)
                    .subcommands(Self::make_relation_subcommands())
                    .subcommand_required(true),
            )
            .subcommand(add_keys::<EI::Entity, EI>(
                clap::Command::new("delete"),
                ValueRole::BaseTarget,
            ))
            .subcommand(add_keys::<EI::Entity, EI>(
                clap::Command::new("inspect"),
                ValueRole::BaseTarget,
            ))
            .subcommand(clap::Command::new("list"));

        EI::CustomCommand::augment_subcommands(cmd)
    }

    fn augment_subcommands_for_update(_cmd: clap::Command) -> clap::Command {
        todo!()
    }
}