Skip to main content

fission_core/
platform_barcode.rs

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