Skip to main content

bf_types - Shared Types Library

The bf_types crate provides a central hub for types shared across all services in the platform.

Key Files

  • crates/bf_types/src/lib.rs - Module structure and re-exports
  • crates/bf_types/src/error.rs - Unified error types
  • crates/bf_types/src/mesh/ - Service-to-service client (feature-gated)

Module Structure

bf_types/
├── src/
│ ├── lib.rs # Re-exports
│ ├── error.rs # AppError, ErrorResponse, Result
│ ├── auth/ # Authentication types
│ ├── notify/ # Webhook types
│ ├── ota/ # OTA update types
│ ├── resident/ # Property, device queries
│ ├── smart/ # Smart device types
│ ├── user/ # User lifecycle types
│ └── mesh/ # Service mesh client (feature-gated)
└── Cargo.toml

Key Re-exports

// crates/bf_types/src/lib.rs

pub mod auth;
pub mod error;
pub mod notify;
pub mod ota;
pub mod resident;
pub mod smart;
pub mod user;

#[cfg(feature = "mesh")]
pub mod mesh;

// Unified error handling
pub use error::{AppError, ErrorResponse, Result};

// Webhook types
pub use notify::{WebhookRequest, WebhookResponse, WebhookSource};

// OTA types
pub use ota::{BuildInfo, SignedBuildInfo};

// User lifecycle
pub use user::{
BackupRequest, BackupResponse,
MoveInRequest, MoveInResponse, MoveInSummary,
MoveOutRequest, MoveOutResponse, MoveOutSummary,
UserSeedRequest, UserSeedResponse,
};

Error Types

AppError

// crates/bf_types/src/error.rs

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AppError {
#[serde(rename = "bad_request")]
BadRequest(String),

#[serde(rename = "unauthorized")]
Unauthorized(String),

#[serde(rename = "forbidden")]
Forbidden(String),

#[serde(rename = "not_found")]
NotFound(String),

#[serde(rename = "conflict")]
Conflict(String),

#[serde(rename = "internal")]
Internal(String),

#[serde(rename = "service_unavailable")]
ServiceUnavailable(String),
}

impl AppError {
pub fn status_code(&self) -> u16 {
match self {
AppError::BadRequest(_) => 400,
AppError::Unauthorized(_) => 401,
AppError::Forbidden(_) => 403,
AppError::NotFound(_) => 404,
AppError::Conflict(_) => 409,
AppError::Internal(_) => 500,
AppError::ServiceUnavailable(_) => 503,
}
}

pub fn error_type(&self) -> &'static str {
match self {
AppError::BadRequest(_) => "bad_request",
AppError::Unauthorized(_) => "unauthorized",
AppError::Forbidden(_) => "forbidden",
AppError::NotFound(_) => "not_found",
AppError::Conflict(_) => "conflict",
AppError::Internal(_) => "internal",
AppError::ServiceUnavailable(_) => "service_unavailable",
}
}
}

ErrorResponse

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ErrorResponse {
pub error: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}

impl From<AppError> for ErrorResponse {
fn from(err: AppError) -> Self {
ErrorResponse {
error: err.error_type().to_string(),
message: err.to_string(),
details: None,
}
}
}

Result Type

pub type Result<T> = std::result::Result<T, AppError>;

Notification Types

