1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//!
//! IPP helper functions
//!
use http::Uri;
use num_traits::FromPrimitive;

use crate::{
    attribute::IppAttribute,
    error::IppError,
    model::{DelimiterTag, PrinterState},
    prelude::IppRequestResponse,
};

/// convert `http://username:pwd@host:port/path?query` into `ipp://host:port/path`
pub fn canonicalize_uri(uri: &Uri) -> Uri {
    let mut builder = Uri::builder().scheme("ipp").path_and_query(uri.path());
    if let Some(authority) = uri.authority() {
        if let Some(port) = authority.port_u16() {
            builder = builder.authority(format!("{}:{}", authority.host(), port).as_str());
        } else {
            builder = builder.authority(authority.host());
        }
    }
    builder.build().unwrap_or_else(|_| uri.to_owned())
}

const ERROR_STATES: &[&str] = &[
    "media-jam",
    "toner-empty",
    "spool-area-full",
    "cover-open",
    "door-open",
    "input-tray-missing",
    "output-tray-missing",
    "marker-supply-empty",
    "paused",
    "shutdown",
];

/// Check if the printer is ready for printing
///
/// * `response` - IPP response to check
pub fn is_printer_ready(response: &IppRequestResponse) -> Result<bool, IppError> {
    let status = response.header().status_code();
    if !status.is_success() {
        return Err(IppError::StatusError(status));
    }

    let state = response
        .attributes()
        .groups_of(DelimiterTag::PrinterAttributes)
        .next()
        .and_then(|g| g.attributes().get(IppAttribute::PRINTER_STATE))
        .and_then(|attr| attr.value().as_enum())
        .and_then(|v| PrinterState::from_i32(*v));

    if let Some(PrinterState::Stopped) = state {
        return Ok(false);
    }

    if let Some(reasons) = response
        .attributes()
        .groups_of(DelimiterTag::PrinterAttributes)
        .next()
        .and_then(|g| g.attributes().get(IppAttribute::PRINTER_STATE_REASONS))
    {
        let keywords = reasons
            .value()
            .into_iter()
            .filter_map(|e| e.as_keyword())
            .map(ToOwned::to_owned)
            .collect::<Vec<_>>();

        if keywords.iter().any(|k| ERROR_STATES.contains(&&k[..])) {
            return Ok(false);
        }
    }
    Ok(true)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_canonicalize_uri() {
        assert_eq!(
            canonicalize_uri(&"http://user:pass@example.com:631/path?query=val".parse().unwrap()),
            "ipp://example.com:631/path"
        );
        assert_eq!(
            canonicalize_uri(&"http://example.com/path?query=val".parse().unwrap()),
            "ipp://example.com/path"
        );
    }
}