Skip to main content

fission_core/
platform_barcode.rs

1//! Barcode scanner host capabilities.
2
3use crate::capability::{CapabilityType, OperationCapability};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum BarcodeFormat {
8    QrCode,
9    Aztec,
10    DataMatrix,
11    Ean13,
12    Ean8,
13    Code128,
14    Code39,
15    Code93,
16    Codabar,
17    Itf,
18    Pdf417,
19    UpcA,
20    UpcE,
21    MaxiCode,
22    Rss14,
23    RssExpanded,
24    Other(String),
25}
26
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
28pub struct BarcodePoint {
29    pub x: i32,
30    pub y: i32,
31}
32
33#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct BarcodeScanRequest {
35    pub formats: Vec<BarcodeFormat>,
36    pub prompt: Option<String>,
37    pub camera_id: Option<String>,
38    pub timeout_ms: Option<u64>,
39    pub allow_multiple: bool,
40}
41
42#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
43pub struct BarcodeImageDecodeRequest {
44    pub bytes: Vec<u8>,
45    pub content_type: Option<String>,
46    pub formats: Vec<BarcodeFormat>,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50pub struct BarcodeScanResult {
51    pub value: String,
52    pub format: BarcodeFormat,
53    pub raw_bytes: Vec<u8>,
54    pub bounds: Vec<BarcodePoint>,
55    pub symbology_identifier: Option<String>,
56}
57
58#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
59pub struct BarcodeScanResults {
60    pub items: Vec<BarcodeScanResult>,
61}
62
63#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
64pub struct BarcodeScannerError {
65    pub code: String,
66    pub message: String,
67}
68
69impl BarcodeScannerError {
70    /// Creates a portable barcode scanner error payload.
71    ///
72    /// `code` should be a stable, machine-readable reason such as
73    /// `unsupported`, `permission_denied`, or `timeout`. `message` should be a
74    /// concise human-readable explanation suitable for logs or developer-facing
75    /// diagnostics.
76    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
77        Self {
78            code: code.into(),
79            message: message.into(),
80        }
81    }
82
83    /// Creates the standard unsupported-operation error for this capability.
84    ///
85    /// `operation` should name the attempted barcode scanner operation. Use this
86    /// from hosts that implement the capability contract but cannot provide this
87    /// operation on the current platform or hardware.
88    pub fn unsupported(operation: impl Into<String>) -> Self {
89        Self::new(
90            "unsupported",
91            format!(
92                "barcode scanner operation `{}` is not supported by this host",
93                operation.into()
94            ),
95        )
96    }
97}
98
99pub struct ScanBarcodeCapability;
100impl OperationCapability for ScanBarcodeCapability {
101    type Request = BarcodeScanRequest;
102    type Ok = BarcodeScanResults;
103    type Err = BarcodeScannerError;
104}
105
106pub struct DecodeBarcodeImageCapability;
107impl OperationCapability for DecodeBarcodeImageCapability {
108    type Request = BarcodeImageDecodeRequest;
109    type Ok = BarcodeScanResults;
110    type Err = BarcodeScannerError;
111}
112
113pub struct CancelBarcodeScanCapability;
114impl OperationCapability for CancelBarcodeScanCapability {
115    type Request = ();
116    type Ok = ();
117    type Err = BarcodeScannerError;
118}
119
120pub const SCAN_BARCODE: CapabilityType<ScanBarcodeCapability> =
121    CapabilityType::new("fission.barcode.scan");
122pub const DECODE_BARCODE_IMAGE: CapabilityType<DecodeBarcodeImageCapability> =
123    CapabilityType::new("fission.barcode.decode_image");
124pub const CANCEL_BARCODE_SCAN: CapabilityType<CancelBarcodeScanCapability> =
125    CapabilityType::new("fission.barcode.cancel_scan");
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn barcode_scan_request_round_trips() {
133        let request = BarcodeScanRequest {
134            formats: vec![BarcodeFormat::QrCode, BarcodeFormat::Code128],
135            prompt: Some("Scan the label".into()),
136            camera_id: Some("back".into()),
137            timeout_ms: Some(10_000),
138            allow_multiple: true,
139        };
140
141        let bytes = serde_json::to_vec(&request).unwrap();
142        let decoded: BarcodeScanRequest = serde_json::from_slice(&bytes).unwrap();
143
144        assert_eq!(decoded, request);
145    }
146
147    #[test]
148    fn barcode_results_round_trip() {
149        let results = BarcodeScanResults {
150            items: vec![BarcodeScanResult {
151                value: "https://fission.rs".into(),
152                format: BarcodeFormat::QrCode,
153                raw_bytes: b"https://fission.rs".to_vec(),
154                bounds: vec![BarcodePoint { x: 1, y: 2 }, BarcodePoint { x: 3, y: 4 }],
155                symbology_identifier: None,
156            }],
157        };
158
159        let bytes = serde_json::to_vec(&results).unwrap();
160        let decoded: BarcodeScanResults = serde_json::from_slice(&bytes).unwrap();
161
162        assert_eq!(decoded, results);
163    }
164}