mycelium_command 0.1.1

Library for Mycelium DDM
Documentation
#[macro_use]
extern crate serde;
extern crate serde_json;
extern crate uuid;

use serde::Serialize;

use std::str::FromStr;

pub mod node;
pub mod prelude;

pub type ResultList = Vec<Vec<u8>>;
pub type ReturnPkg = (
    Option<[u8; 16]>,
    Option<String>,
    Option<Vec<u8>>,
    Option<ResultList>,
    Option<Vec<String>>,
);

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum KeyWord {
    Unknown,
    Archive,
    First,
    From,
    Insert,
    Into,
    Last,
    Limit,
    Select,
    Top,
    Update,
    Where,
}

impl From<&str> for KeyWord {
    fn from(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "archive" => KeyWord::Archive,
            "first" => KeyWord::First,
            "from" => KeyWord::From,
            "insert" => KeyWord::Insert,
            "into" => KeyWord::Into,
            "last" => KeyWord::Last,
            "limit" => KeyWord::Limit,
            "select" => KeyWord::Select,
            "top" => KeyWord::Top,
            "update" => KeyWord::Update,
            "where" => KeyWord::Where,
            _ => KeyWord::Unknown,
        }
    }
}

impl Into<&str> for KeyWord {
    fn into(self) -> &'static str {
        match self {
            KeyWord::Unknown => "unknown",
            KeyWord::Archive => "archive",
            KeyWord::First => "first",
            KeyWord::From => "from",
            KeyWord::Insert => "insert",
            KeyWord::Into => "into",
            KeyWord::Last => "last",
            KeyWord::Limit => "limit",
            KeyWord::Select => "select",
            KeyWord::Top => "top",
            KeyWord::Update => "update",
            KeyWord::Where => "where",
        }
    }
}

///
/// # Command struct can be used to build a command with the normal builder pattern.
///
///
/// ```rust
/// use crate::mycelium_command::prelude::*;
/// let cmd = Command::new()
///     .with_action(Action::Select)
///     .with_what(What::Index(String::from("dog"))) //and
///     .with_or_what(What::Index("idx3".to_string())) //or
///     .with_limit(Limits::First(4)); // first usize results
/// ```
///
///  - Action: Default (error), Archive, Insert, Select, Update. Action to perform.
///  - Limits: None, Some(Default), Some(First(usize)), Some(Last(usize)), Some(Limit(usize))
///  - LogicOps: List of logic ops and target
///  - Tag: Tag to execute command against. Required.
///  - What: What to execute command or operation against. Id of another node, Index value, or other tags.
///
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Command {
    pub action: Action,
    pub limit: Option<Limits>,
    pub ops: Vec<(LogicOp, What)>,
    pub tag: String,
    pub what: What,
}

///
/// # Action enum values like Insert, Update, Archive, Select
///
///  - Default: No Action or Error result
///  - Archive: Archive node of given Id will require with_tag
///  - Insert: Insert new node with byte array content will require with_tag
///  - Select: Select command will require with_tag and a with_what target
///  - Update: Update command id of node and new byte content for node will require with_tag
///
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Action {
    Default,
    Archive([u8; 16]),
    Insert(Vec<u8>),
    Select,
    Update(([u8; 16], Vec<u8>)),
}

///
/// # What to target: Id, Index
///
/// - Default: error
/// - Id: Node Id to target
/// - Index: Index term to target
///
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum What {
    Default,
    Id([u8; 16]),
    Index(String),
}

///
/// # Logic Operations like, and, or
///
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum LogicOp {
    Default,
    And,
    Or,
}

