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;