chore: Move ADR helper files into folders with their corresponding ADR number
This commit is contained in:
232
adr/003-abstractions/topology/src/main.rs
Normal file
232
adr/003-abstractions/topology/src/main.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
// Basic traits from your example
|
||||
trait Topology {}
|
||||
|
||||
trait Score: Clone + std::fmt::Debug {
|
||||
fn get_interpret<T: Topology>(&self) -> Box<dyn Interpret<T>>;
|
||||
fn name(&self) -> String;
|
||||
}
|
||||
|
||||
trait Interpret<T: Topology> {
|
||||
fn execute(&self);
|
||||
}
|
||||
|
||||
struct Maestro<T: Topology> {
|
||||
topology: T
|
||||
}
|
||||
|
||||
impl<T: Topology> Maestro<T> {
|
||||
pub fn new(topology: T) -> Self {
|
||||
Maestro { topology }
|
||||
}
|
||||
|
||||
pub fn register_score<S: Score + 'static>(&self, score: S) {
|
||||
println!("Registering score: {}", score.name());
|
||||
}
|
||||
|
||||
pub fn execute_score<S: Score + 'static>(&self, score: S) {
|
||||
println!("Executing score: {}", score.name());
|
||||
score.get_interpret::<T>().execute();
|
||||
}
|
||||
}
|
||||
|
||||
// Capability traits - these are used to enforce requirements
|
||||
trait CommandExecution {
|
||||
fn execute_command(&self, command: &[String]) -> Result<String, String>;
|
||||
}
|
||||
|
||||
trait FileSystem {
|
||||
fn read_file(&self, path: &str) -> Result<String, String>;
|
||||
fn write_file(&self, path: &str, content: &str) -> Result<(), String>;
|
||||
}
|
||||
|
||||
// A concrete topology implementation
|
||||
#[derive(Clone, Debug)]
|
||||
struct LinuxHostTopology {
|
||||
hostname: String,
|
||||
}
|
||||
|
||||
impl Topology for LinuxHostTopology {}
|
||||
|
||||
// Implement the capabilities for LinuxHostTopology
|
||||
impl CommandExecution for LinuxHostTopology {
|
||||
fn execute_command(&self, command: &[String]) -> Result<String, String> {
|
||||
println!("Executing command on {}: {:?}", self.hostname, command);
|
||||
// In a real implementation, this would use std::process::Command
|
||||
Ok(format!("Command executed successfully on {}", self.hostname))
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystem for LinuxHostTopology {
|
||||
fn read_file(&self, path: &str) -> Result<String, String> {
|
||||
println!("Reading file {} on {}", path, self.hostname);
|
||||
Ok(format!("Content of {} on {}", path, self.hostname))
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &str, content: &str) -> Result<(), String> {
|
||||
println!("Writing to file {} on {}: {}", path, self.hostname, content);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Another topology that doesn't support command execution
|
||||
#[derive(Clone, Debug)]
|
||||
struct BareMetalTopology {
|
||||
device_id: String,
|
||||
}
|
||||
|
||||
impl Topology for BareMetalTopology {}
|
||||
|
||||
impl FileSystem for BareMetalTopology {
|
||||
fn read_file(&self, path: &str) -> Result<String, String> {
|
||||
println!("Reading file {} on device {}", path, self.device_id);
|
||||
Ok(format!("Content of {} on device {}", path, self.device_id))
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &str, content: &str) -> Result<(), String> {
|
||||
println!("Writing to file {} on device {}: {}", path, self.device_id, content);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// CommandScore implementation
|
||||
#[derive(Clone, Debug)]
|
||||
struct CommandScore {
|
||||
name: String,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
impl CommandScore {
|
||||
pub fn new(name: String, args: Vec<String>) -> Self {
|
||||
CommandScore { name, args }
|
||||
}
|
||||
}
|
||||
|
||||
impl Score for CommandScore {
|
||||
fn get_interpret<T: Topology + CommandExecution + 'static>(&self) -> Box<dyn Interpret<T>> {
|
||||
// This is the key part: we constrain T to implement CommandExecution
|
||||
// If T doesn't implement CommandExecution, this will fail to compile
|
||||
Box::new(CommandInterpret::<T>::new(self.clone()))
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// CommandInterpret implementation
|
||||
struct CommandInterpret<T: Topology + CommandExecution> {
|
||||
score: CommandScore,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Topology + CommandExecution> CommandInterpret<T> {
|
||||
pub fn new(score: CommandScore) -> Self {
|
||||
CommandInterpret {
|
||||
score,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Topology + CommandExecution> Interpret<T> for CommandInterpret<T> {
|
||||
fn execute(&self) {
|
||||
println!("Command interpret is executing: {:?}", self.score.args);
|
||||
// In a real implementation, you would call the topology's execute_command method
|
||||
// topology.execute_command(&self.score.args);
|
||||
}
|
||||
}
|
||||
|
||||
// FileScore implementation - a different type of score that requires FileSystem capability
|
||||
#[derive(Clone, Debug)]
|
||||
struct FileScore {
|
||||
name: String,
|
||||
path: String,
|
||||
content: Option<String>,
|
||||
}
|
||||
|
||||
impl FileScore {
|
||||
pub fn new_read(name: String, path: String) -> Self {
|
||||
FileScore { name, path, content: None }
|
||||
}
|
||||
|
||||
pub fn new_write(name: String, path: String, content: String) -> Self {
|
||||
FileScore { name, path, content: Some(content) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Score for FileScore {
|
||||
fn get_interpret<T: Topology>(&self) -> Box<dyn Interpret<T>> {
|
||||
// This constrains T to implement FileSystem
|
||||
Box::new(FileInterpret::<T>::new(self.clone()))
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// FileInterpret implementation
|
||||
struct FileInterpret<T: Topology + FileSystem> {
|
||||
score: FileScore,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Topology + FileSystem> FileInterpret<T> {
|
||||
pub fn new(score: FileScore) -> Self {
|
||||
FileInterpret {
|
||||
score,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Topology + FileSystem> Interpret<T> for FileInterpret<T> {
|
||||
fn execute(&self) {
|
||||
match &self.score.content {
|
||||
Some(content) => {
|
||||
println!("File interpret is writing to {}: {}", self.score.path, content);
|
||||
// In a real implementation: topology.write_file(&self.score.path, content);
|
||||
},
|
||||
None => {
|
||||
println!("File interpret is reading from {}", self.score.path);
|
||||
// In a real implementation: let content = topology.read_file(&self.score.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Create our topologies
|
||||
let linux = LinuxHostTopology { hostname: "server1.example.com".to_string() };
|
||||
let bare_metal = BareMetalTopology { device_id: "device001".to_string() };
|
||||
|
||||
// Create our maestros
|
||||
let linux_maestro = Maestro::new(linux);
|
||||
let bare_metal_maestro = Maestro::new(bare_metal);
|
||||
|
||||
// Create scores
|
||||
let command_score = CommandScore::new(
|
||||
"List Files".to_string(),
|
||||
vec!["ls".to_string(), "-la".to_string()]
|
||||
);
|
||||
|
||||
let file_read_score = FileScore::new_read(
|
||||
"Read Config".to_string(),
|
||||
"/etc/config.json".to_string()
|
||||
);
|
||||
|
||||
// This will work because LinuxHostTopology implements CommandExecution
|
||||
linux_maestro.execute_score(command_score.clone());
|
||||
|
||||
// This will work because LinuxHostTopology implements FileSystem
|
||||
linux_maestro.execute_score(file_read_score.clone());
|
||||
|
||||
// This will work because BareMetalTopology implements FileSystem
|
||||
bare_metal_maestro.execute_score(file_read_score);
|
||||
|
||||
// This would NOT compile because BareMetalTopology doesn't implement CommandExecution:
|
||||
// bare_metal_maestro.execute_score(command_score);
|
||||
// The error would occur at compile time, ensuring type safety
|
||||
|
||||
println!("All scores executed successfully!");
|
||||
}
|
||||
Reference in New Issue
Block a user