1use std::collections::HashMap;
8use std::future::Future;
9use std::sync::Arc;
10
11use axum::Router;
12
13use atrg_core::AppState;
14
15use crate::handler::{FeedHandler, FeedRequest};
16use crate::routes;
17use crate::types::{FeedConfig, FeedSkeleton};
18
19pub struct FeedGenerator {
30 did: String,
32 feeds: HashMap<String, (FeedConfig, FeedHandler)>,
34}
35
36impl FeedGenerator {
37 pub fn new(did: impl Into<String>) -> Self {
42 Self {
43 did: did.into(),
44 feeds: HashMap::new(),
45 }
46 }
47
48 pub fn feed<F, Fut>(
70 mut self,
71 id: &str,
72 display_name: &str,
73 description: Option<&str>,
74 handler: F,
75 ) -> Self
76 where
77 F: Fn(FeedRequest, AppState) -> Fut + Send + Sync + 'static,
78 Fut: Future<Output = Result<FeedSkeleton, atrg_xrpc::XrpcError>> + Send + 'static,
79 {
80 let config = FeedConfig {
81 id: id.to_string(),
82 display_name: display_name.to_string(),
83 description: description.map(|s| s.to_string()),
84 avatar: None,
85 };
86 let handler: FeedHandler = Arc::new(move |req, state| Box::pin(handler(req, state)));
87 self.feeds.insert(id.to_string(), (config, handler));
88 self
89 }
90
91 pub fn feed_with_config<F, Fut>(mut self, config: FeedConfig, handler: F) -> Self
96 where
97 F: Fn(FeedRequest, AppState) -> Fut + Send + Sync + 'static,
98 Fut: Future<Output = Result<FeedSkeleton, atrg_xrpc::XrpcError>> + Send + 'static,
99 {
100 let id = config.id.clone();
101 let handler: FeedHandler = Arc::new(move |req, state| Box::pin(handler(req, state)));
102 self.feeds.insert(id, (config, handler));
103 self
104 }
105
106 pub fn did(&self) -> &str {
108 &self.did
109 }
110
111 pub fn feed_count(&self) -> usize {
113 self.feeds.len()
114 }
115
116 pub fn into_router(self) -> Router<AppState> {
122 routes::build_router(self.did, self.feeds)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn new_generator_has_no_feeds() {
132 let gen = FeedGenerator::new("did:web:example.com");
133 assert_eq!(gen.did(), "did:web:example.com");
134 assert_eq!(gen.feed_count(), 0);
135 }
136
137 #[test]
138 fn register_feeds_increments_count() {
139 let gen = FeedGenerator::new("did:web:example.com")
140 .feed("a", "Feed A", None, |_req, _state| async {
141 Ok(FeedSkeleton {
142 feed: vec![],
143 cursor: None,
144 })
145 })
146 .feed("b", "Feed B", Some("desc"), |_req, _state| async {
147 Ok(FeedSkeleton {
148 feed: vec![],
149 cursor: None,
150 })
151 });
152 assert_eq!(gen.feed_count(), 2);
153 }
154
155 #[test]
156 fn duplicate_id_overwrites() {
157 let gen = FeedGenerator::new("did:web:example.com")
158 .feed("a", "First", None, |_req, _state| async {
159 Ok(FeedSkeleton {
160 feed: vec![],
161 cursor: None,
162 })
163 })
164 .feed("a", "Second", None, |_req, _state| async {
165 Ok(FeedSkeleton {
166 feed: vec![],
167 cursor: None,
168 })
169 });
170 assert_eq!(gen.feed_count(), 1);
171 }
172
173 #[test]
174 fn feed_with_config_registers_feed() {
175 let config = FeedConfig {
176 id: "custom-feed".to_string(),
177 display_name: "Custom Feed".to_string(),
178 description: Some("A custom feed from config".to_string()),
179 avatar: None,
180 };
181 let gen = FeedGenerator::new("did:web:example.com").feed_with_config(
182 config,
183 |_req, _state| async {
184 Ok(FeedSkeleton {
185 feed: vec![],
186 cursor: None,
187 })
188 },
189 );
190 assert_eq!(gen.feed_count(), 1);
191 }
192}