strval 0.5.0

Parse strings into values
Documentation
use std::{fmt, str::FromStr};

#[cfg(feature = "rusqlite")]
use rusqlite::{
  ToSql,
  types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}
};

use crate::{StrVal, err::Error};


/// A boolean value.
///
/// `false` is accepted as `"0"`, `"false"`, `"f"`, `"off"`.
/// `true` is accepted as `"1"`, `"true"`, `"t"`, `"on"`.
#[derive(Debug, Clone)]
pub struct Bool {
  sval: String,
  val: bool
}

impl Default for Bool {
  fn default() -> Self {
    Self {
      sval: "false".into(),
      val: false
    }
  }
}

impl StrVal for Bool {
  type Type = bool;

  fn set(&mut self, sval: &str) -> Result<Self::Type, Error> {
    let dv = sval.parse::<Self>()?;
    self.sval = sval.to_string();
    self.val = dv.val;
    Ok(dv.val)
  }

  fn get(&self) -> Self::Type {
    self.val
  }

  fn val_str(&self) -> Option<String> {
    if self.val {
      Some(String::from("true"))
    } else {
      Some(String::from("false"))
    }
  }

  fn get_str_vals(&self) -> (String, Option<String>) {
    (self.sval.clone(), None)
  }
}

impl AsRef<str> for Bool {
  fn as_ref(&self) -> &str {
    &self.sval
  }
}

impl fmt::Display for Bool {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", self.sval)
  }
}

impl FromStr for Bool {
  type Err = Error;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let val = match s {
      "0" | "false" | "f" | "off" => false,
      "1" | "true" | "t" | "on" => true,
      _ => return Err(Error::Invalid("Unknown boolean value".into()))
    };
    Ok(Self {
      sval: s.to_string(),
      val
    })
  }
}


#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl ToSql for Bool {
  fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
    Ok(ToSqlOutput::from(self.as_ref()))
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl FromSql for Bool {
  #[inline]
  fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
    let s = String::column_result(value)?;
    s.parse::<Self>()
      .map_err(|e| FromSqlError::Other(Box::new(e)))
  }
}


#[cfg(test)]
mod tests {
  use super::*;

  #[cfg(feature = "rusqlite")]
  use rusqlite::{Connection, params};

  #[test]
  #[should_panic(expected = "Invalid(\"Unknown boolean value\"")]
  fn invalid() {
    "hello".parse::<Bool>().unwrap();
  }

  #[test]
  fn false_values() {
    for s in ["0", "false", "f", "off"] {
      let v = s.parse::<Bool>().unwrap();
      assert_eq!(v.as_ref(), s);
      assert!(!v.get());
    }
  }

  #[test]
  fn true_values() {
    for s in ["1", "true", "t", "on"] {
      let v = s.parse::<Bool>().unwrap();
      assert_eq!(v.as_ref(), s);
      assert!(v.get());
    }
  }

  #[cfg(feature = "rusqlite")]
  fn memdb() -> Result<Connection, rusqlite::Error> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch(
      "CREATE TABLE tbl (id INTEGER PRIMARY KEY, txtval TEXT)"
    )?;
    Ok(conn)
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  fn insert_query() {
    let conn = memdb().unwrap();

    for (idx, s) in ["1", "true", "t", "on"].iter().enumerate() {
      let b = Bool::from_str(s).unwrap();
      conn
        .execute(
          "INSERT INTO tbl (id, txtval) VALUES (?, ?);",
          params![idx, b]
        )
        .unwrap();

      let mut stmt =
        conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();
      let b: Bool = stmt.query_one([idx], |row| row.get(0)).unwrap();
      assert!(b.get());
    }

    conn.execute("DELETE FROM tbl;", []).unwrap();

    for (idx, s) in ["0", "false", "f", "off"].iter().enumerate() {
      let b = Bool::from_str(s).unwrap();
      conn
        .execute(
          "INSERT INTO tbl (id, txtval) VALUES (?, ?);",
          params![idx, b]
        )
        .unwrap();

      let mut stmt =
        conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();
      let b: Bool = stmt.query_one([idx], |row| row.get(0)).unwrap();
      assert!(!b.get());
    }
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  #[should_panic(expected = "FromSqlConversionFailure(0, Text, \
                             Invalid(\"Unknown boolean value\"))")]
  fn bad_query() {
    let conn = memdb().unwrap();

    conn
      .execute(
        "INSERT INTO tbl (id, txtval) VALUES (?, ?);",
        params![1, "hello"]
      )
      .unwrap();

    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();
    let _b: Bool = stmt.query_one([1], |row| row.get(0)).unwrap();
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :