Skip to main content

fission_core/
platform_geolocation.rs

1//! Geolocation host capabilities.
2
3use crate::capability::{CapabilityType, OperationCapability};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
7pub enum GeolocationPermission {
8    Granted,
9    Denied,
10    Prompt,
11    #[default]
12    Unknown,
13    Unsupported,
14}
15
16#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
17pub struct GeolocationPermissionRequest {
18    pub precise: bool,
19    pub background: bool,
20}
21
22#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
23pub struct GeolocationPositionRequest {
24    pub high_accuracy: bool,
25    pub timeout_ms: Option<u64>,
26    pub maximum_age_ms: Option<u64>,
27}
28
29#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
30pub struct GeolocationPosition {
31    pub latitude: f64,
32    pub longitude: f64,
33    pub altitude_meters: Option<f64>,
34    pub accuracy_meters: f64,
35    pub altitude_accuracy_meters: Option<f64>,
36    pub heading_degrees: Option<f64>,
37    pub speed_mps: Option<f64>,
38    pub timestamp_unix_ms: u64,
39}
40
41#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
42pub struct GeolocationError {
43    pub code: String,
44    pub message: String,
45}
46
47impl GeolocationError {
48    /// Creates a portable geolocation error payload.
49    ///
50    /// `code` should be a stable, machine-readable reason such as
51    /// `unsupported`, `permission_denied`, or `timeout`. `message` should be a
52    /// concise human-readable explanation suitable for logs or developer-facing
53    /// diagnostics.
54    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
55        Self {
56            code: code.into(),
57            message: message.into(),
58        }
59    }
60
61    /// Creates the standard unsupported-operation error for this capability.
62    ///
63    /// `operation` should name the attempted geolocation operation. Use this
64    /// from hosts that implement the capability contract but cannot provide this
65    /// operation on the current platform or hardware.
66    pub fn unsupported(operation: impl Into<String>) -> Self {
67        Self::new(
68            "unsupported",
69            format!(
70                "geolocation operation `{}` is not supported by this host",
71                operation.into()
72            ),
73        )
74    }
75}
76
77pub struct GetGeolocationPermissionCapability;
78impl OperationCapability for GetGeolocationPermissionCapability {
79    type Request = ();
80    type Ok = GeolocationPermission;
81    type Err = GeolocationError;
82}
83
84pub struct RequestGeolocationPermissionCapability;
85impl OperationCapability for RequestGeolocationPermissionCapability {
86    type Request = GeolocationPermissionRequest;
87    type Ok = GeolocationPermission;
88    type Err = GeolocationError;
89}
90
91pub struct GetCurrentPositionCapability;
92impl OperationCapability for GetCurrentPositionCapability {
93    type Request = GeolocationPositionRequest;
94    type Ok = GeolocationPosition;
95    type Err = GeolocationError;
96}
97
98pub const GET_GEOLOCATION_PERMISSION: CapabilityType<GetGeolocationPermissionCapability> =
99    CapabilityType::new("fission.geolocation.get_permission");
100pub const REQUEST_GEOLOCATION_PERMISSION: CapabilityType<RequestGeolocationPermissionCapability> =
101    CapabilityType::new("fission.geolocation.request_permission");
102pub const GET_CURRENT_POSITION: CapabilityType<GetCurrentPositionCapability> =
103    CapabilityType::new("fission.geolocation.get_current_position");
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn geolocation_request_round_trips() {
111        let request = GeolocationPositionRequest {
112            high_accuracy: true,
113            timeout_ms: Some(5_000),
114            maximum_age_ms: Some(30_000),
115        };
116        let bytes = serde_json::to_vec(&request).unwrap();
117        let decoded: GeolocationPositionRequest = serde_json::from_slice(&bytes).unwrap();
118        assert_eq!(decoded, request);
119    }
120
121    #[test]
122    fn geolocation_position_round_trips() {
123        let position = GeolocationPosition {
124            latitude: 51.5074,
125            longitude: -0.1278,
126            altitude_meters: None,
127            accuracy_meters: 8.0,
128            altitude_accuracy_meters: None,
129            heading_degrees: None,
130            speed_mps: None,
131            timestamp_unix_ms: 1_774_000_000_000,
132        };
133        let bytes = serde_json::to_vec(&position).unwrap();
134        let decoded: GeolocationPosition = serde_json::from_slice(&bytes).unwrap();
135        assert_eq!(decoded, position);
136    }
137}