plm_rs/
lib.rs

1#[macro_use]
2extern crate diesel;
3#[macro_use]
4extern crate diesel_migrations;
5
6#[macro_use]
7extern crate prettytable;
8
9pub mod config;
10pub mod models;
11pub mod prompt;
12pub mod schema;
13pub mod schematic;
14pub mod tables;
15
16use diesel::prelude::*;
17
18use models::*;
19
20use std::io::{StdinLock, Stdout};
21
22pub struct Application<'a> {
23    /// Config
24    pub config: config::Config,
25
26    /// Path to config
27    pub config_path: std::path::PathBuf,
28
29    /// Global prompt
30    pub prompt: prompt::Prompt<StdinLock<'a>, Stdout>,
31
32    /// Global conn
33    pub conn: SqliteConnection,
34}
35
36// Migrate
37embed_migrations!();
38
39pub fn establish_connection(db_name: &str) -> SqliteConnection {
40    // Get text version of configpath
41    let mut database_url =
42        config::get_default_config_path().unwrap_or_else(|_| panic!("Unable to get config path."));
43
44    // Add database name
45    database_url.push(db_name);
46
47    // Establish the "connection" (We're using SQLite here so no connection excpet to the filesystem)
48    let conn = SqliteConnection::establish(&database_url.to_string_lossy())
49        .unwrap_or_else(|_| panic!("Error connecting to {}", &database_url.to_string_lossy()));
50
51    // This will run migrations and also create a DB if it doesn't exist.
52    embedded_migrations::run(&conn).expect("Unable to run migration.");
53
54    conn
55}
56
57// Part related
58pub fn create_part(
59    conn: &SqliteConnection,
60    part: &NewUpdatePart,
61) -> std::result::Result<usize, diesel::result::Error> {
62    use schema::parts;
63
64    diesel::insert_into(parts::table).values(part).execute(conn)
65}
66
67pub fn update_part(
68    conn: &SqliteConnection,
69    id: &i32,
70    part: &NewUpdatePart,
71) -> std::result::Result<usize, diesel::result::Error> {
72    use schema::parts;
73
74    diesel::update(parts::dsl::parts.filter(parts::dsl::id.eq(id)))
75        .set(part)
76        .execute(conn)
77}
78
79pub fn rename_part(
80    conn: &SqliteConnection,
81    oldpn: &str,
82    newpn: &str,
83) -> std::result::Result<usize, diesel::result::Error> {
84    use schema::parts::dsl::*;
85
86    let part = find_part_by_pn(&conn, &oldpn).expect("Old part not found");
87
88    diesel::update(parts.find(part.id))
89        .set(pn.eq(newpn))
90        .execute(conn)
91}
92
93pub fn delete_part(
94    conn: &SqliteConnection,
95    id: &i32,
96) -> std::result::Result<usize, diesel::result::Error> {
97    use schema::parts;
98
99    diesel::delete(parts::dsl::parts.filter(parts::dsl::id.eq(id))).execute(conn)
100}
101
102pub fn find_part_by_pn(
103    conn: &SqliteConnection,
104    pn: &str,
105) -> std::result::Result<Part, diesel::result::Error> {
106    use schema::parts;
107
108    parts::dsl::parts.filter(parts::dsl::pn.eq(pn)).first(conn)
109}
110
111pub fn find_part_by_mpn(
112    conn: &SqliteConnection,
113    mpn: &str,
114) -> std::result::Result<Part, diesel::result::Error> {
115    use schema::parts;
116
117    println!("mpn: \"{}\"", mpn);
118
119    parts::dsl::parts
120        .filter(parts::dsl::mpn.eq(mpn))
121        .first(conn)
122}
123
124pub fn find_part_by_pn_and_ver(
125    conn: &SqliteConnection,
126    pn: &str,
127    ver: &i32,
128) -> std::result::Result<Part, diesel::result::Error> {
129    use schema::parts;
130
131    parts::dsl::parts
132        .filter(parts::dsl::pn.eq(pn))
133        .filter(parts::dsl::ver.eq(ver))
134        .first(conn)
135}
136
137pub fn find_part_by_id(
138    conn: &SqliteConnection,
139    id: &i32,
140) -> std::result::Result<Part, diesel::result::Error> {
141    use schema::parts;
142
143    parts::dsl::parts.filter(parts::dsl::id.eq(id)).first(conn)
144}
145
146pub fn create_bom_line_item(
147    conn: &SqliteConnection,
148    part: &NewPartsParts,
149) -> std::result::Result<usize, diesel::result::Error> {
150    use schema::parts_parts;
151
152    diesel::insert_into(parts_parts::table)
153        .values(part)
154        .execute(conn)
155}
156
157pub fn delete_bom_list_by_id_and_ver(
158    conn: &SqliteConnection,
159    bom_id: &i32,
160    ver: &i32,
161) -> std::result::Result<usize, diesel::result::Error> {
162    use schema::parts_parts::dsl::*;
163
164    // First get list of ids that match the bom_part_id
165    let query = parts_parts
166        .select(id)
167        .filter(bom_part_id.eq(bom_id))
168        .load::<i32>(conn)?;
169
170    // Then make sure that the bom ver is equal. Match against the ids found in the first step
171    let target = parts_parts.filter(bom_ver.eq(ver)).filter(id.eq_any(query));
172
173    // Delete appropriately
174    diesel::delete(target).execute(conn)
175}
176
177// Build related
178
179pub fn create_build(
180    conn: &SqliteConnection,
181    build: &NewUpdateBuild,
182) -> std::result::Result<usize, diesel::result::Error> {
183    use schema::builds;
184
185    diesel::insert_into(builds::table)
186        .values(build)
187        .execute(conn)
188}
189
190pub fn update_build_by_id(
191    conn: &SqliteConnection,
192    id: &i32,
193    entry: &NewUpdateBuild,
194) -> std::result::Result<usize, diesel::result::Error> {
195    use schema::builds;
196
197    diesel::update(builds::dsl::builds.filter(builds::dsl::id.eq(id)))
198        .set(entry)
199        .execute(conn)
200}
201
202pub fn find_builds_by_pn(
203    conn: &SqliteConnection,
204    pn: &str,
205) -> std::result::Result<Vec<Build>, diesel::result::Error> {
206    use schema::builds;
207
208    let part = find_part_by_pn(&conn, &pn).expect("Unable to run parts query.");
209
210    builds::dsl::builds
211        .filter(builds::dsl::part_id.eq(part.id))
212        .load::<Build>(conn)
213}
214
215pub fn find_build_by_id(
216    conn: &SqliteConnection,
217    id: &i32,
218) -> std::result::Result<Build, diesel::result::Error> {
219    use schema::builds;
220
221    builds::dsl::builds
222        .filter(builds::dsl::id.eq(id))
223        .first(conn)
224}
225
226pub fn delete_build(
227    conn: &SqliteConnection,
228    id: &i32,
229) -> std::result::Result<usize, diesel::result::Error> {
230    use schema::builds;
231
232    diesel::delete(builds::dsl::builds.filter(builds::dsl::id.eq(id))).execute(conn)
233}
234
235// Inventory related
236
237pub fn create_inventory(
238    conn: &SqliteConnection,
239    entry: &NewUpdateInventoryEntry,
240) -> std::result::Result<usize, diesel::result::Error> {
241    use schema::inventories;
242
243    diesel::insert_into(inventories::table)
244        .values(entry)
245        .execute(conn)
246}
247
248pub fn update_inventory_by_id(
249    conn: &SqliteConnection,
250    id: &i32,
251    entry: &NewUpdateInventoryEntry,
252) -> std::result::Result<usize, diesel::result::Error> {
253    use schema::inventories;
254
255    diesel::update(inventories::dsl::inventories.filter(inventories::dsl::id.eq(id)))
256        .set(entry)
257        .execute(conn)
258}
259
260pub fn find_inventories_by_part_id(
261    conn: &SqliteConnection,
262    id: &i32,
263) -> std::result::Result<Vec<Inventory>, diesel::result::Error> {
264    use schema::inventories;
265
266    inventories::dsl::inventories
267        .filter(inventories::dsl::part_id.eq(id))
268        .load::<Inventory>(conn)
269}
270
271pub fn test_connection() -> SqliteConnection {
272    // Start a connection from memory
273    let conn = SqliteConnection::establish(":memory:").expect("Unable to establish db in memory!");
274
275    // This will run the necessary migrations.
276    embedded_migrations::run(&conn).expect("Unable to run test migration.");
277
278    // Return the active connection
279    conn
280}
281
282/* START: Part Related Tests */
283mod part_tests {
284
285    #[test]
286    fn create_part_check_if_created() {
287        use super::*;
288        use models::Part;
289        use schema::parts::dsl::*;
290
291        let conn = test_connection();
292
293        // Create NewUpdatePart instance
294        let part = NewUpdatePart {
295            pn: "CAP-0.1U-10V-0402",
296            mpn: "ABCD",
297            descr: "CAP 0.1U 10V 0402",
298            ver: &1,
299            mqty: &1,
300        };
301
302        // Create the part
303        create_part(&conn, &part).expect("Error creating part!");
304
305        // Serach for it and make sure that it matches
306        let found: Part = parts.find(1).first(&conn).unwrap();
307
308        // Make sure these guys are equal
309        assert_eq!(part.pn, found.pn);
310        assert_eq!(part.mpn, found.mpn);
311        assert_eq!(part.descr, found.descr);
312        assert_eq!(*part.ver, found.ver);
313    }
314
315    #[test]
316    #[should_panic]
317    // This is testing the schema more than anything
318    // Only one part with the same PN!
319    fn create_duplicate_pn_should_panic() {
320        use super::*;
321        let conn = test_connection();
322
323        // Create NewUpdatePart instance
324        let part = NewUpdatePart {
325            pn: "CAP-0.1U-10V-0402",
326            mpn: "ABCD",
327            descr: "CAP 0.1U 10V 0402",
328            ver: &1,
329            mqty: &1,
330        };
331
332        // Create the part
333        create_part(&conn, &part).expect("Error creating part!");
334
335        // Create NewUpdatePart instance
336        let part = NewUpdatePart {
337            pn: "CAP-0.1U-10V-0402",
338            mpn: "ABCD-ND",
339            descr: "CAP 0.1U 10V 0402",
340            ver: &1,
341            mqty: &1,
342        };
343
344        // Do it again
345        create_part(&conn, &part).expect("Error creating part!");
346    }
347
348    #[test]
349    #[should_panic]
350    // This is testing the schema more than anything
351    // Only one part with the same MPN!
352    fn create_duplicate_mpn_should_panic() {
353        use super::*;
354        let conn = test_connection();
355
356        // Create NewUpdatePart instance
357        let part = NewUpdatePart {
358            pn: "CAP-0.1U-10V-0402",
359            mpn: "ABCD",
360            descr: "CAP 0.1U 10V 0402",
361            ver: &1,
362            mqty: &1,
363        };
364
365        // Create the part
366        create_part(&conn, &part).expect("Error creating part!");
367
368        // Create NewUpdatePart instance
369        let part = NewUpdatePart {
370            pn: "CAP-0.1U-10V-0402-01",
371            mpn: "ABCD",
372            descr: "CAP 0.1U 10V 0402",
373            ver: &1,
374            mqty: &1,
375        };
376
377        // Do it again
378        create_part(&conn, &part).expect("Error creating part!");
379    }
380
381    #[test]
382    fn create_and_update_part() {
383        use super::*;
384        use models::Part;
385        use schema::parts::dsl::*;
386
387        let conn = test_connection();
388
389        // Create NewUpdatePart instance
390        let part = NewUpdatePart {
391            pn: "CAP-0.1U-10V-0402",
392            mpn: "ABCD",
393            descr: "CAP 0.1U 10V 0402",
394            ver: &1,
395            mqty: &1,
396        };
397
398        // Create the part
399        create_part(&conn, &part).expect("Error creating part!");
400
401        // Get part back
402        let found = find_part_by_pn(&conn, &part.pn).expect("Error getting part back.");
403
404        // Update the value
405        let part = NewUpdatePart {
406            pn: "CAP-0.1U-10V-0402",
407            mpn: "ABCD",
408            descr: "CAP 0.1 10V 0402 GOOD", // Only changing this guy
409            ver: &1,
410            mqty: &1,
411        };
412
413        // Update the part
414        update_part(&conn, &found.id, &part).expect("Error creating part!");
415
416        // Serach for it and make sure that it matches
417        let found: Part = parts.find(1).first(&conn).unwrap();
418
419        // Make sure these guys are equal
420        assert_eq!(part.descr, found.descr);
421    }
422}
423
424/* START: Inventory Related Tests */
425mod inventory_tests {}
426
427/* START: Build Related Tests */
428mod build_tests {}