sip2/
spec.rs

1//! SIP2 Specification as a collection of static values.
2use std::fmt;
3
4pub const SIP_PROTOCOL_VERSION: &str = "2.00";
5pub const LINE_TERMINATOR: char = '\r';
6pub const SIP_DATE_FORMAT: &str = "%Y%m%d    %H%M%S";
7
8/// Fee Paid Payment Types
9#[derive(Debug, PartialEq, Clone, Copy)]
10pub enum PayType {
11    Cash,
12    Visa,
13    CreditCard,
14    Check,
15}
16
17impl TryFrom<&str> for PayType {
18    type Error = String;
19
20    fn try_from(pt: &str) -> Result<PayType, Self::Error> {
21        match pt {
22            "00" => Ok(Self::Cash),
23            "01" => Ok(Self::Visa),
24            "02" => Ok(Self::CreditCard),
25            "05" => Ok(Self::Check),
26            _ => Err(format!("Unknown payment type code: {pt}")),
27        }
28    }
29}
30
31impl From<PayType> for &'static str {
32    fn from(pt: PayType) -> &'static str {
33        match pt {
34            PayType::Cash => "00",
35            PayType::Visa => "01",
36            PayType::CreditCard => "02",
37            PayType::Check => "05",
38        }
39    }
40}
41
42/// Fee Paid Fee Types
43#[derive(Debug, PartialEq, Clone, Copy)]
44pub enum FeeType {
45    OtherUnknown,
46    Administrative,
47    Damage,
48    Overdue,
49    Processing,
50    Rental,
51    Replacement,
52    ComputerAccessCharge,
53    HoldFee,
54}
55
56impl TryFrom<&str> for FeeType {
57    type Error = String;
58
59    fn try_from(ft: &str) -> Result<FeeType, Self::Error> {
60        match ft {
61            "01" => Ok(Self::OtherUnknown),
62            "02" => Ok(Self::Administrative),
63            "03" => Ok(Self::Damage),
64            "04" => Ok(Self::Overdue),
65            "05" => Ok(Self::Processing),
66            "06" => Ok(Self::Rental),
67            "07" => Ok(Self::Replacement),
68            "08" => Ok(Self::ComputerAccessCharge),
69            "09" => Ok(Self::HoldFee),
70            _ => Err(format!("Unknown fee type: {ft}")),
71        }
72    }
73}
74
75impl From<FeeType> for &'static str {
76    fn from(ft: FeeType) -> &'static str {
77        match ft {
78            FeeType::OtherUnknown => "01",
79            FeeType::Administrative => "02",
80            FeeType::Damage => "03",
81            FeeType::Overdue => "04",
82            FeeType::Processing => "05",
83            FeeType::Rental => "06",
84            FeeType::Replacement => "07",
85            FeeType::ComputerAccessCharge => "08",
86            FeeType::HoldFee => "09",
87        }
88    }
89}
90
91/// Fixed field definition with label and field length
92#[derive(PartialEq, Debug)]
93pub struct FixedField {
94    /// For documentation and debugging purposes.
95    ///
96    /// This value does not appear in any messages.
97    pub label: &'static str,
98
99    /// Length of the fixed field.
100    ///
101    /// Fixed field values are always ASCII, this is essentially
102    /// the number of characters in the fixed field.
103    pub length: usize,
104}
105
106impl fmt::Display for FixedField {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        write!(f, "{} ({})", self.label, self.length)
109    }
110}
111
112/// Field definition with label and 2-character code.
113#[derive(PartialEq, Debug)]
114pub struct Field {
115    /// For documentation and debugging purposes.
116    ///
117    /// This value does not appear in any messages.
118    pub label: &'static str,
119
120    /// 2-Character SIP Field Code
121    pub code: &'static str,
122}
123
124impl fmt::Display for Field {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        write!(f, "{} {}", self.code, self.label)
127    }
128}
129
130impl Field {
131    /// Get a Field from its 2-character code.
132    ///
133    /// ```
134    /// use sip2::spec;
135    /// let f = &spec::F_LOGIN_UID;
136    /// let f2 = spec::Field::from_code(f.code).unwrap();
137    /// assert_eq!(f2.code, f.code);
138    /// ```
139    pub fn from_code(code: &str) -> Option<&'static Field> {
140        match code {
141            f if f == F_LOGIN_UID.code => Some(&F_LOGIN_UID),
142            f if f == F_LOGIN_PWD.code => Some(&F_LOGIN_PWD),
143            f if f == F_PATRON_ID.code => Some(&F_PATRON_ID),
144            f if f == F_PATRON_IDENT.code => Some(&F_PATRON_IDENT),
145            f if f == F_ITEM_IDENT.code => Some(&F_ITEM_IDENT),
146            f if f == F_TERMINAL_PWD.code => Some(&F_TERMINAL_PWD),
147            f if f == F_PATRON_PWD.code => Some(&F_PATRON_PWD),
148            f if f == F_PERSONAL_NAME.code => Some(&F_PERSONAL_NAME),
149            f if f == F_SCREEN_MSG.code => Some(&F_SCREEN_MSG),
150            f if f == F_PRINT_LINE.code => Some(&F_PRINT_LINE),
151            f if f == F_DUE_DATE.code => Some(&F_DUE_DATE),
152            f if f == F_TITLE_IDENT.code => Some(&F_TITLE_IDENT),
153            f if f == F_BLOCKED_CARD_MSG.code => Some(&F_BLOCKED_CARD_MSG),
154            f if f == F_LIBRARY_NAME.code => Some(&F_LIBRARY_NAME),
155            f if f == F_TERMINAL_LOCATION.code => Some(&F_TERMINAL_LOCATION),
156            f if f == F_INSTITUTION_ID.code => Some(&F_INSTITUTION_ID),
157            f if f == F_CURRENT_LOCATION.code => Some(&F_CURRENT_LOCATION),
158            f if f == F_PERMANENT_LOCATION.code => Some(&F_PERMANENT_LOCATION),
159            f if f == F_HOLD_ITEMS.code => Some(&F_HOLD_ITEMS),
160            f if f == F_OVERDUE_ITEMS.code => Some(&F_OVERDUE_ITEMS),
161            f if f == F_CHARGED_ITEMS.code => Some(&F_CHARGED_ITEMS),
162            f if f == F_FINE_ITEMS.code => Some(&F_FINE_ITEMS),
163            f if f == F_SEQUENCE_NUMBER.code => Some(&F_SEQUENCE_NUMBER),
164            f if f == F_CHECKSUM.code => Some(&F_CHECKSUM),
165            f if f == F_HOME_ADDRESS.code => Some(&F_HOME_ADDRESS),
166            f if f == F_EMAIL_ADDRESS.code => Some(&F_EMAIL_ADDRESS),
167            f if f == F_HOME_PHONE.code => Some(&F_HOME_PHONE),
168            f if f == F_OWNER.code => Some(&F_OWNER),
169            f if f == F_CURRENCY.code => Some(&F_CURRENCY),
170            f if f == F_CANCEL.code => Some(&F_CANCEL),
171            f if f == F_TRANSACTION_ID.code => Some(&F_TRANSACTION_ID),
172            f if f == F_VALID_PATRON.code => Some(&F_VALID_PATRON),
173            f if f == F_RENEWED_ITEMS.code => Some(&F_RENEWED_ITEMS),
174            f if f == F_UNRENEWED_ITEMS.code => Some(&F_UNRENEWED_ITEMS),
175            f if f == F_FEE_ACKNOWLEGED.code => Some(&F_FEE_ACKNOWLEGED),
176            f if f == F_START_ITEM.code => Some(&F_START_ITEM),
177            f if f == F_END_ITEM.code => Some(&F_END_ITEM),
178            f if f == F_QUEUE_POSITION.code => Some(&F_QUEUE_POSITION),
179            f if f == F_PICKUP_LOCATION.code => Some(&F_PICKUP_LOCATION),
180            f if f == F_RECALL_ITEMS.code => Some(&F_RECALL_ITEMS),
181            f if f == F_FEE_TYPE.code => Some(&F_FEE_TYPE),
182            f if f == F_FEE_LIMIT.code => Some(&F_FEE_LIMIT),
183            f if f == F_FEE_AMOUNT.code => Some(&F_FEE_AMOUNT),
184            f if f == F_EXPIRE_DATE.code => Some(&F_EXPIRE_DATE),
185            f if f == F_SUPPORTED_MESSAGES.code => Some(&F_SUPPORTED_MESSAGES),
186            f if f == F_HOLD_TYPE.code => Some(&F_HOLD_TYPE),
187            f if f == F_HOLD_ITEMS_LIMIT.code => Some(&F_HOLD_ITEMS_LIMIT),
188            f if f == F_OVERDUE_ITEMS_LIST.code => Some(&F_OVERDUE_ITEMS_LIST),
189            f if f == F_CHARGED_ITEMS_LIMIT.code => Some(&F_CHARGED_ITEMS_LIMIT),
190            f if f == F_UNAVAIL_HOLD_ITEMS.code => Some(&F_UNAVAIL_HOLD_ITEMS),
191            f if f == F_HOLD_QUEUE_LENGTH.code => Some(&F_HOLD_QUEUE_LENGTH),
192            f if f == F_FEE_IDENTIFIER.code => Some(&F_FEE_IDENTIFIER),
193            f if f == F_ITEM_PROPERTIES.code => Some(&F_ITEM_PROPERTIES),
194            f if f == F_SECURITY_INHIBIT.code => Some(&F_SECURITY_INHIBIT),
195            f if f == F_RECALL_DATE.code => Some(&F_RECALL_DATE),
196            f if f == F_MEDIA_TYPE.code => Some(&F_MEDIA_TYPE),
197            f if f == F_SORT_BIN.code => Some(&F_SORT_BIN),
198            f if f == F_HOLD_PICKUP_DATE.code => Some(&F_HOLD_PICKUP_DATE),
199            f if f == F_LOGIN_USER_ID.code => Some(&F_LOGIN_USER_ID),
200            f if f == F_LOCATION_CODE.code => Some(&F_LOCATION_CODE),
201            f if f == F_VALID_PATRON_PWD.code => Some(&F_VALID_PATRON_PWD),
202            f if f == F_INET_PROFILE.code => Some(&F_INET_PROFILE),
203            f if f == F_CALL_NUMBER.code => Some(&F_CALL_NUMBER),
204            f if f == F_COLLECTION_CODE.code => Some(&F_COLLECTION_CODE),
205            f if f == F_ALERT_TYPE.code => Some(&F_ALERT_TYPE),
206            f if f == F_HOLD_PATRON_ID.code => Some(&F_HOLD_PATRON_ID),
207            f if f == F_HOLD_PATRON_NAME.code => Some(&F_HOLD_PATRON_NAME),
208            f if f == F_DEST_LOCATION.code => Some(&F_DEST_LOCATION),
209            f if f == F_PATRON_EXPIRE_DATE.code => Some(&F_PATRON_EXPIRE_DATE),
210            f if f == F_PATRON_DOB.code => Some(&F_PATRON_DOB),
211            f if f == F_PATRON_CLASS.code => Some(&F_PATRON_CLASS),
212            f if f == F_REGISTER_LOGIN.code => Some(&F_REGISTER_LOGIN),
213            f if f == F_CHECK_NUMBER.code => Some(&F_CHECK_NUMBER),
214            _ => None,
215        }
216    }
217}
218
219/// SIP message definition with 2-character code, label, and
220/// fixed fields.
221///
222/// No attempt is made to specify which spec::Field's are used for
223/// each Message since use in the wild varies wildly.
224#[derive(PartialEq, Debug)]
225pub struct Message {
226    /// Two-Character SIP Message Code
227    pub code: &'static str,
228
229    /// For documentation and debugging purposes.
230    ///
231    /// This value does not appear in any messages.
232    pub label: &'static str,
233
234    /// Fixed fields used by this message, defined in the order they
235    /// appear in the compiled message.
236    pub fixed_fields: &'static [&'static FixedField],
237}
238
239impl Message {
240    /// Maps a message code to a message spec.
241    ///
242    /// ```
243    /// use sip2::spec;
244    /// let msg = &spec::M_LOGIN;
245    /// let msg2 = spec::Message::from_code(&spec::M_LOGIN.code).unwrap();
246    /// assert_eq!(msg2.code, msg.code);
247    /// ```
248    pub fn from_code(code: &str) -> Option<&'static Message> {
249        match code {
250            m if m == M_SC_STATUS.code => Some(&M_SC_STATUS),
251            m if m == M_ACS_STATUS.code => Some(&M_ACS_STATUS),
252            m if m == M_LOGIN.code => Some(&M_LOGIN),
253            m if m == M_LOGIN_RESP.code => Some(&M_LOGIN_RESP),
254            m if m == M_ITEM_INFO.code => Some(&M_ITEM_INFO),
255            m if m == M_ITEM_INFO_RESP.code => Some(&M_ITEM_INFO_RESP),
256            m if m == M_PATRON_STATUS.code => Some(&M_PATRON_STATUS),
257            m if m == M_PATRON_STATUS_RESP.code => Some(&M_PATRON_STATUS_RESP),
258            m if m == M_PATRON_INFO.code => Some(&M_PATRON_INFO),
259            m if m == M_PATRON_INFO_RESP.code => Some(&M_PATRON_INFO_RESP),
260            m if m == M_CHECKOUT.code => Some(&M_CHECKOUT),
261            m if m == M_CHECKOUT_RESP.code => Some(&M_CHECKOUT_RESP),
262            m if m == M_RENEW.code => Some(&M_RENEW),
263            m if m == M_RENEW_RESP.code => Some(&M_RENEW_RESP),
264            m if m == M_RENEW_ALL.code => Some(&M_RENEW_ALL),
265            m if m == M_RENEW_ALL_RESP.code => Some(&M_RENEW_ALL_RESP),
266            m if m == M_CHECKIN.code => Some(&M_CHECKIN),
267            m if m == M_CHECKIN_RESP.code => Some(&M_CHECKIN_RESP),
268            m if m == M_HOLD.code => Some(&M_HOLD),
269            m if m == M_HOLD_RESP.code => Some(&M_HOLD_RESP),
270            m if m == M_FEE_PAID.code => Some(&M_FEE_PAID),
271            m if m == M_FEE_PAID_RESP.code => Some(&M_FEE_PAID_RESP),
272            m if m == M_END_PATRON_SESSION.code => Some(&M_END_PATRON_SESSION),
273            m if m == M_END_PATRON_SESSION_RESP.code => Some(&M_END_PATRON_SESSION_RESP),
274            m if m == M_END_SESSION.code => Some(&M_END_SESSION),
275            m if m == M_END_SESSION_RESP.code => Some(&M_END_SESSION_RESP),
276            m if m == M_BLOCK_PATRON.code => Some(&M_BLOCK_PATRON),
277            m if m == M_REQUEST_ACS_RESEND.code => Some(&M_REQUEST_ACS_RESEND),
278            _ => None,
279        }
280    }
281}
282
283// -------------------------------------------------------------------------
284// Fixed Fields
285// -------------------------------------------------------------------------
286
287type FF = FixedField; // local shorthand
288
289pub const FF_DATE: FF = FF {
290    length: 18,
291    label: "transaction date",
292};
293pub const FF_OK: FF = FF {
294    length: 1,
295    label: "ok",
296};
297pub const FF_UID_ALGO: FF = FF {
298    length: 1,
299    label: "uid algorithm",
300};
301pub const FF_PWD_ALGO: FF = FF {
302    length: 1,
303    label: "pwd algorithm",
304};
305pub const FF_FEE_TYPE: FF = FF {
306    length: 2,
307    label: "fee type",
308};
309pub const FF_PAYMENT_TYPE: FF = FF {
310    length: 2,
311    label: "payment type",
312};
313pub const FF_CURRENCY: FF = FF {
314    length: 3,
315    label: "currency type",
316};
317pub const FF_PAYMENT_ACCEPTED: FF = FF {
318    length: 1,
319    label: "payment accepted",
320};
321pub const FF_CIRCULATION_STATUS: FF = FF {
322    length: 2,
323    label: "circulation status",
324};
325pub const FF_SECURITY_MARKER: FF = FF {
326    length: 2,
327    label: "security marker",
328};
329pub const FF_LANGUAGE: FF = FF {
330    length: 3,
331    label: "language",
332};
333pub const FF_PATRON_STATUS: FF = FF {
334    length: 14,
335    label: "patron status",
336};
337pub const FF_SUMMARY: FF = FF {
338    length: 10,
339    label: "summary",
340};
341pub const FF_HOLD_ITEMS_COUNT: FF = FF {
342    length: 4,
343    label: "hold items count",
344};
345pub const FF_OD_ITEMS_COUNT: FF = FF {
346    length: 4,
347    label: "overdue items count",
348};
349pub const FF_CH_ITEMS_COUNT: FF = FF {
350    length: 4,
351    label: "charged items count",
352};
353pub const FF_FINE_ITEMS_COUNT: FF = FF {
354    length: 4,
355    label: "fine items count",
356};
357pub const FF_RECALL_ITEMS_COUNT: FF = FF {
358    length: 4,
359    label: "recall items count",
360};
361pub const FF_UNAVAIL_HOLDS_COUNT: FF = FF {
362    length: 4,
363    label: "unavail holds count",
364};
365pub const FF_SC_RENEWAL_POLICY: FF = FF {
366    length: 1,
367    label: "sc renewal policy",
368};
369pub const FF_NO_BLOCK: FF = FF {
370    length: 1,
371    label: "no block",
372};
373pub const FF_NB_DUE_DATE: FF = FF {
374    length: 18,
375    label: "nb due date",
376};
377pub const FF_STATUS_CODE: FF = FF {
378    length: 1,
379    label: "status code",
380};
381pub const FF_MAX_PRINT_WIDTH: FF = FF {
382    length: 3,
383    label: "max print width",
384};
385pub const FF_PROTOCOL_VERSION: FF = FF {
386    length: 4,
387    label: "protocol version",
388};
389pub const FF_RENEW_OK: FF = FF {
390    length: 1,
391    label: "renewal ok",
392};
393pub const FF_MAGNETIC_MEDIA: FF = FF {
394    length: 1,
395    label: "magnetic media",
396};
397pub const FF_DESENSITIZE: FF = FF {
398    length: 1,
399    label: "desensitize",
400};
401pub const FF_RESENSITIZE: FF = FF {
402    length: 1,
403    label: "resensitize",
404};
405pub const FF_RETURN_DATE: FF = FF {
406    length: 18,
407    label: "return date",
408};
409pub const FF_ALERT: FF = FF {
410    length: 1,
411    label: "alert",
412};
413pub const FF_ONLINE_STATUS: FF = FF {
414    length: 1,
415    label: "on-line status",
416};
417pub const FF_CHECKIN_OK: FF = FF {
418    length: 1,
419    label: "checkin ok",
420};
421pub const FF_CHECKOUT_OK: FF = FF {
422    length: 1,
423    label: "checkout ok",
424};
425pub const FF_ACS_RENEWAL_POLICY: FF = FF {
426    length: 1,
427    label: "acs renewal policy",
428};
429pub const FF_STATUS_UPDATE_OK: FF = FF {
430    length: 1,
431    label: "status update ok",
432};
433pub const FF_OFFLINE_OK: FF = FF {
434    length: 1,
435    label: "offline ok",
436};
437pub const FF_TIMEOUT_PERIOD: FF = FF {
438    length: 3,
439    label: "timeout period",
440};
441pub const FF_RETRIES_ALLOWED: FF = FF {
442    length: 3,
443    label: "retries allowed",
444};
445pub const FF_DATETIME_SYNC: FF = FF {
446    length: 18,
447    label: "date/time sync",
448};
449pub const FF_THIRD_PARTY_ALLOWED: FF = FF {
450    length: 1,
451    label: "third party allowed",
452};
453pub const FF_RENEWED_COUNT: FF = FF {
454    length: 4,
455    label: "renewed count",
456};
457pub const FF_UNRENEWED_COUNT: FF = FF {
458    length: 4,
459    label: "unrenewed count",
460};
461pub const FF_HOLD_MODE: FF = FF {
462    length: 1,
463    label: "hold mode",
464};
465pub const FF_HOLD_AVAILABLE: FF = FF {
466    length: 1,
467    label: "hold available",
468};
469pub const FF_CARD_RETAINED: FF = FF {
470    length: 1,
471    label: "card retained",
472};
473pub const FF_END_PATRON_SESSION: FF = FF {
474    length: 1,
475    label: "end session",
476};
477
478// -------------------------------------------------------------------------
479// Fields
480// -------------------------------------------------------------------------
481
482type F = Field; // local shorthand
483
484pub const F_LOGIN_UID: F = F {
485    code: "CN",
486    label: "login user id",
487};
488pub const F_LOGIN_PWD: F = F {
489    code: "CO",
490    label: "login password",
491};
492pub const F_PATRON_ID: F = F {
493    code: "AA",
494    label: "patron identifier",
495};
496pub const F_PATRON_IDENT: F = F {
497    code: "AA",
498    label: "patron identifier",
499};
500pub const F_ITEM_IDENT: F = F {
501    code: "AB",
502    label: "item identifier",
503};
504pub const F_TERMINAL_PWD: F = F {
505    code: "AC",
506    label: "terminal password",
507};
508pub const F_PATRON_PWD: F = F {
509    code: "AD",
510    label: "patron password",
511};
512pub const F_PERSONAL_NAME: F = F {
513    code: "AE",
514    label: "personal name",
515};
516pub const F_SCREEN_MSG: F = F {
517    code: "AF",
518    label: "screen message",
519};
520pub const F_PRINT_LINE: F = F {
521    code: "AG",
522    label: "print line",
523};
524pub const F_DUE_DATE: F = F {
525    code: "AH",
526    label: "due date",
527};
528pub const F_TITLE_IDENT: F = F {
529    code: "AJ",
530    label: "title identifier",
531};
532pub const F_BLOCKED_CARD_MSG: F = F {
533    code: "AL",
534    label: "blocked card msg",
535};
536pub const F_LIBRARY_NAME: F = F {
537    code: "AM",
538    label: "library name",
539};
540pub const F_TERMINAL_LOCATION: F = F {
541    code: "AN",
542    label: "terminal location",
543};
544pub const F_INSTITUTION_ID: F = F {
545    code: "AO",
546    label: "institution id",
547};
548pub const F_CURRENT_LOCATION: F = F {
549    code: "AP",
550    label: "current location",
551};
552pub const F_PERMANENT_LOCATION: F = F {
553    code: "AQ",
554    label: "permanent location",
555};
556pub const F_HOLD_ITEMS: F = F {
557    code: "AS",
558    label: "hold items",
559};
560pub const F_OVERDUE_ITEMS: F = F {
561    code: "AT",
562    label: "overdue items",
563};
564pub const F_CHARGED_ITEMS: F = F {
565    code: "AU",
566    label: "charged items",
567};
568pub const F_FINE_ITEMS: F = F {
569    code: "AV",
570    label: "fine items",
571};
572pub const F_SEQUENCE_NUMBER: F = F {
573    code: "AY",
574    label: "sequence number",
575};
576pub const F_CHECKSUM: F = F {
577    code: "AZ",
578    label: "checksum",
579};
580pub const F_HOME_ADDRESS: F = F {
581    code: "BD",
582    label: "home address",
583};
584pub const F_EMAIL_ADDRESS: F = F {
585    code: "BE",
586    label: "e-mail address",
587};
588pub const F_HOME_PHONE: F = F {
589    code: "BF",
590    label: "home phone number",
591};
592pub const F_OWNER: F = F {
593    code: "BG",
594    label: "owner",
595};
596pub const F_CURRENCY: F = F {
597    code: "BH",
598    label: "currency type",
599};
600pub const F_CANCEL: F = F {
601    code: "BI",
602    label: "cancel",
603};
604pub const F_TRANSACTION_ID: F = F {
605    code: "BK",
606    label: "transaction id",
607};
608pub const F_VALID_PATRON: F = F {
609    code: "BL",
610    label: "valid patron",
611};
612pub const F_RENEWED_ITEMS: F = F {
613    code: "BM",
614    label: "renewed items",
615};
616pub const F_UNRENEWED_ITEMS: F = F {
617    code: "BN",
618    label: "unrenewed items",
619};
620pub const F_FEE_ACKNOWLEGED: F = F {
621    code: "BO",
622    label: "fee acknowledged",
623};
624pub const F_START_ITEM: F = F {
625    code: "BP",
626    label: "start item",
627};
628pub const F_END_ITEM: F = F {
629    code: "BQ",
630    label: "end item",
631};
632pub const F_QUEUE_POSITION: F = F {
633    code: "BR",
634    label: "queue position",
635};
636pub const F_PICKUP_LOCATION: F = F {
637    code: "BS",
638    label: "pickup location",
639};
640pub const F_RECALL_ITEMS: F = F {
641    code: "BU",
642    label: "recall items",
643};
644pub const F_FEE_TYPE: F = F {
645    code: "BT",
646    label: "fee type",
647};
648pub const F_FEE_LIMIT: F = F {
649    code: "CC",
650    label: "fee limit",
651};
652pub const F_FEE_AMOUNT: F = F {
653    code: "BV",
654    label: "fee amount",
655};
656pub const F_EXPIRE_DATE: F = F {
657    code: "BW",
658    label: "expiration date",
659};
660pub const F_SUPPORTED_MESSAGES: F = F {
661    code: "BX",
662    label: "supported messages",
663};
664pub const F_HOLD_TYPE: F = F {
665    code: "BY",
666    label: "hold type",
667};
668pub const F_HOLD_ITEMS_LIMIT: F = F {
669    code: "BZ",
670    label: "hold items limit",
671};
672pub const F_OVERDUE_ITEMS_LIST: F = F {
673    code: "CA",
674    label: "overdue items limit",
675};
676pub const F_CHARGED_ITEMS_LIMIT: F = F {
677    code: "CB",
678    label: "charged items limit",
679};
680pub const F_UNAVAIL_HOLD_ITEMS: F = F {
681    code: "CD",
682    label: "unavailable hold items",
683};
684pub const F_HOLD_QUEUE_LENGTH: F = F {
685    code: "CF",
686    label: "hold queue length",
687};
688pub const F_FEE_IDENTIFIER: F = F {
689    code: "CG",
690    label: "fee identifier",
691};
692pub const F_ITEM_PROPERTIES: F = F {
693    code: "CH",
694    label: "item properties",
695};
696pub const F_SECURITY_INHIBIT: F = F {
697    code: "CI",
698    label: "security inhibit",
699};
700pub const F_RECALL_DATE: F = F {
701    code: "CJ",
702    label: "recall date",
703};
704pub const F_MEDIA_TYPE: F = F {
705    code: "CK",
706    label: "media type",
707};
708pub const F_SORT_BIN: F = F {
709    code: "CL",
710    label: "sort bin",
711};
712pub const F_HOLD_PICKUP_DATE: F = F {
713    code: "CM",
714    label: "hold pickup date",
715};
716pub const F_LOGIN_USER_ID: F = F {
717    code: "CN",
718    label: "login user id",
719};
720pub const F_LOCATION_CODE: F = F {
721    code: "CP",
722    label: "location code",
723};
724pub const F_VALID_PATRON_PWD: F = F {
725    code: "CQ",
726    label: "valid patron password",
727};
728pub const F_INET_PROFILE: F = F {
729    code: "PI",
730    label: "patron internet profile",
731};
732pub const F_CALL_NUMBER: F = F {
733    code: "CS",
734    label: "call number",
735};
736pub const F_COLLECTION_CODE: F = F {
737    code: "CR",
738    label: "collection code",
739};
740pub const F_ALERT_TYPE: F = F {
741    code: "CV",
742    label: "alert type",
743};
744pub const F_HOLD_PATRON_ID: F = F {
745    code: "CY",
746    label: "hold patron id",
747};
748pub const F_HOLD_PATRON_NAME: F = F {
749    code: "DA",
750    label: "hold patron name",
751};
752pub const F_DEST_LOCATION: F = F {
753    code: "CT",
754    label: "destination location",
755};
756
757//  Envisionware Terminal Extensions
758pub const F_PATRON_EXPIRE_DATE: F = F {
759    code: "PA",
760    label: "patron expire date",
761};
762pub const F_PATRON_DOB: F = F {
763    code: "PB",
764    label: "patron birth date",
765};
766pub const F_PATRON_CLASS: F = F {
767    code: "PC",
768    label: "patron class",
769};
770pub const F_REGISTER_LOGIN: F = F {
771    code: "OR",
772    label: "register login",
773};
774pub const F_CHECK_NUMBER: F = F {
775    code: "RN",
776    label: "check number",
777};
778
779// NOTE: when adding new fields, be sure to also add the new
780// to Field::from_code()
781
782// -------------------------------------------------------------------------
783// Messages
784// -------------------------------------------------------------------------
785
786pub const EMPTY: &[&FixedField; 0] = &[];
787
788/// Message 99
789pub const M_SC_STATUS: Message = Message {
790    code: "99",
791    label: "SC Status",
792    fixed_fields: &[&FF_STATUS_CODE, &FF_MAX_PRINT_WIDTH, &FF_PROTOCOL_VERSION],
793};
794
795/// Message 98
796pub const M_ACS_STATUS: Message = Message {
797    code: "98",
798    label: "ACS Status",
799    fixed_fields: &[
800        &FF_ONLINE_STATUS,
801        &FF_CHECKIN_OK,
802        &FF_CHECKOUT_OK,
803        &FF_ACS_RENEWAL_POLICY,
804        &FF_STATUS_UPDATE_OK,
805        &FF_OFFLINE_OK,
806        &FF_TIMEOUT_PERIOD,
807        &FF_RETRIES_ALLOWED,
808        &FF_DATETIME_SYNC,
809        &FF_PROTOCOL_VERSION,
810    ],
811};
812
813/// Message 93
814pub const M_LOGIN: Message = Message {
815    code: "93",
816    label: "Login Request",
817    fixed_fields: &[&FF_UID_ALGO, &FF_PWD_ALGO],
818};
819
820/// Message 94
821pub const M_LOGIN_RESP: Message = Message {
822    code: "94",
823    label: "Login Response",
824    fixed_fields: &[&FF_OK],
825};
826
827/// Message 17
828pub const M_ITEM_INFO: Message = Message {
829    code: "17",
830    label: "Item Information Request",
831    fixed_fields: &[&FF_DATE],
832};
833
834/// Message 18
835pub const M_ITEM_INFO_RESP: Message = Message {
836    code: "18",
837    label: "Item Information Response",
838    fixed_fields: &[
839        &FF_CIRCULATION_STATUS,
840        &FF_SECURITY_MARKER,
841        &FF_FEE_TYPE,
842        &FF_DATE,
843    ],
844};
845
846/// Message 23
847pub const M_PATRON_STATUS: Message = Message {
848    code: "23",
849    label: "Patron Status Request",
850    fixed_fields: &[&FF_LANGUAGE, &FF_DATE],
851};
852
853/// Message 24
854pub const M_PATRON_STATUS_RESP: Message = Message {
855    code: "24",
856    label: "Patron Status Response",
857    fixed_fields: &[&FF_PATRON_STATUS, &FF_LANGUAGE, &FF_DATE],
858};
859
860/// Message 63
861pub const M_PATRON_INFO: Message = Message {
862    code: "63",
863    label: "Patron Information",
864    fixed_fields: &[&FF_LANGUAGE, &FF_DATE, &FF_SUMMARY],
865};
866
867/// Message 64
868pub const M_PATRON_INFO_RESP: Message = Message {
869    code: "64",
870    label: "Patron Information Response",
871    fixed_fields: &[
872        &FF_PATRON_STATUS,
873        &FF_LANGUAGE,
874        &FF_DATE,
875        &FF_HOLD_ITEMS_COUNT,
876        &FF_OD_ITEMS_COUNT,
877        &FF_CH_ITEMS_COUNT,
878        &FF_FINE_ITEMS_COUNT,
879        &FF_RECALL_ITEMS_COUNT,
880        &FF_UNAVAIL_HOLDS_COUNT,
881    ],
882};
883
884/// Message 11
885pub const M_CHECKOUT: Message = Message {
886    code: "11",
887    label: "Checkout Request",
888    fixed_fields: &[
889        &FF_SC_RENEWAL_POLICY,
890        &FF_NO_BLOCK,
891        &FF_DATE,
892        &FF_NB_DUE_DATE,
893    ],
894};
895
896/// Message 12
897pub const M_CHECKOUT_RESP: Message = Message {
898    code: "12",
899    label: "Checkout Response",
900    fixed_fields: &[
901        &FF_OK,
902        &FF_RENEW_OK,
903        &FF_MAGNETIC_MEDIA,
904        &FF_DESENSITIZE,
905        &FF_DATE,
906    ],
907};
908
909/// Message 29
910pub const M_RENEW: Message = Message {
911    code: "29",
912    label: "Renew Request",
913    fixed_fields: &[
914        &FF_THIRD_PARTY_ALLOWED,
915        &FF_NO_BLOCK,
916        &FF_DATE,
917        &FF_NB_DUE_DATE,
918    ],
919};
920
921/// Message 30
922pub const M_RENEW_RESP: Message = Message {
923    code: "30",
924    label: "Renew Response",
925    fixed_fields: &[
926        &FF_OK,
927        &FF_RENEW_OK,
928        &FF_MAGNETIC_MEDIA,
929        &FF_DESENSITIZE,
930        &FF_DATE,
931    ],
932};
933
934/// Message 65
935pub const M_RENEW_ALL: Message = Message {
936    code: "65",
937    label: "Renew All Request",
938    fixed_fields: &[&FF_DATE],
939};
940
941/// Message 66
942pub const M_RENEW_ALL_RESP: Message = Message {
943    code: "66",
944    label: "Renew All Response",
945    fixed_fields: &[&FF_OK, &FF_RENEWED_COUNT, &FF_UNRENEWED_COUNT, &FF_DATE],
946};
947
948/// Message 09
949pub const M_CHECKIN: Message = Message {
950    code: "09",
951    label: "Checkin Request",
952    fixed_fields: &[&FF_NO_BLOCK, &FF_DATE, &FF_RETURN_DATE],
953};
954
955/// Message 10
956pub const M_CHECKIN_RESP: Message = Message {
957    code: "10",
958    label: "Checkin Response",
959    fixed_fields: &[
960        &FF_OK,
961        &FF_RESENSITIZE,
962        &FF_MAGNETIC_MEDIA,
963        &FF_ALERT,
964        &FF_DATE,
965    ],
966};
967
968/// Message 15
969pub const M_HOLD: Message = Message {
970    code: "15",
971    label: "Hold Request",
972    fixed_fields: &[&FF_HOLD_MODE, &FF_DATE],
973};
974
975/// Message 16
976pub const M_HOLD_RESP: Message = Message {
977    code: "16",
978    label: "Hold Response",
979    fixed_fields: &[&FF_OK, &FF_HOLD_AVAILABLE, &FF_DATE],
980};
981
982/// Message 35
983pub const M_END_PATRON_SESSION: Message = Message {
984    code: "35",
985    label: "End Patron Session",
986    fixed_fields: &[&FF_DATE],
987};
988
989/// Message 36
990pub const M_END_PATRON_SESSION_RESP: Message = Message {
991    code: "36",
992    label: "End Session Response",
993    fixed_fields: &[&FF_END_PATRON_SESSION, &FF_DATE],
994};
995
996/// Message 37
997pub const M_FEE_PAID: Message = Message {
998    code: "37",
999    label: "Fee Paid",
1000    fixed_fields: &[&FF_DATE, &FF_FEE_TYPE, &FF_PAYMENT_TYPE, &FF_CURRENCY],
1001};
1002
1003/// Message 38
1004pub const M_FEE_PAID_RESP: Message = Message {
1005    code: "38",
1006    label: "Fee Paid Response",
1007    fixed_fields: &[&FF_PAYMENT_ACCEPTED, &FF_DATE],
1008};
1009
1010/// Message 97
1011pub const M_REQUEST_ACS_RESEND: Message = Message {
1012    code: "97",
1013    label: "Request ACS Resend",
1014    fixed_fields: &[],
1015};
1016
1017/// Message 01
1018pub const M_BLOCK_PATRON: Message = Message {
1019    code: "01",
1020    label: "Block Patron",
1021    fixed_fields: &[&FF_CARD_RETAINED, &FF_DATE],
1022};
1023
1024// Custom "end session" messages for SIP2Mediator.
1025// This differs from the "End Patron Session" (35) message in that it's
1026// not about a patron but about a SIP client session, which can involve
1027// many patrons (or none).
1028
1029/// SIP2Mediator XS (End Session) Message
1030pub const M_END_SESSION: Message = Message {
1031    code: "XS",
1032    label: "End SIP Session",
1033    fixed_fields: &[],
1034};
1035
1036/// SIP2Mediator XT (End Session Response) Message
1037pub const M_END_SESSION_RESP: Message = Message {
1038    code: "XT",
1039    label: "End SIP Session Response",
1040    fixed_fields: &[],
1041};
1042
1043// NOTE: when adding new message types, be sure to also add the new
1044// message to Message::from_code()
1045
1046#[derive(Debug, PartialEq)]
1047pub enum CheckinAlert {
1048    Unknown,
1049    LocalHold,
1050    RemoteHold,
1051    Ill,
1052    Transit,
1053    Other,
1054}
1055
1056impl TryFrom<&str> for CheckinAlert {
1057    type Error = super::error::Error;
1058
1059    fn try_from(v: &str) -> Result<Self, Self::Error> {
1060        match v {
1061            "00" => Ok(Self::Unknown),
1062            "01" => Ok(Self::LocalHold),
1063            "02" => Ok(Self::RemoteHold),
1064            "03" => Ok(Self::Ill),
1065            "04" => Ok(Self::Transit),
1066            "99" => Ok(Self::Other),
1067            _ => {
1068                log::error!("Invalid checkin alert code: {v}");
1069                Err(Self::Error::MessageFormatError)
1070            }
1071        }
1072    }
1073}
1074
1075impl From<CheckinAlert> for &str {
1076    fn from(a: CheckinAlert) -> &'static str {
1077        match a {
1078            CheckinAlert::Unknown => "00",
1079            CheckinAlert::LocalHold => "01",
1080            CheckinAlert::RemoteHold => "02",
1081            CheckinAlert::Ill => "03",
1082            CheckinAlert::Transit => "04",
1083            CheckinAlert::Other => "99",
1084        }
1085    }
1086}