usual 0.1.2

An experimental, extremely light, 'NORM' wrapper.
Documentation
use anyhow::Error;

#[cfg(not(feature = "tokio-postgres"))]
pub trait TryGetRow {
    fn try_get<'a, T: 'static + Clone>(&'a self, index: &str) -> Result<T, Error>;
}

#[cfg(feature = "tokio-postgres")]
use tokio_postgres::{types::FromSql, Row};

#[cfg(feature = "tokio-postgres")]
pub trait TryGetRow {
    fn try_get<'a, T: 'static + Clone + FromSql<'a>>(&'a self, index: &str) -> Result<T, Error>;
}

#[cfg(feature = "tokio-postgres")]
impl TryGetRow for Row {
    fn try_get<'a, T: 'static + Clone + FromSql<'a>>(&'a self, index: &str) -> Result<T, Error> {
        self.try_get(index).map_err(|e| Error::from(e))
    }
}

pub trait IntoEnum<T> {
    fn as_enum(&self) -> T;
    fn as_enum_i32(&self) -> i32;
}

#[macro_export]
macro_rules! impl_model {
  ($struct:ident {
      $(
          pub $field:ident:$type:ty
        ),*
  }) => {
      #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
      #[serde(rename_all = "camelCase")]
      pub struct $struct {
          $(
              pub $field: $type,
          )*
      }

      impl Model for $struct {
          fn from_row_starting_index(_index: usize, row: &impl TryGetRow) -> Self {
            $struct {
                $(
                    $field: row.try_get(format!("{}{}", Self::prefix(), stringify!($field)).as_str())
                        .expect(&format!("You messed up while trying to get {} ({}{}) from {}", stringify!($field), Self::prefix(), stringify!($field), stringify!($struct)))
                ),+
              }
          }

          fn from_row_with_prefix(prefix: &str, row: &impl TryGetRow) -> Self {
            $struct {
                $(
                    $field: row.try_get(format!("{}{}", prefix, stringify!($field)).as_str())
                        .expect(&format!("You messed up while trying to get {} ({}{}) from {}", stringify!($field), prefix, stringify!($field), stringify!($struct)))
                ),+
            }
          }

          fn columns_list() -> Vec<&'static str> {
              vec![$(
                  stringify!($field)
              ),+]
          }

          fn prefix() -> &'static str {
            concat!(stringify!($struct), "__")
          }
      }
  };
}

pub trait Model
where
    Self: Sized,
{
    fn from_rows<T: TryGetRow>(rows: Vec<T>) -> Vec<Self> {
        rows.into_iter().map(|r| Self::from_row(&r)).collect()
    }

    fn from_row_starting_index(index: usize, row: &impl TryGetRow) -> Self;

    fn from_row_with_prefix(_prefix: &str, row: &impl TryGetRow) -> Self {
        Self::from_row_starting_index(0, row)
    }

    fn from_row(row: &impl TryGetRow) -> Self {
        Self::from_row_starting_index(0, row)
    }

    fn columns_list() -> Vec<&'static str>;

    fn prefix() -> &'static str {
        ""
    }

    fn columns_with_prefix_and_table(prefix: Option<&str>, table: Option<&str>) -> String {
        Self::columns_list()
            .iter()
            .map(|c| Self::column_with_prefix_and_table(c.to_owned(), prefix, table))
            .collect::<Vec<String>>()
            .join(", ")
    }

    fn column_with_prefix_and_table(
        column: &str,
        prefix: Option<&str>,
        table: Option<&str>,
    ) -> String {
        let mut column = match prefix {
            Some(val) => format!("{} as {}{}", column, val, column),
            None => column.to_owned(),
        };

        column = match table {
            Some(val) => format!("{}.{}", val, column),
            None => column.to_owned(),
        };

        column
    }

    fn columns() -> String {
        Self::columns_with_prefix_and_table(Some(Self::prefix()), None)
    }

    fn columns_with_prefix(prefix: &str) -> String {
        Self::columns_with_prefix_and_table(Some(prefix), None)
    }

    fn columns_with_table(table: &str) -> String {
        Self::columns_with_prefix_and_table(Some(Self::prefix()), Some(table))
    }
}

