1use std::sync::Arc;
2
3use thiserror::Error;
4
5pub mod auth;
6
7pub type MeldResult<T> = Result<T, MeldError>;
8pub type AlloyResult<T> = MeldResult<T>;
9
10#[derive(Debug, Clone)]
11pub struct AppConfig {
12 pub service_name: String,
13 pub environment: String,
14}
15
16impl AppConfig {
17 pub fn local(service_name: impl Into<String>) -> Self {
18 Self {
19 service_name: service_name.into(),
20 environment: "local".to_string(),
21 }
22 }
23}
24
25#[derive(Debug, Error)]
26pub enum MeldError {
27 #[error("validation error: {0}")]
28 Validation(String),
29 #[error("internal error: {0}")]
30 Internal(String),
31}
32
33pub type AlloyError = MeldError;
34
35pub trait GreetingEngine: Send + Sync {
36 fn greet(&self, name: &str) -> MeldResult<String>;
37}
38
39pub trait MetricsSink: Send + Sync {
40 fn incr_counter(&self, name: &str);
41}
42
43#[derive(Debug, Default)]
44pub struct NoopMetrics;
45
46impl MetricsSink for NoopMetrics {
47 fn incr_counter(&self, _name: &str) {}
48}
49
50#[derive(Debug, Clone)]
51pub struct StaticGreetingEngine {
52 prefix: String,
53}
54
55impl StaticGreetingEngine {
56 pub fn new(prefix: impl Into<String>) -> Self {
57 Self {
58 prefix: prefix.into(),
59 }
60 }
61}
62
63impl GreetingEngine for StaticGreetingEngine {
64 fn greet(&self, name: &str) -> MeldResult<String> {
65 if name.trim().is_empty() {
66 return Err(MeldError::Validation("name must not be empty".to_string()));
67 }
68 Ok(format!("{}, {}!", self.prefix, name))
69 }
70}
71
72#[derive(Clone)]
73pub struct AppState {
74 pub config: AppConfig,
75 pub greeter: Arc<dyn GreetingEngine>,
76 pub metrics: Arc<dyn MetricsSink>,
77}
78
79impl AppState {
80 pub fn new(
81 config: AppConfig,
82 greeter: Arc<dyn GreetingEngine>,
83 metrics: Arc<dyn MetricsSink>,
84 ) -> Self {
85 Self {
86 config,
87 greeter,
88 metrics,
89 }
90 }
91
92 pub fn local(service_name: impl Into<String>) -> Self {
93 Self {
94 config: AppConfig::local(service_name),
95 greeter: Arc::new(StaticGreetingEngine::new("Hello")),
96 metrics: Arc::new(NoopMetrics),
97 }
98 }
99
100 pub fn greet(&self, name: &str) -> MeldResult<String> {
101 self.metrics.incr_counter("greet.requests");
102 self.greeter.greet(name)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn greet_rejects_empty_name() {
112 let state = AppState::local("meld-test");
113 let result = state.greet(" ");
114 assert!(matches!(result, Err(MeldError::Validation(_))));
115 }
116
117 #[test]
118 fn greet_returns_message() {
119 let state = AppState::local("meld-test");
120 let result = state.greet("Rust");
121 assert_eq!(result.expect("must greet"), "Hello, Rust!");
122 }
123}