1use std::sync::Arc;
2
3use async_trait::async_trait;
4use modkit::api::OpenApiRegistry;
5use modkit::{Module, ModuleCtx, RestApiCapability};
6use tracing::{debug, info};
7
8use crate::config::FileParserConfig;
9use crate::domain::service::{FileParserService, ServiceConfig};
10use crate::infra::parsers::{
11 DocxParser, HtmlParser, ImageParser, PdfParser, PlainTextParser, PptxParser, StubParser,
12 XlsxParser,
13};
14
15#[modkit::module(
17 name = "file_parser",
18 capabilities = [rest]
19)]
20pub struct FileParserModule {
21 service: arc_swap::ArcSwapOption<FileParserService>,
23}
24
25impl Default for FileParserModule {
26 fn default() -> Self {
27 Self {
28 service: arc_swap::ArcSwapOption::from(None),
29 }
30 }
31}
32
33impl Clone for FileParserModule {
34 fn clone(&self) -> Self {
35 Self {
36 service: arc_swap::ArcSwapOption::new(self.service.load().as_ref().map(Clone::clone)),
37 }
38 }
39}
40
41#[async_trait]
42impl Module for FileParserModule {
43 #[allow(clippy::cast_possible_truncation)]
44 async fn init(&self, ctx: &ModuleCtx) -> anyhow::Result<()> {
45 const BYTES_IN_MB: u64 = 1024_u64 * 1024;
46
47 info!("Initializing file_parser module");
48
49 let cfg: FileParserConfig = ctx.config()?;
51 debug!(
52 "Loaded file_parser config: max_file_size_mb={}, download_timeout_secs={}",
53 cfg.max_file_size_mb, cfg.download_timeout_secs
54 );
55
56 let parsers: Vec<Arc<dyn crate::domain::parser::FileParserBackend>> = vec![
58 Arc::new(PlainTextParser::new()),
59 Arc::new(HtmlParser::new()),
60 Arc::new(PdfParser::new()),
61 Arc::new(DocxParser::new()),
62 Arc::new(XlsxParser::new()),
63 Arc::new(PptxParser::new()),
64 Arc::new(ImageParser::new()),
65 Arc::new(StubParser::new()),
66 ];
67
68 info!("Registered {} parser backends", parsers.len());
69
70 let service_config = ServiceConfig {
72 max_file_size_bytes: usize::try_from(cfg.max_file_size_mb * BYTES_IN_MB)
73 .unwrap_or(usize::MAX),
74 download_timeout_secs: cfg.download_timeout_secs,
75 };
76
77 let file_parser_service = Arc::new(FileParserService::new(parsers, service_config));
79
80 self.service.store(Some(file_parser_service));
82
83 info!("FileParserService initialized successfully");
84 Ok(())
85 }
86}
87
88impl RestApiCapability for FileParserModule {
89 fn register_rest(
90 &self,
91 _ctx: &ModuleCtx,
92 router: axum::Router,
93 openapi: &dyn OpenApiRegistry,
94 ) -> anyhow::Result<axum::Router> {
95 info!("Registering file_parser REST routes");
96
97 let service = self
98 .service
99 .load()
100 .as_ref()
101 .ok_or_else(|| anyhow::anyhow!("Service not initialized"))?
102 .clone();
103
104 let router = crate::api::rest::routes::register_routes(router, openapi, service);
105
106 info!("File parser REST routes registered successfully");
107 Ok(router)
108 }
109}