// crates/bf_types/src/notify/webhook.rs

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookRequest {
pub source: WebhookSource,
pub event_type: String,
pub property_id: Option<String>,
pub device_id: Option<String>,
pub payload: serde_json::Value,
pub timestamp: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WebhookSource {
SmartThings,
Yale,
Hive,
Internal,
Other(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookResponse {
pub success: bool,
pub message: Option<String>,
pub processed_at: i64,
}

Property Query Types

// crates/bf_types/src/resident/property_queries.rs

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LookupBySmartthingsIdsRequest {
pub smartthings_property_id: String,
pub smartthings_device_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LookupByYaleIdRequest {
pub yale_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LookupByExternalIdRequest {
pub external_id: String,
pub service_type: ExternalIdServiceType,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExternalIdServiceType {
SmartThings,
Yale,
Hive,
Daikin,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LookupByExternalIdResponse {
pub property_id: String,
pub device_id: String,
pub property_address: String,
pub device_name: String,
pub device_type: DeviceType,
}

User Lifecycle Types

Move-In

// crates/bf_types/src/user/move_in.rs

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MoveInRequest {
#[serde(default)]
pub notes: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveInResponse {
pub success: bool,
pub summary: MoveInSummary,
pub timestamp: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveInSummary {
pub guests_cleared: usize,
pub pin_codes_cleared: usize,
pub access_logs_cleared: usize,
pub notifications_cleared: usize,
pub device_states_reset: usize,
}

Move-Out

// crates/bf_types/src/user/move_out.rs

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveOutRequest {
#[serde(default = "default_true")]
pub archive_data: bool,
#[serde(default)]
pub preserve_energy_data: bool,
}

impl Default for MoveOutRequest {
fn default() -> Self {
Self {
archive_data: true,
preserve_energy_data: false,
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveOutResponse {
pub success: bool,
pub summary: MoveOutSummary,
pub backup_url: Option<String>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveOutSummary {
pub guests_removed: usize,
pub pin_codes_removed: usize,
pub access_logs_archived: usize,
pub energy_readings_archived: usize,
pub notifications_removed: usize,
}

Backup

// crates/bf_types/src/user/backup.rs

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BackupRequest {
#[serde(default)]
pub include_access_logs: bool,
#[serde(default)]
pub include_energy_readings: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupResponse {
pub success: bool,
pub backup_url: String,
pub backup_size_bytes: u64,
pub records_backed_up: BackupRecordCounts,
pub timestamp: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupRecordCounts {
pub guests: usize,
pub pin_codes: usize,
pub access_logs: usize,
pub energy_readings: usize,
pub notifications: usize,
}

OTA Types

// crates/bf_types/src/ota.rs

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildInfo {
pub version: u32,
pub file_hash: String, // SHA-256 base64
pub min_app_version: String, // Semver
pub file_name: String, // "v{version}.zstd"
pub created_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedBuildInfo {
pub data: BuildInfo,
pub data_signature: String, // Ed25519 signature (base64)
}

Smart Device Types

// crates/bf_types/src/smart/devices.rs

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub enum DeviceType {
SmartLock,
Thermostat,
DaikinThermostat, // Daikin Altherma (via Onecta Cloud API)
ContactSensor,
MotionSensor,
WaterSensor,
SmokeSensor,
VideoDoorbellPro,
AlarmHub,
SmartPlug,
EnergyMeter,
Oven,
Hob,
Extractor,
Dishwasher,
WasherDryer,
FridgeFreezer,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub enum DevicePlatform {
SmartThings,
Yale,
Hive,
Daikin,
Mock,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub enum DeviceStatus {
Discovered,
Commissioned,
Offline,
Error,
}

Feature-Gated Mesh Client

The mesh feature enables typed service-to-service clients:

# Cargo.toml
[features]
default = []
mesh = ["reqwest", "clerk-auth"]
// crates/bf_types/src/mesh/client.rs

#[cfg(feature = "mesh")]
pub struct Mesh { /* ... */ }

#[cfg(feature = "mesh")]
impl Mesh {
pub fn resident(&self) -> ResidentClient<'_> { /* ... */ }
pub fn smart(&self) -> SmartClient<'_> { /* ... */ }
pub fn notify(&self) -> NotifyClient<'_> { /* ... */ }
}

Dependencies

# crates/bf_types/Cargo.toml

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1.0", features = ["v4", "serde"] }
thiserror = "1.0"

# OpenAPI documentation
utoipa = { version = "4", features = ["chrono", "uuid"] }

# TypeScript generation
specta = { version = "2", optional = true }

# Mesh client (optional)
reqwest = { version = "0.11", optional = true }
clerk-auth = { path = "../clerk-auth", optional = true }

See Also