#[cfg(test)]
mod tests {
    use anyhow::{anyhow, Error};
    use std::{any::Any, collections::HashMap};
    use usual_proc::{query, UsualModel};

    use super::{Model, TryGetRow};
    use crate::impl_model;

    struct Row {
        value: HashMap<String, Box<dyn Any>>,
    }

    impl TryGetRow for Row {
        fn try_get<T: 'static + Clone>(&self, index: &str) -> Result<T, Error> {
            let value = self.value.get(index).unwrap();
            let cast: T = (value as &Box<dyn Any>)
                .downcast_ref::<T>()
                .ok_or_else(|| {
                    anyhow!(format!(
                        "Attempted to get type {}, but was not the correct type",
                        std::any::type_name::<T>(),
                    ))
                })?
                .clone();

            Ok(cast)
        }
    }

    #[derive(UsualModel)]
    struct TestModel {
        pub some_string: String,
        pub some_int: i32,
    }

    #[derive(UsualModel)]
    struct TestModel2 {
        pub key: String,
    }

    // impl_model!(TestModel {
    //     pub some_string: String,
    //     pub some_int: i32
    // });

    // impl_model!(TestModel2 {
    //     pub key: String
    // });

    #[test]
    fn it_should_be_able_to_get_from_row() {
        let some_string = "asdfasdfasdf".to_string();
        let some_int = 42;

        let mut value: HashMap<String, Box<dyn Any>> = HashMap::new();
        value.insert("some_string".to_owned(), Box::new(some_string.clone()));
        value.insert("some_int".to_owned(), Box::new(some_int));

        let row = Row { value };

        let test = TestModel::from_row_with_prefix("", &row);

        assert!(
            test.some_string == some_string,
            "It should correctly pull the string value from a row."
        );
        assert!(
            test.some_int == some_int,
            "It shoudl correctly pull the int value from a row"
        );
    }

    #[test]
    fn it_should_correctly_insert_columns() {
        let macro_output = query!("SELECT {TestModel} FROM test_model");

        assert!(macro_output == "SELECT some_string as TestModel__some_string, some_int as TestModel__some_int FROM test_model")
    }

    #[test]
    fn it_should_correctly_select_subsets_of_columns() {
        let macro_output = query!("SELECT {TestModel::some_string} FROM test_model");

        assert!(macro_output == "SELECT some_string as TestModel__some_string FROM test_model")
    }

    #[test]
    fn it_should_correctly_select_subsets_of_multiple_columns() {
        let macro_output = query!("SELECT {TestModel::some_string,some_int} FROM test_model");

        assert!(macro_output == "SELECT some_string as TestModel__some_string, some_int as TestModel__some_int FROM test_model")
    }

    #[test]
    fn it_should_correctly_insert_columns_with_a_table() {
        let macro_output = query!("SELECT {TestModel as t} FROM test_model as t");

        assert!(macro_output == "SELECT t.some_string as TestModel__some_string, t.some_int as TestModel__some_int FROM test_model as t")
    }

    #[test]
    fn it_should_correctly_insert_columns_with_multiple_tables() {
        let macro_output = query!("SELECT {TestModel as t}, {TestModel2 as t2} FROM test_model as t JOIN test_model as t2 on t.id = t2.id");

        println!("macro_output: {}", macro_output);

        assert!(macro_output == "SELECT t.some_string as TestModel__some_string, t.some_int as TestModel__some_int, t2.key as TestModel2__key FROM test_model as t JOIN test_model as t2 on t.id = t2.id")
    }

    #[test]
    fn it_should_correctly_select_subsets_of_columns_with_a_table() {
        let macro_output = query!("SELECT {TestModel::some_string as t} FROM test_model as t");

        assert!(
            macro_output == "SELECT t.some_string as TestModel__some_string FROM test_model as t"
        )
    }
}