Add Printer trait + PrinterHandle enum domain model with polymorphism example
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
//! Printer hierarchy — the Rust answer to
|
||||
//! `GenericPrinter -> GenericBambuPrinter -> BambuPrinterV1/V2`-style
|
||||
//! inheritance.
|
||||
//!
|
||||
//! Rust structs can't `extend` each other, so instead of an inheritance
|
||||
//! chain we use three separate, composable mechanisms:
|
||||
//!
|
||||
//! 1. **Composition** (`PrinterBase` as a field) for shared *data* — stands
|
||||
//! in for a base class's fields.
|
||||
//! 2. **A trait** (`Printer`) for shared *behavior* — stands in for a base
|
||||
//! class's methods. A method with no default body (like `connect`) must
|
||||
//! be supplied by every implementor: that's what "overriding" looks like
|
||||
//! here, since there's no inherited body to override in the first place.
|
||||
//! 3. **An enum** (`PrinterHandle`) for the closed set of concrete printer
|
||||
//! kinds — stands in for "any subclass of GenericPrinter". Matching on it
|
||||
//! is exhaustive, so the compiler forces every call site to handle a new
|
||||
//! vendor when one is added.
|
||||
//!
|
||||
//! See `examples/printer_polymorphism.rs` for this in action.
|
||||
|
||||
mod bambu;
|
||||
mod klipper;
|
||||
mod prusa;
|
||||
|
||||
pub use bambu::BambuPrinter;
|
||||
pub use klipper::KlipperPrinter;
|
||||
pub use prusa::PrusaPrinter;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PrinterError {
|
||||
#[error("connection to {0} failed: {1}")]
|
||||
Connection(String, String),
|
||||
#[error("{0} does not support this operation")]
|
||||
Unsupported(&'static str),
|
||||
}
|
||||
|
||||
/// Fields every printer has, regardless of vendor. Every concrete printer
|
||||
/// struct holds one of these as a field rather than "inheriting" it.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrinterBase {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub host: String,
|
||||
}
|
||||
|
||||
/// The shared interface — think `interface Printer` (Java/TS) or an ABC
|
||||
/// (Python). Every vendor's struct implements this.
|
||||
pub trait Printer {
|
||||
/// Read-only access to the shared fields (the "inherited" data).
|
||||
fn base(&self) -> &PrinterBase;
|
||||
|
||||
/// REQUIRED, no default body: every printer vendor speaks a different
|
||||
/// protocol, so there's nothing sensible to share here. Each `impl`
|
||||
/// below provides a completely different body — that's the "override".
|
||||
async fn connect(&mut self) -> Result<(), PrinterError>;
|
||||
|
||||
/// OPTIONAL: a default body every printer gets for free unless it
|
||||
/// chooses to provide its own. None of ours do, so all three vendors
|
||||
/// use this exact implementation — this is the trait-method equivalent
|
||||
/// of inheriting a base-class method unchanged.
|
||||
fn display_name(&self) -> String {
|
||||
format!("{} ({}) @ {}", self.base().name, self.base().id, self.base().host)
|
||||
}
|
||||
}
|
||||
|
||||
/// The closed set of printer kinds this fleet can talk to — stands in for
|
||||
/// "any subclass of GenericPrinter" without needing a `Box<dyn Printer>`.
|
||||
///
|
||||
/// The `impl Printer for PrinterHandle` below is hand-written delegation:
|
||||
/// each trait method just matches on the variant and forwards to that
|
||||
/// variant's own implementation. This is exactly the boilerplate a crate
|
||||
/// like `enum_dispatch` would generate for you — written out by hand here
|
||||
/// so the mechanism is visible rather than hidden behind a macro.
|
||||
pub enum PrinterHandle {
|
||||
Bambu(BambuPrinter),
|
||||
Prusa(PrusaPrinter),
|
||||
Klipper(KlipperPrinter),
|
||||
}
|
||||
|
||||
impl Printer for PrinterHandle {
|
||||
fn base(&self) -> &PrinterBase {
|
||||
match self {
|
||||
PrinterHandle::Bambu(p) => p.base(),
|
||||
PrinterHandle::Prusa(p) => p.base(),
|
||||
PrinterHandle::Klipper(p) => p.base(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(&mut self) -> Result<(), PrinterError> {
|
||||
match self {
|
||||
PrinterHandle::Bambu(p) => p.connect().await,
|
||||
PrinterHandle::Prusa(p) => p.connect().await,
|
||||
PrinterHandle::Klipper(p) => p.connect().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user