max / makenotwork
1 file changed,
+124 insertions,
-0 deletions
| @@ -184,6 +184,121 @@ pub struct AppLimiters { | |||
| 184 | 184 | pub git_smart_http_semaphore: Arc<tokio::sync::Semaphore>, | |
| 185 | 185 | } | |
| 186 | 186 | ||
| 187 | + | // --------------------------------------------------------------------------- | |
| 188 | + | // Capability slices (decomposition Phase 1). | |
| 189 | + | // | |
| 190 | + | // Each of these is a logical grouping of `AppState` fields that a handler can | |
| 191 | + | // extract as `State<Slice>` instead of the whole god struct. They are projection | |
| 192 | + | // *views*, not physical containers: the fields keep their existing homes on | |
| 193 | + | // `AppState` (flat, or inside `AppStorage`/`AppCaches`/`AppLimiters`), and a | |
| 194 | + | // hand-written `FromRef<AppState>` clones the relevant ones on extraction. This | |
| 195 | + | // is what lets a slice like `Sync` or `Scanning` draw from fields that live in | |
| 196 | + | // different physical sub-structs. Every field here is `Arc`/pool/small-`Clone`, | |
| 197 | + | // so the projection clone is cheap. Single-field slices (e.g. `State<PgPool>`, | |
| 198 | + | // `State<EmailClient>`, `State<BackgroundTx>`) come free from the `AppState` | |
| 199 | + | // derive and don't need a struct here. | |
| 200 | + | // | |
| 201 | + | // See `_private/docs/mnw/appstate-decomposition-plan.md`. | |
| 202 | + | // --------------------------------------------------------------------------- | |
| 203 | + | ||
| 204 | + | /// Billing/pricing slice: the Stripe provider plus the pricing tables derived | |
| 205 | + | /// from `assumptions.toml`. Used by checkout, subscription, promo, and the | |
| 206 | + | /// public `/pricing` page. | |
| 207 | + | #[derive(Clone)] | |
| 208 | + | pub struct Billing { | |
| 209 | + | pub stripe: Option<Arc<dyn PaymentProvider>>, | |
| 210 | + | pub tier_prices: tier_prices::TierPrices, | |
| 211 | + | pub runway_config: tier_prices::RunwayConfig, | |
| 212 | + | pub pricing_comparison: pricing_comparison::PricingComparison, | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | impl FromRef<AppState> for Billing { | |
| 216 | + | fn from_ref(s: &AppState) -> Self { | |
| 217 | + | Self { | |
| 218 | + | stripe: s.stripe.clone(), | |
| 219 | + | tier_prices: s.tier_prices.clone(), | |
| 220 | + | runway_config: s.runway_config.clone(), | |
| 221 | + | pricing_comparison: s.pricing_comparison.clone(), | |
| 222 | + | } | |
| 223 | + | } | |
| 224 | + | } | |
| 225 | + | ||
| 226 | + | /// External-service integration clients (both optional, tailnet/internal). | |
| 227 | + | #[derive(Clone)] | |
| 228 | + | pub struct Integrations { | |
| 229 | + | pub mt_client: Option<mt_client::MtClient>, | |
| 230 | + | pub wam: Option<wam_client::WamClient>, | |
| 231 | + | } | |
| 232 | + | ||
| 233 | + | impl FromRef<AppState> for Integrations { | |
| 234 | + | fn from_ref(s: &AppState) -> Self { | |
| 235 | + | Self { | |
| 236 | + | mt_client: s.mt_client.clone(), | |
| 237 | + | wam: s.wam.clone(), | |
| 238 | + | } | |
| 239 | + | } | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | /// File-scanning slice: the scan pipeline plus the semaphore that bounds | |
| 243 | + | /// concurrent in-memory scans. Draws `scanner` from the top level and | |
| 244 | + | /// `scan_semaphore` from [`AppLimiters`]. | |
| 245 | + | #[derive(Clone)] | |
| 246 | + | pub struct Scanning { | |
| 247 | + | pub scanner: Option<Arc<ScanPipeline>>, | |
| 248 | + | pub scan_semaphore: Arc<tokio::sync::Semaphore>, | |
| 249 | + | } | |
| 250 | + | ||
| 251 | + | impl FromRef<AppState> for Scanning { | |
| 252 | + | fn from_ref(s: &AppState) -> Self { | |
| 253 | + | Self { | |
| 254 | + | scanner: s.scanner.clone(), | |
| 255 | + | scan_semaphore: s.limiters.scan_semaphore.clone(), | |
| 256 | + | } | |
| 257 | + | } | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | /// SyncKit slice: the blob bucket plus the SSE push/rate-limit maps. Draws | |
| 261 | + | /// `synckit_s3` from [`AppStorage`] and the two maps from [`AppCaches`]. | |
| 262 | + | #[derive(Clone)] | |
| 263 | + | pub struct Sync { | |
| 264 | + | pub synckit_s3: Option<Arc<dyn StorageBackend>>, | |
| 265 | + | pub sync_notify: Arc<DashMap<(SyncAppId, UserId), tokio::sync::broadcast::Sender<i64>>>, | |
| 266 | + | pub sse_connections: Arc<DashMap<UserId, std::sync::atomic::AtomicUsize>>, | |
| 267 | + | } | |
| 268 | + | ||
| 269 | + | impl FromRef<AppState> for Sync { | |
| 270 | + | fn from_ref(s: &AppState) -> Self { | |
| 271 | + | Self { | |
| 272 | + | synckit_s3: s.storage.synckit_s3.clone(), | |
| 273 | + | sync_notify: s.caches.sync_notify.clone(), | |
| 274 | + | sse_connections: s.caches.sse_connections.clone(), | |
| 275 | + | } | |
| 276 | + | } | |
| 277 | + | } | |
| 278 | + | ||
| 279 | + | /// Observability/lifecycle slice: uptime clocks, the pending-restart flag, and | |
| 280 | + | /// the Prometheus render handle. Used by status/health/admin/metrics handlers. | |
| 281 | + | /// (`started_at`/`start_instant` are `#[from_ref(skip)]` on `AppState`, so this | |
| 282 | + | /// view is the only way to extract them without the whole struct.) | |
| 283 | + | #[derive(Clone)] | |
| 284 | + | pub struct Ops { | |
| 285 | + | pub started_at: chrono::DateTime<chrono::Utc>, | |
| 286 | + | pub start_instant: Instant, | |
| 287 | + | pub restart_at: Arc<std::sync::atomic::AtomicI64>, | |
| 288 | + | pub metrics_handle: Option<metrics_exporter_prometheus::PrometheusHandle>, | |
| 289 | + | } | |
| 290 | + | ||
| 291 | + | impl FromRef<AppState> for Ops { | |
| 292 | + | fn from_ref(s: &AppState) -> Self { | |
| 293 | + | Self { | |
| 294 | + | started_at: s.started_at, | |
| 295 | + | start_instant: s.start_instant, | |
| 296 | + | restart_at: s.restart_at.clone(), | |
| 297 | + | metrics_handle: s.metrics_handle.clone(), | |
| 298 | + | } | |
| 299 | + | } | |
| 300 | + | } | |
| 301 | + | ||
| 187 | 302 | /// Externally-wired dependencies handed to [`AppState::build`]. | |
| 188 | 303 | /// | |
| 189 | 304 | /// Holds every field the caller must construct from the environment (pools, | |
| @@ -570,12 +685,21 @@ mod from_ref_slices { | |||
| 570 | 685 | ||
| 571 | 686 | #[test] | |
| 572 | 687 | fn slices_are_extractable() { | |
| 688 | + | // Single-field slices from the AppState derive. | |
| 573 | 689 | _assert_from_ref::<sqlx::PgPool>(); | |
| 574 | 690 | _assert_from_ref::<Config>(); | |
| 575 | 691 | _assert_from_ref::<EmailClient>(); | |
| 692 | + | _assert_from_ref::<background::BackgroundTx>(); | |
| 693 | + | // Physical sub-structs (also from the derive). | |
| 576 | 694 | _assert_from_ref::<AppStorage>(); | |
| 577 | 695 | _assert_from_ref::<AppCaches>(); | |
| 578 | 696 | _assert_from_ref::<AppLimiters>(); | |
| 697 | + | // Phase 1 projection views (hand-written FromRef). | |
| 698 | + | _assert_from_ref::<Billing>(); | |
| 699 | + | _assert_from_ref::<Integrations>(); | |
| 700 | + | _assert_from_ref::<Scanning>(); | |
| 701 | + | _assert_from_ref::<Sync>(); | |
| 702 | + | _assert_from_ref::<Ops>(); | |
| 579 | 703 | } | |
| 580 | 704 | } | |
| 581 | 705 |