///
/// # Limits like First, Last, or Limit
///
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Limits {
    Default,
    Limit(usize),
    First(usize),
    Last(usize),
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Result {
    Some(ReturnPkg),
    None,
}

impl Default for Command {
    fn default() -> Command {
        Command {
            action: Action::Default,
            limit: None,
            ops: vec![],
            tag: "".to_string(),
            what: What::Default,
        }
    }
}

impl Command {
    pub fn new() -> Command {
        Command::default()
    }

    ///
    /// # Parse
    ///
    /// Parse a msql statement to a command.
    ///
    /// ```rust
    /// use crate::mycelium_command::prelude::*;
    /// let cmd = Command::parse("select from tag").unwrap();
    /// ```
    /// * Parameters
    ///     - cmd: msql statement see [sql like](https://gitlab.com/matthew.bradford/myceliumdds/wikis/sql)
    ///
    pub fn parse(cmd: &str) -> std::result::Result<Command, Box<dyn std::error::Error>> {
        let tmp = cmd.trim().to_string();
        let split = tmp.split_at(7);

        match split.0.trim().to_lowercase().as_str() {
            "archive" => parse_archive(split.1),
            "insert" => parse_insert(split.1),
            "select" => parse_select(split.1),
            "update" => parse_update(split.1),
            _ => Err(From::from(format!(
                "Invalid command: {} unrecognized.",
                split.0
            ))),
        }
    }

    pub fn with_action(mut self, action: Action) -> Self {
        self.action = action;
        self
    }

    pub fn with_limit(mut self, limit: Limits) -> Self {
        self.limit = Some(limit);
        self
    }

    pub fn with_what(mut self, what: What) -> Self {
        self._what(what);
        self
    }

    fn _what(&mut self, what: What) {
        self.what = what;
    }

    pub fn with_and_what(mut self, what: What) -> Self {
        self._and_what(what);
        self
    }

    fn _and_what(&mut self, what: What) {
        self.ops.push((LogicOp::And, what));
    }

    pub fn with_or_what(mut self, what: What) -> Self {
        self._or_what(what);
        self
    }

    fn _or_what(&mut self, what: What) {
        self.ops.push((LogicOp::Or, what));
    }

    pub fn with_tag(mut self, tag: &str) -> Self {
        self._tag(tag);
        self
    }

    fn _tag(&mut self, tag: &str) {
        self.tag = tag.to_string();
    }
}

// archive from <tag> where id is [;]
fn parse_archive(_select: &str) -> std::result::Result<Command, Box<dyn std::error::Error>> {
    unimplemented!()
}

fn parse_item_arr(stmt: &str) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>> {
    match (stmt.find('['), stmt.find(']')) {
        (Some(start), Some(end)) => {
            let obj = stmt.get(start..=end).unwrap();
            match serde_json::from_str(obj) {
                Ok(item) => Ok(item),
                _ => Err(From::from("Invalid command.")),
            }
        }
        (_, _) => Err(From::from("Invalid statement termination.")),
    }
}

// Insert [] into <tag>
// Insert \"[65,32,100,111,103,32,105,115,32,97,32,100,111,103,32,105,115,32,97,32,102,105,115,104]\" into dog
fn parse_insert(stmt: &str) -> std::result::Result<Command, Box<dyn std::error::Error>> {
    let item_vec = parse_item_arr(stmt)?;
    let mut item = Command::new().with_action(Action::Insert(item_vec));
    let tag = parse_tag(stmt).expect("Failed to parse tag.");
    item._tag(tag.as_str());
    Ok(item)
}

// Select from <tag> where id is [u8; 16]
// Select from <tag> where is dog and is fish or is like orange limit 1
// Select from <tag> where is not fish
// Select from <tag>
fn parse_select(select: &str) -> std::result::Result<Command, Box<dyn std::error::Error>> {
    let cmd = Command::new().with_action(Action::Select);

    let pattern = (
        select.find("from"),
        select.find("where"),
        select.find("limit"),
    );
    match pattern {
        (Some(_), None, None) => {
            let tag = parse_tag(select)?;
            let mut cmd = cmd;
            cmd._tag(&tag);
            Ok(cmd)
        }
        (Some(_), Some(l_where), None) => {
            let cmd_tag = parse_tag(select)?;
            let mut cmd = cmd;
            cmd._tag(&cmd_tag);
            parse_where(select, l_where, select.len(), cmd)
        }
        (Some(_from), Some(_l_where), Some(_limit)) => {
            //let cmd_from = parse_from(select, from, l_where, cmd);
            //let cmd_what = parse_where(select, l_where, limit, cmd_from);
            unimplemented!()
        }
        (Some(_from), None, Some(_limit)) => unimplemented!(),
        (None, _, _) => Err(From::from("from part required")),
    }
}

// mvp: is dog and is fish or like orange
// eventual: is dog and is (select from <other tag> where is canine)
// or: is dog and (select from <other tag> where is canine)
fn parse_where(
    stmt: &str,
    l_where: usize,
    stop: usize,
    cmd: Command,
) -> std::result::Result<Command, Box<dyn std::error::Error>> {
    match stmt.find("id") {
        Some(_) => {
            // retrieve item by id
            let id = parse_id(stmt);
            if id.is_some() {
                let id = *uuid::Uuid::from_str(&id.unwrap())?.as_bytes();
                let mut cmd = cmd;
                cmd._what(What::Id(id));
                Ok(cmd)
            } else {
                Err(From::from("statement id is required"))
            }
        }
        None => {
            // no id's we are searching on indexes
            let l_where = stmt.get((l_where + 5)..stop).unwrap();
            parse_what(l_where, cmd)
        }
    }
}

// select from dog where id is [;]
// update [] from dog where id is [;]
fn parse_id(stmt: &str) -> Option<String> {
    let pattern = (stmt.find("id"), stmt.find("is"));
    match pattern {
        (Some(_), Some(is_idx)) => {
            let tmp = stmt.get(is_idx + 2..stmt.len()).unwrap();
            let rmv = next_keyword_idx(tmp);
            if rmv.is_some() {
                let rmv = rmv.unwrap();
                Some(stmt.get(0..rmv).unwrap().trim().to_string())
            } else {
                Some(tmp.trim().to_string())
            }
        }
        (Some(id_idx), None) => {
            let tmp = stmt.get(id_idx + 2..stmt.len()).unwrap();
            let rmv = next_keyword_idx(tmp);
            if rmv.is_some() {
                let rmv = rmv.unwrap();
                Some(stmt.get(0..rmv).unwrap().trim().to_string())
            } else {
                Some(tmp.trim().to_string())
            }
        }
        _ => None,
    }
}

fn parse_what(
    stmt: &str,
    cmd: Command,
) -> std::result::Result<Command, Box<dyn std::error::Error>> {
    let mut cmd = cmd;
    match stmt.find("is") {
        Some(_is) => {
            let is: Vec<&str> = stmt.split("is").collect();
            let mut next_modifier = None;
            for condition in is {
                let this_modifier = next_modifier.clone();
                match (condition.trim().is_empty(), condition.len() > 4) {
                    (true, _) => (), // Empty nothing to to do.
                    (false, false) => {
                        let what = condition.trim().to_string();
                        match this_modifier {
                            Some(LogicOp::And) => cmd._and_what(What::Index(what)),
                            Some(LogicOp::Or) => cmd._or_what(What::Index(what)),
                            Some(LogicOp::Default) | None => cmd._what(What::Index(what)),
                        }
                    }
                    (false, true) => {
                        let mut what = condition.trim().to_string();
                        let next_op = what.get((what.len() - 3)..what.len()).unwrap();
                        match next_op.trim() {
                            "and" => next_modifier = Some(LogicOp::And),
                            "or" => next_modifier = Some(LogicOp::Or),
                            _ => next_modifier = None,
                        }
                        if next_modifier.is_some() {
                            what = what.get(0..(what.len() - 3)).unwrap().trim().to_string();
                        }

                        match this_modifier {
                            Some(LogicOp::And) => cmd._and_what(What::Index(what)),
                            Some(LogicOp::Or) => cmd._or_what(What::Index(what)),
                            Some(LogicOp::Default) | None => cmd._what(What::Index(what)),
                        }
                    }
                }
            }
        }
        None => {
            let stmt = stmt.trim().to_string();
            cmd._what(What::Index(stmt))
        }
    }
    Ok(cmd)
}

fn parse_tag(stmt: &str) -> std::result::Result<String, Box<dyn std::error::Error>> {
    let kw_from: &str = KeyWord::From.into();
    let kw_into: &str = KeyWord::Into.into();
    let pattern = (stmt.find(kw_from), stmt.find(kw_into));
    match pattern {
        (Some(idx), None) | (None, Some(idx)) => {
            let stmt_minus_kw = stmt
                .get((idx + 4)..stmt.len())
                .expect("Split off from failed");
            let next_key_word = next_keyword_idx(stmt_minus_kw);
            if next_key_word.is_some() {
                let next_key_word = next_key_word.unwrap();
                Ok(stmt_minus_kw
                    .get(0..next_key_word)
                    .expect("Seperate tag from statement failed")
                    .trim()
                    .to_string())
            } else {
                Ok(stmt_minus_kw
                    .get(0..stmt_minus_kw.len())
                    .expect("Seperate tag from statement failed")
                    .trim()
                    .to_string())
            }
        }
        _ => Err(From::from("Failed to parse tag out of statement")),
    }
}

fn next_keyword_idx(stmt: &str) -> Option<usize> {
    let mut iter = stmt
        .split_whitespace()
        .filter(|&s| KeyWord::from(s) != KeyWord::Unknown);
    let first = iter.nth(0);

    if first.is_some() {
        let first = first.unwrap();
        return Some(stmt.find(first).expect("Shouldn't error"));
    }

    None
}

/// Update [0,0,0,0,0] where id is [u8; 16]
fn parse_update(stmt: &str) -> std::result::Result<Command, Box<dyn std::error::Error>> {
    let item = parse_item_arr(stmt)?;
    let id = parse_id(stmt);
    let tag = parse_tag(stmt)?;
    if id.is_some() {
        let id = *uuid::Uuid::from_str(&id.unwrap())?.as_bytes();
        let mut cmd = Command::new().with_action(Action::Update((id, item)));
        cmd._tag(tag.as_str());
        Ok(cmd)
    } else {
        Err(From::from("Statement id is required."))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::str::FromStr;
    use uuid::Uuid;

    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct Test {
        pub test: String,
        pub test_int: i32,
        pub test_lint: i64,
        pub test_index: usize,
        pub test_float: f32,
        pub test_lfloat: f64,
        pub test_bool: bool,
        pub test_vec: Vec<u8>,
        pub test_arr: [u8; 16],
    }

    impl Default for Test {
        fn default() -> Self {
            Test {
                test: "Test".to_string(),
                test_int: 0,
                test_lint: 0,
                test_index: 0,
                test_float: 0.1,
                test_lfloat: 0.22,
                test_bool: false,
                test_vec: vec![],
                test_arr: [0; 16],
            }
        }
    }

    impl Test {
        #[allow(clippy::neg_multiply, clippy::cast_lossless)]
        pub fn new<S>(text: S, index: usize) -> Test
        where
            S: Into<String>,
        {
            let mut test = Test::default();
            test.test = text.into();
            test.test_int = (index as i32 * -1) as i32;
            test.test_lint = (index as i32 * -1) as i64;
            test.test_float = index as f32;
            test.test_lfloat = index as f64;
            test.test_index = index;

            test
        }

        pub fn get_index(&self) -> usize {
            self.test_index
        }

        pub fn serialize(&self) -> String {
            serde_json::to_string(self).expect("Failed to serialize test data.")
        }
    }

    use std::cmp::Ordering;
    impl std::cmp::Ord for Test {
        fn cmp(&self, other: &Self) -> Ordering {
            self.get_index().cmp(&other.get_index())
        }
    }

    impl Eq for Test {}

    impl PartialOrd for Test {
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
            Some(self.get_index().cmp(&other.get_index()))
        }
    }

    impl PartialEq for Test {
        fn eq(&self, other: &Self) -> bool {
            self.get_index() == other.get_index()
        }
    }

    #[test]
    fn parse_update() {
        // update "\[10, 10, 10]\" where id [u8; 16]
        let id = "936DA01F9ABD4d9d80C702AF85C822A8";
        let test = Test::new("Some text", 1).serialize();
        let test_vec = test.as_bytes().to_vec();
        let sql_sorta = format!(
            "update {} from dog where id {}",
            serde_json::to_string(&test_vec).unwrap(),
            id
        );
        let parse_cmd = Command::parse(sql_sorta.as_str()).expect("Error parsing update command.");
        let uid = *Uuid::from_str(id).expect("Shouldn't fail").as_bytes();
        let correct_cmd = Command::new()
            .with_action(Action::Update((uid, test_vec)))
            .with_tag("dog");
        //correct_cmd._tag("dog");

        assert_eq!(correct_cmd, parse_cmd);
    }

    #[test]
    fn parse_insert() {
        let mut cmp = Command::new().with_action(Action::Insert(
            "A dog is a dog is a fish".as_bytes().to_vec(),
        ));
        cmp._tag("dog");
        let tmp = "A dog is a dog is a fish".as_bytes().to_vec();
        let cmd_string = format!("Insert {} into dog", serde_json::to_string(&tmp).unwrap());
        match Command::parse(cmd_string.as_str()) {
            Ok(cmd) => {
                assert_eq!(cmp, cmd);
            }
            Err(_) => assert!(false),
        }
    }

    #[test]
    fn parse_select_and_or() {
        let mut cmp = Command::new().with_action(Action::Select);
        cmp._tag("frog");
        cmp._what(What::Index("duck".into()));
        cmp._and_what(What::Index("wet".into()));
        cmp._or_what(What::Index("flower".into()));

        match Command::parse("Select from frog where duck and is wet or is flower") {
            Ok(cmd) => assert_eq!(cmp, cmd),
            Err(_) => assert!(false),
        }
    }

    #[test]
    fn parse_select_id() {
        // select from <tag>
        let cmd = Command::parse(
            format!("select from tag where id is 936DA01F9ABD4d9d80C702AF85C822A8").as_str(),
        )
        .expect("Valid shouldn't fail");

        let mut cmd_cmp = Command::new().with_action(Action::Select);
        cmd_cmp._tag("tag");
        cmd_cmp._what(What::Id([
            147, 109, 160, 31, 154, 189, 77, 157, 128, 199, 2, 175, 133, 200, 34, 168,
        ]));

        assert_eq!(cmd_cmp, cmd);
    }

    #[test]
    fn parse_select() {
        // select from <tag>
        let cmd = Command::parse("select from tag").expect("Valid shouldn't fail");
        let mut cmd_cmp = Command::new().with_action(Action::Select);
        cmd_cmp._tag("tag");

        assert_eq!(cmd_cmp, cmd);
    }

    #[test]
    fn parse_select_where() {
        let cmd = Command::parse("select from tag where duck").expect("Valid shouldn't fail.");
        let mut cmd_cmp = Command::new().with_action(Action::Select);
        cmd_cmp._tag("tag");
        cmd_cmp._what(What::Index("duck".to_string()));

        assert_eq!(cmd_cmp, cmd);
    }
}