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 name: Option<String>,
pub description: Option<String>,
pub icon_url: Option<String>,
pub status: LxAppStatus,
pub permissions: Option<LxAppPermissions>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct LxAppPermissions {
pub network: Option<LxAppNetworkPermission>,
pub privileges: Option<LxAppPrivilegePermission>,
}
impl LxAppPermissions {
pub fn all() -> Self {
Self::default()
}
pub fn network(trusted_domains: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
network: Some(LxAppNetworkPermission::new(trusted_domains)),
privileges: None,
}
}
pub fn privileges(granted: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
network: None,
privileges: Some(LxAppPrivilegePermission::new(granted)),
}
}
pub fn with_network(
mut self,
trusted_domains: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.network = Some(LxAppNetworkPermission::new(trusted_domains));
self
}
pub fn with_privileges(mut self, granted: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.privileges = Some(LxAppPrivilegePermission::new(granted));
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct LxAppNetworkPermission {
pub trusted_domains: Vec<String>,
}
impl LxAppNetworkPermission {
pub fn new(trusted_domains: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
trusted_domains: trusted_domains.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct LxAppPrivilegePermission {
pub granted: Vec<String>,
}
impl LxAppPrivilegePermission {
pub fn new(granted: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
granted: granted.into_iter().map(Into::into).collect(),
}
}
}
pub trait LxAppRegistryProvider: Send + Sync + 'static {
fn fetch_registry_info<'a>(
&'a self,
_app: LxAppRegistryRequest<'a>,
) -> BoxFuture<'a, Result<Option<LxAppRegistryInfo>, ProviderError>> {
Box::pin(async { Ok(None) })
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct LxAppRegistryRequest<'a> {
pub appid: &'a str,
pub channel: LxAppChannel,
}
impl<'a> LxAppRegistryRequest<'a> {
pub fn new(appid: &'a str, channel: LxAppChannel) -> Self {
Self { appid, channel }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LxAppChannel {
#[default]
Release,
Preview,
Draft,
}
impl LxAppChannel {
pub const fn as_str(self) -> &'static str {
match self {
Self::Release => "release",
Self::Preview => "preview",
Self::Draft => "draft",
}
}
}
impl std::fmt::Display for LxAppChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[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);
}
}