1use crate as eg;
3use eg::event::EgEvent;
4use eg::idl;
5use eg::osrf::params::ApiParams;
6use eg::result::{EgError, EgResult};
7use eg::Client;
8use eg::ClientSession;
9use eg::EgValue;
10
11const DEFAULT_TIMEOUT: u64 = 60;
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum Personality {
16 Cstore,
17 Pcrud,
18 ReporterStore,
19}
20
21impl From<&str> for Personality {
22 fn from(s: &str) -> Self {
23 match s {
24 "open-ils.pcrud" => Self::Pcrud,
25 "open-ils.reporter-store" => Self::ReporterStore,
26 _ => Self::Cstore,
27 }
28 }
29}
30
31impl From<&Personality> for &str {
32 fn from(p: &Personality) -> &'static str {
33 match *p {
34 Personality::Cstore => "open-ils.cstore",
35 Personality::Pcrud => "open-ils.pcrud",
36 Personality::ReporterStore => "open-ils.reporter-store",
37 }
38 }
39}
40
41pub struct Editor {
50 client: Client,
51 session: Option<ClientSession>,
52
53 personality: Personality,
54 authtoken: Option<String>,
55 authtime: Option<usize>,
56 requestor: Option<EgValue>,
57 timeout: u64,
58
59 xact_wanted: bool,
62
63 xact_id: Option<String>,
65
66 last_event: Option<EgEvent>,
68
69 has_pending_changes: bool,
70}
71
72impl Clone for Editor {
73 fn clone(&self) -> Editor {
74 let mut e = Editor::new(&self.client);
75 e.personality = self.personality().clone();
76 e.authtoken = self.authtoken().map(str::to_string);
77 e.requestor = self.requestor().cloned();
78 e
79 }
80}
81
82impl Editor {
83 pub fn new(client: &Client) -> Self {
85 Editor {
86 client: client.clone(),
87 personality: "".into(),
88 timeout: DEFAULT_TIMEOUT,
89 xact_wanted: false,
90 xact_id: None,
91 session: None,
92 authtoken: None,
93 authtime: None,
94 requestor: None,
95 last_event: None,
96 has_pending_changes: false,
97 }
98 }
99
100 pub fn set_timeout(&mut self, timeout: u64) {
102 self.timeout = timeout;
103 }
104
105 pub fn reset_timeout(&mut self) {
107 self.timeout = DEFAULT_TIMEOUT;
108 }
109
110 pub fn client_mut(&mut self) -> &mut Client {
111 &mut self.client
112 }
113
114 pub fn has_pending_changes(&self) -> bool {
120 self.has_pending_changes
121 }
122
123 pub fn with_auth(client: &Client, authtoken: &str) -> Self {
125 let mut editor = Editor::new(client);
126 editor.authtoken = Some(authtoken.to_string());
127 editor
128 }
129
130 pub fn with_auth_xact(client: &Client, authtoken: &str) -> Self {
133 let mut editor = Editor::new(client);
134 editor.authtoken = Some(authtoken.to_string());
135 editor.xact_wanted = true;
136 editor
137 }
138
139 pub fn checkauth(&mut self) -> EgResult<bool> {
144 let token = match self.authtoken() {
145 Some(t) => t,
146 None => return Ok(false),
147 };
148
149 let service = "open-ils.auth";
150 let method = "open-ils.auth.session.retrieve";
151 let params = vec![EgValue::from(token), EgValue::from(true)];
152
153 let mut ses = self.client.session(service);
154 let resp_op = ses.request(method, params)?.first()?;
155
156 if let Some(user) = resp_op {
157 if let Some(evt) = EgEvent::parse(&user) {
158 log::debug!("Editor checkauth call returned non-success event: {}", evt);
159 self.set_last_event(evt);
160 return Ok(false);
161 }
162
163 if user.has_key("usrname") {
164 self.requestor = Some(user);
165 return Ok(true);
166 }
167 }
168
169 log::debug!("Editor checkauth call returned unexpected data");
170
171 self.set_last_event(EgEvent::new("NO_SESSION"));
173 Ok(false)
174 }
175
176 pub fn clear_auth(&mut self) -> EgResult<()> {
179 self.requestor = None;
180
181 let token = match self.authtoken.take() {
182 Some(t) => t,
183 None => return Ok(()),
184 };
185
186 let service = "open-ils.auth";
187 let method = "open-ils.auth.session.retrieve";
188
189 let mut ses = self.client.session(service);
190 ses.request(method, token).map(|_| ())
191 }
192
193 pub fn personality(&self) -> &Personality {
194 &self.personality
195 }
196
197 pub fn authtoken(&self) -> Option<&str> {
198 self.authtoken.as_deref()
199 }
200
201 pub fn set_authtoken(&mut self, token: &str) {
203 self.authtoken = Some(token.to_string())
204 }
205
206 pub fn apply_authtoken(&mut self, token: &str) -> EgResult<bool> {
208 self.set_authtoken(token);
209 self.checkauth()
210 }
211
212 pub fn authtime(&self) -> Option<usize> {
213 self.authtime
214 }
215
216 fn has_xact_id(&self) -> bool {
217 self.xact_id.is_some()
218 }
219
220 pub fn requestor_id(&self) -> EgResult<i64> {
222 if let Some(req) = self.requestor() {
223 req.id()
224 } else {
225 Err("Editor has no requestor".into())
226 }
227 }
228
229 pub fn requestor_ws_ou(&self) -> Option<i64> {
233 if let Some(req) = self.requestor() {
234 req["ws_ou"].as_int()
235 } else {
236 None
237 }
238 }
239
240 pub fn requestor_ws_id(&self) -> Option<i64> {
244 if let Some(r) = self.requestor() {
245 r["wsid"].as_int()
246 } else {
247 None
248 }
249 }
250
251 pub fn requestor_home_ou(&self) -> EgResult<i64> {
255 if let Some(r) = self.requestor() {
256 r["home_ou"].int()
257 } else {
258 Err("Editor has no requestor".into())
259 }
260 }
261
262 pub fn perm_org(&self) -> i64 {
263 self.requestor_ws_ou()
264 .unwrap_or(self.requestor_home_ou().unwrap_or(-1))
265 }
266
267 pub fn requestor(&self) -> Option<&EgValue> {
268 self.requestor.as_ref()
269 }
270
271 pub fn has_requestor(&self) -> bool {
273 self.requestor.is_some()
274 }
275
276 pub fn set_requestor(&mut self, r: &EgValue) {
277 self.requestor = Some(r.clone())
278 }
279
280 pub fn give_requestor(&mut self, r: EgValue) {
282 self.requestor = Some(r);
283 }
284
285 pub fn last_event(&self) -> Option<&EgEvent> {
286 self.last_event.as_ref()
287 }
288
289 pub fn take_last_event(&mut self) -> Option<EgEvent> {
290 self.last_event.take()
291 }
292
293 pub fn event_as_err(&self) -> EgError {
294 match self.last_event() {
295 Some(e) => EgError::from_event(e.clone()),
296 None => EgError::from_string("Editor Has No Event".to_string()),
297 }
298 }
299
300 pub fn event(&self) -> EgValue {
303 match self.last_event() {
304 Some(e) => e.to_value(),
305 None => EgValue::Null,
306 }
307 }
308
309 fn set_last_event(&mut self, evt: EgEvent) {
310 self.last_event = Some(evt);
311 }
312
313 pub fn die_event(&mut self) -> EgError {
318 if let Err(e) = self.rollback() {
319 return e;
320 }
321 match self.last_event() {
322 Some(e) => EgError::from_event(e.clone()),
323 None => EgError::from_string("Die-Event Called With No Event".to_string()),
324 }
325 }
326
327 pub fn die_event_msg(&mut self, msg: &str) -> EgError {
332 if let Err(e) = self.rollback() {
333 return e;
334 }
335 match self.last_event() {
336 Some(e) => {
337 let mut e2 = e.clone();
338 e2.set_debug(msg);
339 EgError::from_event(e2)
340 }
341 None => EgError::from_string(msg.to_string()),
342 }
343 }
344
345 pub fn rollback(&mut self) -> EgResult<()> {
347 self.xact_rollback()?;
348 self.disconnect()
349 }
350
351 pub fn commit(&mut self) -> EgResult<()> {
353 self.xact_commit()?;
354 self.disconnect()
355 }
356
357 fn app_method(&self, part: &str) -> String {
359 let p: &str = self.personality().into();
360 format!("{p}.{}", part)
361 }
362
363 pub fn in_transaction(&self) -> bool {
364 if let Some(ref ses) = self.session {
365 ses.connected() && self.has_xact_id()
366 } else {
367 false
368 }
369 }
370
371 pub fn xact_rollback(&mut self) -> EgResult<()> {
375 if self.in_transaction() {
376 self.request_np(&self.app_method("transaction.rollback"))?;
377 }
378
379 self.xact_id = None;
380 self.xact_wanted = false;
381 self.has_pending_changes = false;
382
383 Ok(())
384 }
385
386 pub fn xact_begin(&mut self) -> EgResult<()> {
388 self.connect()?;
389 if let Some(id) = self.request_np(&self.app_method("transaction.begin"))? {
390 if let Some(id_str) = id.as_str() {
391 log::debug!("New transaction started with id {}", id_str);
392 self.xact_id = Some(id_str.to_string());
393 }
394 }
395 Ok(())
396 }
397
398 pub fn xact_commit(&mut self) -> EgResult<()> {
402 if self.in_transaction() {
403 let xact_id = self.xact_id.take().unwrap();
407 let method = self.app_method("transaction.commit");
408 self.request(&method, xact_id)?;
409 }
410
411 self.xact_id = None;
412 self.xact_wanted = false;
413 self.has_pending_changes = false;
414
415 Ok(())
416 }
417
418 pub fn disconnect(&mut self) -> EgResult<()> {
420 self.xact_rollback()?;
421
422 if let Some(ref ses) = self.session {
423 ses.disconnect()?;
424 }
425 self.session = None;
426 Ok(())
427 }
428
429 pub fn connect(&mut self) -> EgResult<()> {
431 if let Some(ref ses) = self.session {
432 if ses.connected() {
433 return Ok(());
435 }
436 }
437 self.session().connect()?;
438 Ok(())
439 }
440
441 fn request_np(&mut self, method: &str) -> EgResult<Option<EgValue>> {
445 let params: Vec<EgValue> = Vec::new();
446 self.request(method, params)
447 }
448
449 fn logtag(&self) -> String {
450 let requestor = match self.requestor() {
451 Some(req) => req.id().unwrap_or(0),
452 _ => 0,
453 };
454
455 format!(
456 "editor[{}|{}]",
457 match self.has_xact_id() {
458 true => "1",
459 _ => "0",
460 },
461 requestor
462 )
463 }
464
465 fn args_to_string(&self, params: &ApiParams) -> String {
467 let mut buf = String::new();
468 for p in params.params().iter() {
469 if let Some(pkv) = p.pkey_value() {
470 if pkv.is_null() {
471 buf.push_str("<new object>");
472 } else {
473 buf.push_str(&pkv.dump());
474 }
475 } else {
476 buf.push_str(&p.dump());
478 }
479
480 buf.push(' ');
481 }
482
483 buf.trim().to_string()
484 }
485
486 fn request(&mut self, method: &str, params: impl Into<ApiParams>) -> EgResult<Option<EgValue>> {
490 let params: ApiParams = params.into();
491
492 log::info!(
493 "{} request {} {}",
494 self.logtag(),
495 method,
496 self.args_to_string(¶ms)
497 );
498
499 if method.contains("create") || method.contains("update") || method.contains("delete") {
500 if !self.has_xact_id() {
501 self.disconnect()?;
502 Err(format!(
503 "Attempt to update DB while not in a transaction : {method}"
504 ))?;
505 }
506
507 if params.params().is_empty() {
508 return Err("Create/update/delete calls require a parameter".into());
509 }
510
511 log::info!(
513 "ACT:{} request {} {}",
514 self.logtag(),
515 method,
516 self.args_to_string(¶ms)
517 );
518 }
519
520 let mut req = self.session().request(method, params).or_else(|e| {
521 self.rollback()?;
522 Err(e)
523 })?;
524
525 req.first_with_timeout(self.timeout)
526 }
527
528 fn session(&mut self) -> &mut ClientSession {
537 if let Some(ref ses) = self.session {
538 if ses.connected() {
539 return self.session.as_mut().unwrap();
540 }
541 }
542 self.session = Some(self.client.session(self.personality().into()));
543 self.session.as_mut().unwrap()
544 }
545
546 fn get_fieldmapper(&self, value: &EgValue) -> EgResult<String> {
550 if let Some(cls) = value.idl_class() {
551 if let Some(fm) = cls.fieldmapper() {
552 return Ok(fm.replace("::", "."));
553 }
554 }
555 Err(format!("Cannot determine fieldmapper from {}", value.dump()).into())
556 }
557
558 fn get_fieldmapper_from_classname(&self, classname: &str) -> EgResult<String> {
559 let cls = idl::get_class(classname)?;
560 if let Some(fm) = cls.fieldmapper() {
561 return Ok(fm.replace("::", "."));
562 }
563 Err(format!("Cannot determine fieldmapper from {classname}").into())
564 }
565
566 pub fn json_query(&mut self, query: EgValue) -> EgResult<Vec<EgValue>> {
568 self.json_query_with_ops(query, EgValue::Null)
569 }
570
571 pub fn json_query_with_ops(&mut self, query: EgValue, ops: EgValue) -> EgResult<Vec<EgValue>> {
573 let method = self.app_method("json_query.atomic");
574
575 let mut params: ApiParams = query.into();
576 if !ops.is_null() {
577 params.add(ops);
578 }
579
580 if let Some(EgValue::Array(vec)) = self.request(&method, params)? {
581 return Ok(vec);
582 }
583
584 Err(format!("Unexpected response to method {method}").into())
585 }
586
587 pub fn retrieve(
589 &mut self,
590 idlclass: &str,
591 id: impl Into<ApiParams>,
592 ) -> EgResult<Option<EgValue>> {
593 self.retrieve_with_ops(idlclass, id, EgValue::Null)
594 }
595
596 pub fn retrieve_with_ops(
599 &mut self,
600 idlclass: &str,
601 id: impl Into<ApiParams>,
602 ops: EgValue, ) -> EgResult<Option<EgValue>> {
604 let fmapper = self.get_fieldmapper_from_classname(idlclass)?;
605
606 let method = self.app_method(&format!("direct.{fmapper}.retrieve"));
607
608 let mut params: ApiParams = id.into();
609 if !ops.is_null() {
610 params.add(ops);
611 }
612
613 let resp_op = self.request(&method, params)?;
614
615 if resp_op.is_none() {
616 let key = fmapper.replace('.', "_").to_uppercase();
618 self.set_last_event(EgEvent::new(&format!("{key}_NOT_FOUND")));
619 }
620
621 Ok(resp_op)
622 }
623
624 pub fn search(&mut self, idlclass: &str, query: EgValue) -> EgResult<Vec<EgValue>> {
625 self.search_with_ops(idlclass, query, EgValue::Null)
626 }
627
628 pub fn search_with_ops(
629 &mut self,
630 idlclass: &str,
631 query: EgValue,
632 ops: EgValue, ) -> EgResult<Vec<EgValue>> {
634 let fmapper = self.get_fieldmapper_from_classname(idlclass)?;
635
636 let method = self.app_method(&format!("direct.{fmapper}.search.atomic"));
637
638 let mut params: ApiParams = query.into();
639 if !ops.is_null() {
640 params.add(ops);
641 }
642
643 if let Some(EgValue::Array(vec)) = self.request(&method, params)? {
644 return Ok(vec);
645 }
646
647 Err(format!("Unexpected response to method {method}").into())
648 }
649
650 pub fn update(&mut self, object: EgValue) -> EgResult<()> {
652 if !self.has_xact_id() {
653 return Err("Transaction required for UPDATE".into());
654 }
655
656 let fmapper = self.get_fieldmapper(&object)?;
657
658 let method = self.app_method(&format!("direct.{fmapper}.update"));
659
660 if self.request(&method, object)?.is_none() {
663 return Err("Update returned no response".into());
664 }
665
666 self.has_pending_changes = true;
667
668 Ok(())
669 }
670
671 pub fn create(&mut self, object: EgValue) -> EgResult<EgValue> {
673 if !self.has_xact_id() {
674 return Err("Transaction required for CREATE".into());
675 }
676
677 let fmapper = self.get_fieldmapper(&object)?;
678
679 let method = self.app_method(&format!("direct.{fmapper}.create"));
680
681 if let Some(resp) = self.request(&method, object)? {
682 if let Some(pkey) = resp.pkey_value() {
683 log::info!("Created new {fmapper} object with pkey: {}", pkey.dump());
684 } else {
685 log::debug!("Created new {fmapper} object: {resp:?}");
687 }
688
689 self.has_pending_changes = true;
690
691 Ok(resp)
692 } else {
693 Err("Create returned no response".into())
694 }
695 }
696
697 pub fn delete(&mut self, object: EgValue) -> EgResult<EgValue> {
701 if !self.has_xact_id() {
702 return Err("Transaction required for DELETE".into());
703 }
704
705 let fmapper = self.get_fieldmapper(&object)?;
706
707 let method = self.app_method(&format!("direct.{fmapper}.delete"));
708
709 if let Some(resp) = self.request(&method, object)? {
710 self.has_pending_changes = true;
711 Ok(resp)
712 } else {
713 Err("Create returned no response".into())
714 }
715 }
716
717 pub fn allowed(&mut self, perm: &str) -> EgResult<bool> {
721 self.allowed_maybe_at(perm, None)
722 }
723
724 pub fn allowed_at(&mut self, perm: &str, org_id: i64) -> EgResult<bool> {
727 self.allowed_maybe_at(perm, Some(org_id))
728 }
729
730 fn allowed_maybe_at(&mut self, perm: &str, org_id_op: Option<i64>) -> EgResult<bool> {
731 let user_id = match self.requestor_id() {
732 Ok(v) => v,
733 Err(_) => return Ok(false),
734 };
735
736 let org_id = match org_id_op {
737 Some(i) => i,
738 None => self.perm_org(),
739 };
740
741 let query = eg::hash! {
742 "select": {
743 "au": [ {
744 "transform": "permission.usr_has_perm",
745 "alias": "has_perm",
746 "column": "id",
747 "params": eg::array! [perm, org_id]
748 } ]
749 },
750 "from": "au",
751 "where": {"id": user_id},
752 };
753
754 let resp = self.json_query(query)?;
755 let has_perm = resp[0]["has_perm"].boolish();
756
757 if !has_perm {
758 let mut evt = EgEvent::new("PERM_FAILURE");
759 evt.set_ils_perm(perm);
760 if org_id > 0 {
761 evt.set_ils_perm_loc(org_id);
762 }
763 self.set_last_event(evt);
764 }
765
766 Ok(has_perm)
767 }
768
769 pub fn send_recv_one(
773 &mut self,
774 service: &str,
775 method: &str,
776 params: impl Into<ApiParams>,
777 ) -> EgResult<Option<EgValue>> {
778 self.client_mut().send_recv_one(service, method, params)
779 }
780}