From 3b96f2019d87af1b2458acd362dc74d34588aa93 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Wed, 30 Sep 2020 15:31:31 -0600 Subject: [PATCH] Split out core types into sprinklers_core --- Cargo.toml | 1 + sprinklers_core/Cargo.toml | 16 ++++ sprinklers_core/src/lib.rs | 4 + .../src/model/mod.rs | 0 sprinklers_core/src/model/program.rs | 30 +++++++ .../src/model/section.rs | 16 ---- .../src/schedule.rs | 0 .../src/section_interface.rs | 0 .../src/serde.rs | 2 +- sprinklers_rs/Cargo.toml | 1 + sprinklers_rs/src/database/mod.rs | 10 ++- sprinklers_rs/src/database/program.rs | 46 +++++++++++ sprinklers_rs/src/database/section.rs | 16 ++++ sprinklers_rs/src/main.rs | 10 +-- sprinklers_rs/src/model/program.rs | 78 ------------------- sprinklers_rs/src/mqtt/mod.rs | 8 +- sprinklers_rs/src/mqtt/request/mod.rs | 3 +- sprinklers_rs/src/mqtt/request/programs.rs | 4 +- sprinklers_rs/src/mqtt/request/sections.rs | 9 ++- sprinklers_rs/src/mqtt/topics.rs | 2 +- sprinklers_rs/src/program_runner.rs | 10 ++- sprinklers_rs/src/section_runner.rs | 12 +-- sprinklers_rs/src/section_runner_json.rs | 7 +- 23 files changed, 156 insertions(+), 129 deletions(-) create mode 100644 sprinklers_core/Cargo.toml create mode 100644 sprinklers_core/src/lib.rs rename {sprinklers_rs => sprinklers_core}/src/model/mod.rs (100%) create mode 100644 sprinklers_core/src/model/program.rs rename {sprinklers_rs => sprinklers_core}/src/model/section.rs (68%) rename {sprinklers_rs => sprinklers_core}/src/schedule.rs (100%) rename {sprinklers_rs => sprinklers_core}/src/section_interface.rs (100%) rename {sprinklers_rs => sprinklers_core}/src/serde.rs (95%) create mode 100644 sprinklers_rs/src/database/program.rs create mode 100644 sprinklers_rs/src/database/section.rs delete mode 100644 sprinklers_rs/src/model/program.rs diff --git a/Cargo.toml b/Cargo.toml index 4b9ef1e..2f8bf9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "sprinklers_core", "sprinklers_rs" ] \ No newline at end of file diff --git a/sprinklers_core/Cargo.toml b/sprinklers_core/Cargo.toml new file mode 100644 index 0000000..ff12ff3 --- /dev/null +++ b/sprinklers_core/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "sprinklers_core" +version = "0.1.0" +authors = ["Alex Mikhalev "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.15" } +serde = { version = "1.0.116", features = ["derive"] } +im = "15.0.0" +tracing = { version = "0.1.19" } + +[dev-dependencies] +serde_json = "1.0.57" diff --git a/sprinklers_core/src/lib.rs b/sprinklers_core/src/lib.rs new file mode 100644 index 0000000..9ecb760 --- /dev/null +++ b/sprinklers_core/src/lib.rs @@ -0,0 +1,4 @@ +pub mod model; +pub mod schedule; +pub mod serde; +pub mod section_interface; diff --git a/sprinklers_rs/src/model/mod.rs b/sprinklers_core/src/model/mod.rs similarity index 100% rename from sprinklers_rs/src/model/mod.rs rename to sprinklers_core/src/model/mod.rs diff --git a/sprinklers_core/src/model/program.rs b/sprinklers_core/src/model/program.rs new file mode 100644 index 0000000..de4d6ec --- /dev/null +++ b/sprinklers_core/src/model/program.rs @@ -0,0 +1,30 @@ +use super::section::SectionId; +use crate::schedule::Schedule; +use serde::{Deserialize, Serialize}; +use std::{sync::Arc, time::Duration}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProgramItem { + pub section_id: SectionId, + #[serde(with = "crate::serde::duration_secs")] + pub duration: Duration, +} + +pub type ProgramSequence = Vec; + +pub type ProgramId = u32; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Program { + pub id: ProgramId, + pub name: String, + pub sequence: ProgramSequence, + pub enabled: bool, + pub schedule: Schedule, +} + +pub type ProgramRef = Arc; + +pub type Programs = im::OrdMap; diff --git a/sprinklers_rs/src/model/section.rs b/sprinklers_core/src/model/section.rs similarity index 68% rename from sprinklers_rs/src/model/section.rs rename to sprinklers_core/src/model/section.rs index 60c2f03..7499365 100644 --- a/sprinklers_rs/src/model/section.rs +++ b/sprinklers_core/src/model/section.rs @@ -6,7 +6,6 @@ //! describes a logical section and how it maps to a physical one. use crate::section_interface::SecId; -use rusqlite::{Error as SqlError, Row as SqlRow, ToSql}; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -23,21 +22,6 @@ pub struct Section { pub interface_id: SecId, } -impl Section { - pub fn from_sql<'a>(row: &SqlRow<'a>) -> Result { - Ok(Section { - id: row.get(0)?, - name: row.get(1)?, - interface_id: row.get(2)?, - }) - } - - #[allow(dead_code)] - pub fn as_sql(&self) -> Vec<&dyn ToSql> { - vec![&self.id, &self.name, &self.interface_id] - } -} - pub type SectionRef = Arc
; pub type Sections = im::OrdMap; diff --git a/sprinklers_rs/src/schedule.rs b/sprinklers_core/src/schedule.rs similarity index 100% rename from sprinklers_rs/src/schedule.rs rename to sprinklers_core/src/schedule.rs diff --git a/sprinklers_rs/src/section_interface.rs b/sprinklers_core/src/section_interface.rs similarity index 100% rename from sprinklers_rs/src/section_interface.rs rename to sprinklers_core/src/section_interface.rs diff --git a/sprinklers_rs/src/serde.rs b/sprinklers_core/src/serde.rs similarity index 95% rename from sprinklers_rs/src/serde.rs rename to sprinklers_core/src/serde.rs index 92c3ac6..9737734 100644 --- a/sprinklers_rs/src/serde.rs +++ b/sprinklers_core/src/serde.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; -pub mod duration { +pub mod duration_secs { use super::*; use std::time::Duration; diff --git a/sprinklers_rs/Cargo.toml b/sprinklers_rs/Cargo.toml index cb635aa..297d8a7 100644 --- a/sprinklers_rs/Cargo.toml +++ b/sprinklers_rs/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +sprinklers_core = { path = "../sprinklers_core" } rusqlite = "0.24.0" color-eyre = "0.5.1" eyre = "0.6.0" diff --git a/sprinklers_rs/src/database/mod.rs b/sprinklers_rs/src/database/mod.rs index 07668da..50abec9 100644 --- a/sprinklers_rs/src/database/mod.rs +++ b/sprinklers_rs/src/database/mod.rs @@ -1,14 +1,16 @@ mod migration; mod migrations; +mod program; +mod section; pub use migration::*; pub use migrations::create_migrations; pub use rusqlite::Connection as DbConn; -use eyre::Result; +use sprinklers_core::model::{Programs, Sections}; -use crate::model::{Program, Programs, Section, Sections}; +use eyre::Result; use rusqlite::NO_PARAMS; pub fn setup_db() -> Result { @@ -26,7 +28,7 @@ pub fn query_sections(conn: &DbConn) -> Result { "SELECT s.id, s.name, s.interface_id \ FROM sections AS s;", )?; - let rows = statement.query_map(NO_PARAMS, Section::from_sql)?; + let rows = statement.query_map(NO_PARAMS, section::from_sql)?; let mut sections = Sections::new(); for row in rows { let section = row?; @@ -43,7 +45,7 @@ pub fn query_programs(conn: &DbConn) -> Result { INNER JOIN program_sequences AS ps ON ps.program_id = p.id;", )?; - let rows = statement.query_map(NO_PARAMS, Program::from_sql)?; + let rows = statement.query_map(NO_PARAMS, program::from_sql)?; let mut programs = Programs::new(); for row in rows { let program = row?; diff --git a/sprinklers_rs/src/database/program.rs b/sprinklers_rs/src/database/program.rs new file mode 100644 index 0000000..a9dbf87 --- /dev/null +++ b/sprinklers_rs/src/database/program.rs @@ -0,0 +1,46 @@ +use sprinklers_core::{ + model::{Program, ProgramSequence}, + schedule::Schedule, +}; + +use rusqlite::{ + types::{FromSql, FromSqlError, FromSqlResult, ValueRef}, + Result as SqlResult, Row as SqlRow, +}; +use serde::Deserialize; + +struct SqlJson(T); + +impl SqlJson { + fn into_inner(self) -> T { + self.0 + } +} + +impl FromSql for SqlJson +where + for<'de> T: Deserialize<'de>, +{ + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + if let ValueRef::Text(text) = value { + let deser_value: T = + serde_json::from_slice(text).map_err(|err| FromSqlError::Other(Box::new(err)))?; + Ok(SqlJson(deser_value)) + } else { + Err(FromSqlError::InvalidType) + } + } +} + +type SqlProgramSequence = SqlJson; +type SqlSchedule = SqlJson; + +pub fn from_sql<'a>(row: &SqlRow<'a>) -> SqlResult { + Ok(Program { + id: row.get(0)?, + name: row.get(1)?, + sequence: row.get::<_, SqlProgramSequence>(2)?.into_inner(), + enabled: row.get(3)?, + schedule: row.get::<_, SqlSchedule>(4)?.into_inner(), + }) +} diff --git a/sprinklers_rs/src/database/section.rs b/sprinklers_rs/src/database/section.rs new file mode 100644 index 0000000..f072fab --- /dev/null +++ b/sprinklers_rs/src/database/section.rs @@ -0,0 +1,16 @@ +use sprinklers_core::model::Section; + +use rusqlite::{Error as SqlError, Row as SqlRow, ToSql}; + +pub fn from_sql<'a>(row: &SqlRow<'a>) -> Result { + Ok(Section { + id: row.get(0)?, + name: row.get(1)?, + interface_id: row.get(2)?, + }) +} + +#[allow(dead_code)] +pub fn as_sql(section: &Section) -> Vec<&dyn ToSql> { + vec![§ion.id, §ion.name, §ion.interface_id] +} diff --git a/sprinklers_rs/src/main.rs b/sprinklers_rs/src/main.rs index 32d2017..2e5c55a 100644 --- a/sprinklers_rs/src/main.rs +++ b/sprinklers_rs/src/main.rs @@ -2,24 +2,22 @@ #![warn(clippy::print_stdout)] mod database; -mod model; mod mqtt; mod option_future; mod program_runner; -mod schedule; -mod section_interface; mod section_runner; mod section_runner_json; -mod serde; #[cfg(test)] mod trace_listeners; mod update_listener; +use sprinklers_core::section_interface::MockSectionInterface; +use update_listener::UpdateListener; + use eyre::Result; use std::sync::Arc; use tracing::{debug, info}; use tracing_subscriber::EnvFilter; -use update_listener::UpdateListener; #[actix_rt::main] async fn main() -> Result<()> { @@ -40,7 +38,7 @@ async fn main() -> Result<()> { } // TODO: Section interface which actual does something. Preferrably selectable somehow - let section_interface: Arc<_> = section_interface::MockSectionInterface::new(6).into(); + let section_interface: Arc<_> = MockSectionInterface::new(6).into(); let mut section_runner = section_runner::SectionRunner::new(section_interface); let mut program_runner = program_runner::ProgramRunner::new(section_runner.clone()); diff --git a/sprinklers_rs/src/model/program.rs b/sprinklers_rs/src/model/program.rs deleted file mode 100644 index 8ce2a4f..0000000 --- a/sprinklers_rs/src/model/program.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::section::SectionId; -use crate::schedule::Schedule; -use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Duration}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ProgramItem { - pub section_id: SectionId, - #[serde(with = "crate::serde::duration")] - pub duration: Duration, -} - -pub type ProgramSequence = Vec; - -pub type ProgramId = u32; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Program { - pub id: ProgramId, - pub name: String, - pub sequence: ProgramSequence, - pub enabled: bool, - pub schedule: Schedule, -} - -mod sql { - use super::{Program, ProgramSequence}; - use crate::schedule::Schedule; - use rusqlite::{ - types::{FromSql, FromSqlError, FromSqlResult, ValueRef}, - Result as SqlResult, Row as SqlRow, - }; - use serde::Deserialize; - - struct SqlJson(T); - - impl SqlJson { - fn into_inner(self) -> T { - self.0 - } - } - - impl FromSql for SqlJson - where - for<'de> T: Deserialize<'de>, - { - fn column_result(value: ValueRef<'_>) -> FromSqlResult { - if let ValueRef::Text(text) = value { - let deser_value: T = serde_json::from_slice(text) - .map_err(|err| FromSqlError::Other(Box::new(err)))?; - Ok(SqlJson(deser_value)) - } else { - Err(FromSqlError::InvalidType) - } - } - } - - type SqlProgramSequence = SqlJson; - type SqlSchedule = SqlJson; - - impl Program { - pub fn from_sql<'a>(row: &SqlRow<'a>) -> SqlResult { - Ok(Self { - id: row.get(0)?, - name: row.get(1)?, - sequence: row.get::<_, SqlProgramSequence>(2)?.into_inner(), - enabled: row.get(3)?, - schedule: row.get::<_, SqlSchedule>(4)?.into_inner(), - }) - } - } -} - -pub type ProgramRef = Arc; - -pub type Programs = im::OrdMap; diff --git a/sprinklers_rs/src/mqtt/mod.rs b/sprinklers_rs/src/mqtt/mod.rs index 7cc2bfd..623bb77 100644 --- a/sprinklers_rs/src/mqtt/mod.rs +++ b/sprinklers_rs/src/mqtt/mod.rs @@ -6,11 +6,9 @@ mod topics; pub use request::RequestContext; use self::topics::Topics; -use crate::{ - model::{Program, ProgramId, Programs, Section, SectionId, Sections}, - section_runner::SecRunnerState, - section_runner_json::SecRunnerStateJson, -}; +use crate::{section_runner::SecRunnerState, section_runner_json::SecRunnerStateJson}; +use sprinklers_core::model::{Program, ProgramId, Programs, Section, SectionId, Sections}; + use actix::{Actor, Addr}; use eyre::WrapErr; use rumqttc::{LastWill, MqttOptions, QoS}; diff --git a/sprinklers_rs/src/mqtt/request/mod.rs b/sprinklers_rs/src/mqtt/request/mod.rs index 97a056f..2b074db 100644 --- a/sprinklers_rs/src/mqtt/request/mod.rs +++ b/sprinklers_rs/src/mqtt/request/mod.rs @@ -1,4 +1,5 @@ -use crate::{model::Sections, program_runner::ProgramRunner, section_runner::SectionRunner}; +use crate::{program_runner::ProgramRunner, section_runner::SectionRunner}; +use sprinklers_core::model::Sections; use futures_util::ready; use futures_util::FutureExt; diff --git a/sprinklers_rs/src/mqtt/request/programs.rs b/sprinklers_rs/src/mqtt/request/programs.rs index 237ba60..311489b 100644 --- a/sprinklers_rs/src/mqtt/request/programs.rs +++ b/sprinklers_rs/src/mqtt/request/programs.rs @@ -1,5 +1,7 @@ use super::*; -use crate::{model::ProgramId, program_runner::ProgramRunnerError}; +use crate::program_runner::ProgramRunnerError; +use sprinklers_core::model::ProgramId; + use eyre::WrapErr; #[derive(Debug, Serialize, Deserialize)] diff --git a/sprinklers_rs/src/mqtt/request/sections.rs b/sprinklers_rs/src/mqtt/request/sections.rs index 206b9de..6a0aa97 100644 --- a/sprinklers_rs/src/mqtt/request/sections.rs +++ b/sprinklers_rs/src/mqtt/request/sections.rs @@ -1,12 +1,15 @@ use super::*; -use crate::{model::SectionRef, section_runner::SectionRunHandle}; +use crate::section_runner::SectionRunHandle; +use sprinklers_core::model::{self, SectionRef}; +use sprinklers_core::serde::duration_secs; + use eyre::WrapErr; use serde::{Deserialize, Serialize}; use std::time::Duration; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[serde(transparent)] -pub struct SectionId(pub crate::model::SectionId); +pub struct SectionId(pub model::SectionId); impl SectionId { fn get_section(self, sections: &Sections) -> Result { @@ -20,7 +23,7 @@ impl SectionId { #[serde(rename_all = "camelCase")] pub struct RunSectionRequest { pub section_id: SectionId, - #[serde(with = "crate::serde::duration")] + #[serde(with = "duration_secs")] pub duration: Duration, } diff --git a/sprinklers_rs/src/mqtt/topics.rs b/sprinklers_rs/src/mqtt/topics.rs index 6df8e49..7a40978 100644 --- a/sprinklers_rs/src/mqtt/topics.rs +++ b/sprinklers_rs/src/mqtt/topics.rs @@ -1,4 +1,4 @@ -use crate::model::{ProgramId, SectionId}; +use sprinklers_core::model::{ProgramId, SectionId}; #[derive(Clone, Debug)] pub struct Topics diff --git a/sprinklers_rs/src/program_runner.rs b/sprinklers_rs/src/program_runner.rs index 6ce6da7..bd13fdd 100644 --- a/sprinklers_rs/src/program_runner.rs +++ b/sprinklers_rs/src/program_runner.rs @@ -1,7 +1,8 @@ -use crate::model::{ProgramId, ProgramRef, Programs, Sections}; use crate::section_runner::{ Error as SectionRunnerError, SectionEvent, SectionEventRecv, SectionRunHandle, SectionRunner, }; +use sprinklers_core::model::{ProgramId, ProgramRef, Programs, Sections}; + use actix::{ Actor, ActorContext, ActorFuture, ActorStream, Addr, AsyncContext, Handler, Message, MessageResult, SpawnHandle, StreamHandler, WrapFuture, @@ -467,12 +468,13 @@ impl ProgramRunner { #[cfg(test)] mod test { use super::*; - use crate::section_interface::{MockSectionInterface, SectionInterface}; - use crate::{ + use crate::trace_listeners::{EventListener, Filters}; + use sprinklers_core::{ model::{Program, ProgramItem, Section}, schedule::{every_day, DateTimeBound, Schedule}, - trace_listeners::{EventListener, Filters}, + section_interface::{MockSectionInterface, SectionInterface}, }; + use assert_matches::assert_matches; use im::ordmap; use std::{sync::Arc, time::Duration}; diff --git a/sprinklers_rs/src/section_runner.rs b/sprinklers_rs/src/section_runner.rs index 771e04d..ff972ee 100644 --- a/sprinklers_rs/src/section_runner.rs +++ b/sprinklers_rs/src/section_runner.rs @@ -1,5 +1,6 @@ -use crate::model::{SectionId, SectionRef}; -use crate::section_interface::SectionInterface; +use sprinklers_core::model::{SectionId, SectionRef}; +use sprinklers_core::section_interface::SectionInterface; + use actix::{ Actor, ActorContext, Addr, AsyncContext, Handler, Message, MessageResult, SpawnHandle, }; @@ -618,11 +619,12 @@ impl SectionRunner { #[cfg(test)] mod test { use super::*; - use crate::section_interface::MockSectionInterface; - use crate::{ + use crate::trace_listeners::{EventListener, Filters}; + use sprinklers_core::{ model::{Section, Sections}, - trace_listeners::{EventListener, Filters}, + section_interface::MockSectionInterface, }; + use assert_matches::assert_matches; use im::ordmap; use tracing_subscriber::prelude::*; diff --git a/sprinklers_rs/src/section_runner_json.rs b/sprinklers_rs/src/section_runner_json.rs index 65bb363..657f4e2 100644 --- a/sprinklers_rs/src/section_runner_json.rs +++ b/sprinklers_rs/src/section_runner_json.rs @@ -1,7 +1,6 @@ -use crate::{ - model::SectionId, - section_runner::{SecRun, SecRunState, SecRunnerState}, -}; +use crate::section_runner::{SecRun, SecRunState, SecRunnerState}; +use sprinklers_core::model::SectionId; + use chrono::{DateTime, Utc}; use serde::Serialize; use std::time::SystemTime;