Browse Source

Split out core types into sprinklers_core

master
Alex Mikhalev 4 years ago
parent
commit
3b96f2019d
  1. 1
      Cargo.toml
  2. 16
      sprinklers_core/Cargo.toml
  3. 4
      sprinklers_core/src/lib.rs
  4. 0
      sprinklers_core/src/model/mod.rs
  5. 30
      sprinklers_core/src/model/program.rs
  6. 16
      sprinklers_core/src/model/section.rs
  7. 0
      sprinklers_core/src/schedule.rs
  8. 0
      sprinklers_core/src/section_interface.rs
  9. 2
      sprinklers_core/src/serde.rs
  10. 1
      sprinklers_rs/Cargo.toml
  11. 10
      sprinklers_rs/src/database/mod.rs
  12. 46
      sprinklers_rs/src/database/program.rs
  13. 16
      sprinklers_rs/src/database/section.rs
  14. 10
      sprinklers_rs/src/main.rs
  15. 78
      sprinklers_rs/src/model/program.rs
  16. 8
      sprinklers_rs/src/mqtt/mod.rs
  17. 3
      sprinklers_rs/src/mqtt/request/mod.rs
  18. 4
      sprinklers_rs/src/mqtt/request/programs.rs
  19. 9
      sprinklers_rs/src/mqtt/request/sections.rs
  20. 2
      sprinklers_rs/src/mqtt/topics.rs
  21. 10
      sprinklers_rs/src/program_runner.rs
  22. 12
      sprinklers_rs/src/section_runner.rs
  23. 7
      sprinklers_rs/src/section_runner_json.rs

1
Cargo.toml

@ -1,5 +1,6 @@
[workspace] [workspace]
members = [ members = [
"sprinklers_core",
"sprinklers_rs" "sprinklers_rs"
] ]

16
sprinklers_core/Cargo.toml

@ -0,0 +1,16 @@
[package]
name = "sprinklers_core"
version = "0.1.0"
authors = ["Alex Mikhalev <alexmikhalevalex@gmail.com>"]
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"

4
sprinklers_core/src/lib.rs

@ -0,0 +1,4 @@
pub mod model;
pub mod schedule;
pub mod serde;
pub mod section_interface;

0
sprinklers_rs/src/model/mod.rs → sprinklers_core/src/model/mod.rs

30
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<ProgramItem>;
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<Program>;
pub type Programs = im::OrdMap<ProgramId, ProgramRef>;

16
sprinklers_rs/src/model/section.rs → sprinklers_core/src/model/section.rs

