bookdata/ids/
codes.rs

1//! Book codes and their number spaces.
2//!
3//! This implements the [book codes][bc] for the book integration.  In this module,
4//! we use *book code* to refer to the numberspaced numeric code representing the
5//! book record in the common code space, and *identifier* (or *underlying identifier*)
6//! for the native identifier of the underlying record.  Identifiers are translated
7//! into codes by adding the number space's base, which partitions the code space
8//! into different regions.
9//!
10//! [bc]: https://bookdata.piret.info/data/ids.html#book-codes
11
12/// The "number space" structure for identifier spaces.
13pub struct NS<'a> {
14    /// The name of this numberspace.
15    pub name: &'a str,
16    /// The name usable as a part of an identifier name (snake_case).
17    pub fn_name: &'a str,
18    /// The number space numeric code.
19    pub code: i32,
20}
21
22/// The multiplier base for distinguishing numbers in a number space.
23/// Each space supports up to 100K identifiers.
24pub const NS_MULT_BASE: i32 = 100_000_000;
25
26#[allow(dead_code)]
27pub const NS_WORK: NS<'static> = NS::new("OL-W", "ol_work", 1);
28#[allow(dead_code)]
29pub const NS_EDITION: NS<'static> = NS::new("OL-E", "ol_edition", 2);
30#[allow(dead_code)]
31pub const NS_LOC_REC: NS<'static> = NS::new("LOC", "loc_rec", 3);
32#[allow(dead_code)]
33pub const NS_GR_WORK: NS<'static> = NS::new("GR-W", "gr_work", 4);
34#[allow(dead_code)]
35pub const NS_GR_BOOK: NS<'static> = NS::new("GR-B", "gr_book", 5);
36#[allow(dead_code)]
37pub const NS_LOC_WORK: NS<'static> = NS::new("LOC-W", "loc_work", 6);
38#[allow(dead_code)]
39pub const NS_LOC_INSTANCE: NS<'static> = NS::new("LOC-I", "loc_instance", 7);
40#[allow(dead_code)]
41pub const NS_ISBN: NS<'static> = NS::new("ISBN", "isbn", 9);
42
43const NAMESPACES: &'static [&'static NS<'static>] = &[
44    &NS_WORK,
45    &NS_EDITION,
46    &NS_LOC_REC,
47    &NS_GR_WORK,
48    &NS_GR_BOOK,
49    &NS_LOC_WORK,
50    &NS_LOC_INSTANCE,
51    &NS_ISBN,
52];
53
54#[cfg(test)]
55use quickcheck::quickcheck;
56
57impl<'a> NS<'a> {
58    /// Create a new number space. Internal only.
59    const fn new(name: &'a str, fn_name: &'a str, code: i32) -> NS<'a> {
60        NS {
61            name,
62            fn_name,
63            code,
64        }
65    }
66
67    /// Get the name of the number space.
68    #[allow(dead_code)]
69    pub fn name(&'a self) -> &'a str {
70        self.name
71    }
72
73    /// Get the numeric code of the number space.
74    pub fn code(&'a self) -> i32 {
75        self.code
76    }
77
78    /// Get the base of the number space. Identifiers are translated into this space
79    /// by adding the base.
80    pub fn base(&'a self) -> i32 {
81        self.code() * NS_MULT_BASE
82    }
83
84    /// Convert a numeric identifier to a book code in this number space.
85    #[allow(dead_code)]
86    pub fn to_code(&'a self, n: i32) -> i32 {
87        assert!(n >= 0);
88        assert!(n <= NS_MULT_BASE);
89        n + self.base()
90    }
91
92    /// Extract a numeric identifier from a book code in this number space.
93    pub fn from_code(&'a self, n: i32) -> Option<i32> {
94        let lo = self.base();
95        let hi = lo + NS_MULT_BASE;
96        if n >= lo && n < hi {
97            Some(n - lo)
98        } else {
99            None
100        }
101    }
102}
103
104impl NS<'static> {
105    pub fn by_name(name: &str) -> Option<&'static NS<'static>> {
106        for ns in NAMESPACES {
107            if ns.name() == name {
108                return Some(ns);
109            }
110        }
111
112        None
113    }
114}
115
116/// Get the namespace for a book code.
117pub fn ns_of_book_code(code: i32) -> Option<&'static NS<'static>> {
118    let pfx = code / NS_MULT_BASE;
119    if pfx >= 1 {
120        for ns in NAMESPACES {
121            if ns.code() == pfx {
122                return Some(ns);
123            }
124        }
125    }
126
127    None
128}
129
130#[cfg(test)]
131quickcheck! {
132  fn prop_code_looks_up(code: i32) -> bool {
133    if let Some(ns) = ns_of_book_code(code) {
134      // mapping worked
135      let bc = code % NS_MULT_BASE;
136      ns.to_code(bc) == code
137    } else {
138      // acceptable to not map
139      code < NS_MULT_BASE || code > 10*NS_MULT_BASE || code / NS_MULT_BASE == 8
140    }
141  }
142}
143
144#[test]
145fn test_to_code() {
146    let n = 42;
147    let code = NS_LOC_REC.to_code(n);
148    assert_eq!(code / NS_MULT_BASE, NS_LOC_REC.code());
149}
150
151#[test]
152fn test_from_code() {
153    let n = 42;
154    let code = NS_LOC_REC.to_code(n);
155    assert_eq!(NS_LOC_REC.from_code(code), Some(n));
156    assert_eq!(NS_EDITION.from_code(code), None);
157    assert_eq!(NS_ISBN.from_code(code), None);
158}