bookdata/openlib/
key.rs

1use std::str::FromStr;
2
3use thiserror::Error;
4
5/// An OpenLibrary keyspace.
6pub struct OLKS {
7    keyspace: &'static str,
8    codechar: char,
9}
10
11/// Error parsing an OpenLibrary key.
12#[derive(Error, Debug)]
13pub enum OLKeyError {
14    #[error("could not parse {0}: {1}")]
15    InvalidFormat(String, String),
16    #[error("bad keyspace for ‘{0}’, expected {1}")]
17    BadKeyspace(String, &'static str),
18    #[error("trailing code character mismatch")]
19    InvalidCodeChar,
20}
21
22pub const KS_AUTHOR: OLKS = OLKS {
23    keyspace: "authors",
24    codechar: 'A',
25};
26#[allow(dead_code)]
27pub const KS_WORK: OLKS = OLKS {
28    keyspace: "works",
29    codechar: 'W',
30};
31#[allow(dead_code)]
32pub const KS_EDITION: OLKS = OLKS {
33    keyspace: "editions",
34    codechar: 'M',
35};
36
37struct OLKey<'a> {
38    keyspace: &'a str,
39    codechar: char,
40    id: u32,
41}
42
43peg::parser! {
44    grammar key_parser() for str {
45        rule lcchar() = quiet!{['a'..='z']}
46        rule lcword() -> &'input str = s:$(lcchar()+) {s}
47        rule digit() = quiet!{['0'..='9']}
48        rule number() -> u32 = n:$(digit()+) {?
49            u32::from_str(n).or(Err("invalid number"))
50        }
51
52        pub rule ol_key() -> OLKey<'input>
53        = "/" ks:lcword() "/OL" id:number() c:['A'..='Z'] {
54            OLKey {
55                keyspace: ks,
56                codechar: c,
57                id
58            }
59        }
60    }
61}
62
63/// Parse an OpenLibrary key.
64pub fn parse_ol_key(key: &str, ks: OLKS) -> Result<u32, OLKeyError> {
65    let k = key_parser::ol_key(key)
66        .map_err(|e| OLKeyError::InvalidFormat(key.to_string(), format!("{:?}", e)))?;
67    if k.codechar != ks.codechar {
68        Err(OLKeyError::InvalidCodeChar)
69    } else if k.keyspace != ks.keyspace && k.keyspace[0..1] != ks.keyspace[0..1] {
70        Err(OLKeyError::BadKeyspace(key.to_string(), ks.keyspace))
71    } else {
72        Ok(k.id)
73    }
74}
75
76#[test]
77fn test_parse_work() {
78    let id = parse_ol_key("/works/OL38140W", KS_WORK).expect("parse failed");
79    assert_eq!(id, 38140);
80}