@ -6,7 +6,6 @@
//! describes a logical section and how it maps to a physical one. //! describes a logical section and how it maps to a physical one.
use crate::section_interface::SecId; use crate::section_interface::SecId;
use rusqlite::{Error as SqlError, Row as SqlRow, ToSql};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
@ -23,21 +22,6 @@ pub struct Section {
pub interface_id: SecId, pub interface_id: SecId,
} }
impl Section {
pub fn from_sql<'a>(row: &SqlRow<'a>) -> Result<Section, SqlError> {
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<Section>; pub type SectionRef = Arc<Section>;
pub type Sections = im::OrdMap<SectionId, SectionRef>; pub type Sections = im::OrdMap<SectionId, SectionRef>;

0
sprinklers_rs/src/schedule.rs → sprinklers_core/src/schedule.rs

0
sprinklers_rs/src/section_interface.rs → sprinklers_core/src/section_interface.rs

2
sprinklers_rs/src/serde.rs → sprinklers_core/src/serde.rs

@ -1,6 +1,6 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub mod duration { pub mod duration_secs {
use super::*; use super::*;
use std::time::Duration; use std::time::Duration;

1
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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
sprinklers_core = { path = "../sprinklers_core" }
rusqlite = "0.24.0" rusqlite = "0.24.0"
color-eyre = "0.5.1" color-eyre = "0.5.1"
eyre = "0.6.0" eyre = "0.6.0"

10
sprinklers_rs/src/database/mod.rs

@ -1,14 +1,16 @@
mod migration; mod migration;
mod migrations; mod migrations;
mod program;
mod section;
pub use migration::*; pub use migration::*;
pub use migrations::create_migrations; pub use migrations::create_migrations;
pub use rusqlite::Connection as DbConn; 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; use rusqlite::NO_PARAMS;
pub fn setup_db() -> Result<DbConn> { pub fn setup_db() -> Result<DbConn> {
@ -26,7 +28,7 @@ pub fn query_sections(conn: &DbConn) -> Result<Sections> {
"SELECT s.id, s.name, s.interface_id \ "SELECT s.id, s.name, s.interface_id \
FROM sections AS s;", 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(); let mut sections = Sections::new();
for row in rows { for row in rows {
let section = row?; let section = row?;
@ -43,7 +45,7 @@ pub fn query_programs(conn: &DbConn) -> Result<Programs> {
INNER JOIN program_sequences AS ps INNER JOIN program_sequences AS ps
ON ps.program_id = p.id;", 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(); let mut programs = Programs::new();
for row in rows { for row in rows {
let program = row?; let program = row?;

46
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>(T);
impl<T> SqlJson<T> {
fn into_inner(self) -> T {
self.0
}
}
impl<T> FromSql for SqlJson<T>
where
for<'de> T: Deserialize<'de>,
{
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
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<ProgramSequence>;
type SqlSchedule = SqlJson<Schedule>;
pub fn from_sql<'a>(row: &SqlRow<'a>) -> SqlResult<Program> {
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(),
})
}

16
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<Section, SqlError> {
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![&section.id, &section.name, &section.interface_id]
}

10
sprinklers_rs/src/main.rs

@ -2,24 +2,22 @@
#![warn(clippy::print_stdout)] #![warn(clippy::print_stdout)]
mod database; mod database;
mod model;
mod mqtt; mod mqtt;
mod option_future; mod option_future;
mod program_runner; mod program_runner;
mod schedule;
mod section_interface;
mod section_runner; mod section_runner;
mod section_runner_json; mod section_runner_json;
mod serde;
#[cfg(test)] #[cfg(test)]
mod trace_listeners; mod trace_listeners;
mod update_listener; mod update_listener;
use sprinklers_core::section_interface::MockSectionInterface;
use update_listener::UpdateListener;
use eyre::Result; use eyre::Result;
use std::sync::Arc; use std::sync::Arc;
use tracing::{debug, info}; use tracing::{debug, info};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use update_listener::UpdateListener;
#[actix_rt::main] #[actix_rt::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@ -40,7 +38,7 @@ async fn main() -> Result<()> {
} }
// TODO: Section interface which actual does something. Preferrably selectable somehow // 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 section_runner = section_runner::SectionRunner::new(section_interface);
let mut program_runner = program_runner::ProgramRunner::new(section_runner.clone()); let mut program_runner = program_runner::ProgramRunner::new(section_runner.clone());

78
sprinklers_rs/src/model/program.rs

@ -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<ProgramItem>;
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>(T);
impl<T> SqlJson<T> {
fn into_inner(self) -> T {
self.0
}
}
impl<T> FromSql for SqlJson<T>
where
for<'de> T: Deserialize<'de>,
{
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
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<ProgramSequence>;
type SqlSchedule = SqlJson<Schedule>;
impl Program {
pub fn from_sql<'a>(row: &SqlRow<'a>) -> SqlResult<Self> {
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<Program>;
pub type Programs = im::OrdMap<ProgramId, ProgramRef>;

8
sprinklers_rs/src/mqtt/mod.rs

@ -6,11 +6,9 @@ mod topics;
pub use request::RequestContext; pub use request::RequestContext;
use self::topics::Topics; use self::topics::Topics;
use crate::{ use crate::{section_runner::SecRunnerState, section_runner_json::SecRunnerStateJson};
model::{Program, ProgramId, Programs, Section, SectionId, Sections}, use sprinklers_core::model::{Program, ProgramId, Programs, Section, SectionId, Sections};
section_runner::SecRunnerState,
section_runner_json::SecRunnerStateJson,
};
use actix::{Actor, Addr}; use actix::{Actor, Addr};
use eyre::WrapErr; use eyre::WrapErr;
use rumqttc::{LastWill, MqttOptions, QoS}; use rumqttc::{LastWill, MqttOptions, QoS};

3
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::ready;
use futures_util::FutureExt; use futures_util::FutureExt;

4
sprinklers_rs/src/mqtt/request/programs.rs

@ -1,5 +1,7 @@
use super::*; use super::*;
use crate::{model::ProgramId, program_runner::ProgramRunnerError}; use crate::program_runner::ProgramRunnerError;
use sprinklers_core::model::ProgramId;
use eyre::WrapErr; use eyre::WrapErr;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]

9
sprinklers_rs/src/mqtt/request/sections.rs

@ -1,12 +1,15 @@
use super::*; 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 eyre::WrapErr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct SectionId(pub crate::model::SectionId); pub struct SectionId(pub model::SectionId);
impl SectionId { impl SectionId {
fn get_section(self, sections: &Sections) -> Result<SectionRef, RequestError> { fn get_section(self, sections: &Sections) -> Result<SectionRef, RequestError> {
@ -20,7 +23,7 @@ impl SectionId {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RunSectionRequest { pub struct RunSectionRequest {
pub section_id: SectionId, pub section_id: SectionId,
#[serde(with = "crate::serde::duration")] #[serde(with = "duration_secs")]
pub duration: Duration, pub duration: Duration,
} }

2
sprinklers_rs/src/mqtt/topics.rs

@ -1,4 +1,4 @@
use crate::model::{ProgramId, SectionId}; use sprinklers_core::model::{ProgramId, SectionId};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Topics<T> pub struct Topics<T>

10
sprinklers_rs/src/program_runner.rs

@ -1,7 +1,8 @@
use crate::model::{ProgramId, ProgramRef, Programs, Sections};
use crate::section_runner::{ use crate::section_runner::{
Error as SectionRunnerError, SectionEvent, SectionEventRecv, SectionRunHandle, SectionRunner, Error as SectionRunnerError, SectionEvent, SectionEventRecv, SectionRunHandle, SectionRunner,
}; };
use sprinklers_core::model::{ProgramId, ProgramRef, Programs, Sections};
use actix::{ use actix::{
Actor, ActorContext, ActorFuture, ActorStream, Addr, AsyncContext, Handler, Message, Actor, ActorContext, ActorFuture, ActorStream, Addr, AsyncContext, Handler, Message,
MessageResult, SpawnHandle, StreamHandler, WrapFuture, MessageResult, SpawnHandle, StreamHandler, WrapFuture,
@ -467,12 +468,13 @@ impl ProgramRunner {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::section_interface::{MockSectionInterface, SectionInterface}; use crate::trace_listeners::{EventListener, Filters};
use crate::{ use sprinklers_core::{
model::{Program, ProgramItem, Section}, model::{Program, ProgramItem, Section},
schedule::{every_day, DateTimeBound, Schedule}, schedule::{every_day, DateTimeBound, Schedule},
trace_listeners::{EventListener, Filters}, section_interface::{MockSectionInterface, SectionInterface},
}; };
use assert_matches::assert_matches; use assert_matches::assert_matches;
use im::ordmap; use im::ordmap;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};

12
sprinklers_rs/src/section_runner.rs

@ -1,5 +1,6 @@
use crate::model::{SectionId, SectionRef}; use sprinklers_core::model::{SectionId, SectionRef};
use crate::section_interface::SectionInterface; use sprinklers_core::section_interface::SectionInterface;
use actix::{ use actix::{
Actor, ActorContext, Addr, AsyncContext, Handler, Message, MessageResult, SpawnHandle, Actor, ActorContext, Addr, AsyncContext, Handler, Message, MessageResult, SpawnHandle,
}; };
@ -618,11 +619,12 @@ impl SectionRunner {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::section_interface::MockSectionInterface; use crate::trace_listeners::{EventListener, Filters};
use crate::{ use sprinklers_core::{
model::{Section, Sections}, model::{Section, Sections},
trace_listeners::{EventListener, Filters}, section_interface::MockSectionInterface,
}; };
use assert_matches::assert_matches; use assert_matches::assert_matches;
use im::ordmap; use im::ordmap;
use tracing_subscriber::prelude::*; use tracing_subscriber::prelude::*;

7
sprinklers_rs/src/section_runner_json.rs

@ -1,7 +1,6 @@
use crate::{ use crate::section_runner::{SecRun, SecRunState, SecRunnerState};
model::SectionId, use sprinklers_core::model::SectionId;
section_runner::{SecRun, SecRunState, SecRunnerState},
};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::Serialize; use serde::Serialize;
use std::time::SystemTime; use std::time::SystemTime;

Loading…
Cancel
Save