use std::future::Future;
use std::pin::Pin;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderErrorCode {
InvalidRequest,
NotFound,
Network,
Timeout,
Server,
PermissionDenied,
Internal,
}
impl ProviderErrorCode {
pub const fn biz_code(self) -> u32 {
match self {
Self::InvalidRequest => 1002,
Self::NotFound => 1003,
Self::Network => 5001,
Self::Timeout => 5002,
Self::Server => 5003,
Self::PermissionDenied => 3000,
Self::Internal => 1005,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::InvalidRequest => "invalid_request",
Self::NotFound => "not_found",
Self::Network => "network",
Self::Timeout => "timeout",
Self::Server => "server",
Self::PermissionDenied => "permission_denied",
Self::Internal => "internal",
}
}
}
#[derive(Debug, Clone)]
pub struct ProviderError {
code: ProviderErrorCode,
detail: String,
}
impl ProviderError {
pub fn new(code: ProviderErrorCode, detail: impl Into<String>) -> Self {
Self {
code,
detail: detail.into(),
}
}
pub fn invalid_request(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorCode::InvalidRequest, detail)
}
pub fn not_found(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorCode::NotFound, detail)
}
pub fn network(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorCode::Network, detail)
}
pub fn timeout(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorCode::Timeout, detail)
}
pub fn server(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorCode::Server, detail)
}
pub fn permission_denied(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorCode::PermissionDenied, detail)
}
pub fn internal(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorCode::Internal, detail)
}
pub const fn code(&self) -> ProviderErrorCode {
self.code
}
pub const fn biz_code(&self) -> u32 {
self.code.biz_code()
}
pub fn detail(&self) -> &str {
&self.detail
}
}
impl std::fmt::Display for ProviderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code.as_str(), self.detail)
}
}
impl std::error::Error for ProviderError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FingerprintError {
DeviceIdUnavailable,
}
impl std::fmt::Display for FingerprintError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DeviceIdUnavailable => write!(f, "device_id_unavailable"),
}
}
}
impl std::error::Error for FingerprintError {}
pub trait FingerprintProvider: Send + Sync + 'static {
fn get_fingerprint(&self) -> Result<String, FingerprintError> {
Err(FingerprintError::DeviceIdUnavailable)
}
}
pub trait PushNotificationProvider: Send + Sync + 'static {
fn bind_push_token<'a>(&'a self, _token: String) -> BoxFuture<'a, Result<(), ProviderError>> {
Box::pin(async { Ok(()) })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LxAppStatus {
#[default]
Unknown,
Published,
Maintain,
Delisted,
Suspended,
}
impl LxAppStatus {
pub const fn as_str(self) -> &'static str {
match self {
Self::Unknown => "unknown",
Self::Published => "published",
Self::Maintain => "maintain",
Self::Delisted => "delisted",
Self::Suspended => "suspended",
}
}
pub fn from_str_lossy(value: &str) -> Self {
match value.trim().to_ascii_lowercase().as_str() {
"published" => Self::Published,
"maintain" => Self::Maintain,
"delisted" => Self::Delisted,
"suspended" => Self::Suspended,
_ => Self::Unknown,
}
}
pub const fn blocks_open(self) -> bool {
matches!(self, Self::Suspended | Self::Maintain)
}
}
impl std::fmt::Display for LxAppStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Default)]
pub struct LxAppRegistryInfo {
pub appid: String,
pub name: Option<String>,
pub description: Option<String>,
pub icon_url: Option<String>,
pub status: LxAppStatus,
}
pub trait LxAppRegistryProvider: Send + Sync + 'static {
fn fetch_registry_info<'a>(
&'a self,
_appid: &'a str,
) -> BoxFuture<'a, Result<Option<LxAppRegistryInfo>, ProviderError>> {
Box::pin(async { Ok(None) })
}
}
#[cfg(test)]
mod registry_tests {
use super::LxAppStatus;
#[test]
fn only_the_states_that_mean_do_not_open_block() {
assert!(LxAppStatus::Suspended.blocks_open());
assert!(LxAppStatus::Maintain.blocks_open());
assert!(!LxAppStatus::Delisted.blocks_open());
assert!(!LxAppStatus::Published.blocks_open());
assert!(!LxAppStatus::Unknown.blocks_open());
assert_eq!(
LxAppStatus::from_str_lossy("maintain"),
LxAppStatus::Maintain
);
}
#[test]
fn status_parsing_is_case_insensitive_because_unknown_never_blocks() {
assert_eq!(
LxAppStatus::from_str_lossy("suspended"),
LxAppStatus::Suspended
);
assert_eq!(
LxAppStatus::from_str_lossy("Suspended"),
LxAppStatus::Suspended
);
assert_eq!(
LxAppStatus::from_str_lossy(" SUSPENDED "),
LxAppStatus::Suspended
);
assert!(LxAppStatus::from_str_lossy("SUSPENDED").blocks_open());
assert_eq!(
LxAppStatus::from_str_lossy("Delisted"),
LxAppStatus::Delisted
);
assert_eq!(LxAppStatus::from_str_lossy(""), LxAppStatus::Unknown);
assert_eq!(LxAppStatus::from_str_lossy("retired"), LxAppStatus::Unknown);
}
}