jankenstore/lib.rs
1//! # Overview
2//!
3//! This library is designed to provide simple interfaces
4//! to complete basic CRUD operations in a SQLite database, by leveraging [rusqlite].
5//!
6//! This should satisfy 90% of the creator's local app needs
7//!
8//! # Highlighted Features
9//! ## Actions
10//! They are Serializeable (see [serde]) and Deserializeable enums that can be used
11//! to easily translate JSON requests into SQL operation.
12//! Then they can be used to build generic CRUD services,
13//! such as web services using [Axum](https://docs.rs/axum/latest/axum/)
14//!
15//! Currently, the following actions are supported:
16//! - [action::CreateOp]
17//! - [action::CreateOp::Create]
18//! - [action::CreateOp::CreateChild]
19//! - [action::ReadOp]
20//! - [action::ReadOp::All]
21//! - [action::ReadOp::ByPk]
22//! - [action::ReadOp::Children]
23//! - [action::ReadOp::Peers]
24//! - [action::ReadOp::Search]
25//! - [action::UpdateOp]
26//! - [action::UpdateOp::Update]
27//! - [action::UpdateOp::UpdateChildren]
28//! - [action::DelOp]
29//! - [action::DelOp::Delete]
30//! - [action::DelOp::DeleteChildren]
31//! - [action::PeerOp]
32//! - [action::PeerOp::Link]
33//! - [action::PeerOp::Unlink]
34//!
35//! ## Schema
36//! [sqlite::schema::fetch_schema_family] can be used to automatically extract the schema of the database
37//! and use it to validate the input data, reducing the risk of malicious attacks
38//!
39//! * It should be used together with the actions' [run](action::ReadOp::run) (or additionally, for Create/Update ops, [run_map](action::CreateOp::run_map)) method to validate the input data
40//!
41//!
42//! ## Example of using a Read action
43//!
44//! See
45//! - [this example](https://github.com/pandazy/jankenoboe/blob/main/src/main.rs) of using this library together with [Axum](https://docs.rs/axum/latest/axum/) to create a simple web service
46//! - [related frontend code](https://github.com/pandazy/jankenamq-web) that uses the web service above to memorize Anime songs locally
47//!
48//! Also, see the example below
49//! ### Quick code example of how to use a Read action to get data from a SQLite database
50//!
51//! ```rust
52//! use jankenstore::action::{payload::ParsableOp, ReadOp};
53//! use jankenstore::sqlite::{
54//! schema::fetch_schema_family,
55//! shift::val::v_txt,
56//! basics::FetchConfig
57//! };
58//!
59//! use rusqlite::Connection;
60//! use serde_json::{json, from_value};
61//!
62//!
63//! let conn = Connection::open_in_memory().unwrap();
64//!
65//! conn.execute_batch(
66//! r#"
67//! CREATE TABLE myexample (
68//! id INTEGER PRIMARY KEY,
69//! name TEXT NOT NULL,
70//! memo TEXT DEFAULT ''
71//! );
72//! INSERT INTO myexample (id, name, memo) VALUES (1, 'Alice', 'big');
73//! INSERT INTO myexample (id,name, memo) VALUES (2, 'Alice', 'little');
74//! INSERT INTO myexample (id, name, memo) VALUES (3, 'Bob', 'big');
75//! "#
76//! ).unwrap();
77//!
78//! /*
79//! Schema family is a collection of table definitions as well as their relationships
80//! following certain conventions, the function below will automatically extract them
81//! and use them as basic violation checks to reduce malicious attacks
82//! */
83//! let schema_family = fetch_schema_family(&conn, &[], "", "").unwrap();
84//!
85//! // get all records that have the primary key 2
86//! let op: ReadOp = from_value(json!(
87//! {
88//! "ByPk": {
89//! "src": "myexample",
90//! "keys": [2]
91//! }
92//! })).unwrap();
93//! let (results, total) = op.run(&conn, &schema_family, None).unwrap();
94//! assert_eq!(results.len(), 1);
95//! assert_eq!(results[0]["name"], "Alice");
96//! assert_eq!(results[0]["memo"], "little");
97//! assert_eq!(total, 1);
98//!
99//!
100//! // get all records by search keyword in the name column
101//! // the action can also be created from a string
102//! // a practical use case might be if on a API endpoint handler,
103//! // the JSON request is received as a string, then
104//! let query_param = r#"{ "Search": {
105//! "table": "myexample",
106//! "col": "name",
107//! "keyword": "Alice"
108//! }
109//! }"#;
110//! let op = ReadOp::from_str(query_param).unwrap();
111//! let (results, total) = op.run(&conn, &schema_family, None).unwrap();
112//! assert_eq!(results.len(), 2);
113//! assert_eq!(results[0]["name"], "Alice");
114//! assert_eq!(results[1]["name"], "Alice");
115//! assert_eq!(total, 2);
116//!
117//! // Add further condition to the search by using a FetchConfig
118//! let (results, total) = op.run(&conn, &schema_family, Some(FetchConfig{
119//! display_cols: Some(&["name", "memo"]),
120//! is_distinct: true,
121//! where_config: Some(("memo like '%'||?||'%'", &[v_txt("big")])),
122//! group_by: None,
123//! order_by: None,
124//! limit: None,
125//! offset: None
126//! })).unwrap();
127//! assert_eq!(results.len(), 1);
128//! assert_eq!(results[0]["name"], "Alice");
129//! assert_eq!(results[0]["memo"], "big");
130//! assert_eq!(total, 1);
131//!
132//! ```
133
134pub mod action;
135pub mod sqlite;