1use crate::error::ServerError;
44use citum_engine::{
45 Bibliography, BibliographyBlockRequest, Citation, DocumentOptions, Processor, StyleInput,
46 render::{djot::Djot, html::Html, latex::Latex, plain::PlainText, typst::Typst},
47};
48#[cfg(feature = "session")]
49use citum_engine::{
50 CitationInsertPosition, CitationOccurrence, CitationOccurrenceItem, DocumentSession,
51 OpenSessionResult, OutputFormatKind, RefsInput, apply_style_overrides,
52};
53use citum_schema::Style;
54use serde::{Deserialize, Serialize};
55use serde_json::{Value, json};
56#[cfg(all(feature = "session", feature = "http"))]
57use std::collections::HashMap;
58use std::io::{self, BufRead, Write};
59#[cfg(all(feature = "session", feature = "http"))]
60use std::sync::atomic::{AtomicU64, Ordering};
61#[cfg(all(feature = "session", feature = "http"))]
62use std::time::{Duration, SystemTime, UNIX_EPOCH};
63
64#[derive(Debug, Deserialize)]
66#[cfg_attr(
67 any(feature = "schema", feature = "schema-types"),
68 derive(schemars::JsonSchema)
69)]
70pub struct RpcRequest {
71 pub id: Value,
73 pub method: String,
75 pub params: Value,
77}
78
79#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
81#[serde(rename_all = "lowercase")]
82#[cfg_attr(
83 any(feature = "schema", feature = "schema-types"),
84 derive(schemars::JsonSchema)
85)]
86pub enum OutputFormat {
87 #[default]
89 Plain,
90 Html,
92 Djot,
94 Latex,
96 Typst,
98}
99
100#[derive(Debug, Deserialize)]
102#[cfg_attr(
103 any(feature = "schema", feature = "schema-types"),
104 derive(schemars::JsonSchema)
105)]
106pub struct RenderCitationParams {
107 pub style_path: String,
109 pub refs: serde_json::Value,
111 pub citation: serde_json::Value,
113 pub output_format: Option<OutputFormat>,
115 pub inject_ast_indices: Option<bool>,
117}
118
119#[derive(Debug, Deserialize)]
121#[cfg_attr(
122 any(feature = "schema", feature = "schema-types"),
123 derive(schemars::JsonSchema)
124)]
125pub struct RenderBibliographyParams {
126 pub style_path: String,
128 pub refs: serde_json::Value,
130 pub output_format: Option<OutputFormat>,
132 pub inject_ast_indices: Option<bool>,
134}
135
136#[derive(Debug, Deserialize)]
138#[cfg_attr(
139 any(feature = "schema", feature = "schema-types"),
140 derive(schemars::JsonSchema)
141)]
142pub struct ValidateStyleParams {
143 pub style_path: String,
145}
146
147#[derive(Debug, Deserialize)]
149#[cfg_attr(
150 any(feature = "schema", feature = "schema-types"),
151 derive(schemars::JsonSchema)
152)]
153pub struct FormatDocumentParams {
154 pub style: StyleInput,
156 pub style_overrides: Option<String>,
160 pub locale: Option<String>,
162 pub output_format: Option<OutputFormat>,
164 pub refs: serde_json::Value,
167 pub citations: serde_json::Value,
169 #[serde(default, skip_serializing_if = "Vec::is_empty")]
171 pub bibliography_blocks: Vec<BibliographyBlockRequest>,
172 pub document_options: Option<DocumentOptions>,
174}
175
176#[cfg(feature = "session")]
178#[derive(Debug, Deserialize)]
179#[cfg_attr(
180 any(feature = "schema", feature = "schema-types"),
181 derive(schemars::JsonSchema)
182)]
183pub struct OpenSessionParams {
184 pub style: StyleInput,
186 pub style_overrides: Option<String>,
190 pub locale: Option<String>,
192 pub output_format: Option<OutputFormatKind>,
194 pub document_options: Option<DocumentOptions>,
196}
197
198#[cfg(feature = "session")]
200#[derive(Debug, Deserialize)]
201#[cfg_attr(
202 any(feature = "schema", feature = "schema-types"),
203 derive(schemars::JsonSchema)
204)]
205pub struct PutReferencesParams {
206 pub session_id: Option<String>,
208 pub refs: RefsInput,
210}
211
212#[cfg(feature = "session")]
214#[derive(Debug, Deserialize)]
215#[cfg_attr(
216 any(feature = "schema", feature = "schema-types"),
217 derive(schemars::JsonSchema)
218)]
219pub struct InsertCitationsBatchParams {
220 pub session_id: Option<String>,
222 pub citations: Vec<CitationOccurrence>,
224}
225
226#[cfg(feature = "session")]
228#[derive(Debug, Deserialize)]
229#[cfg_attr(
230 any(feature = "schema", feature = "schema-types"),
231 derive(schemars::JsonSchema)
232)]
233pub struct InsertCitationParams {
234 pub session_id: Option<String>,
236 pub citation: CitationOccurrence,
238 pub position: Option<CitationInsertPosition>,
240}
241
242#[cfg(feature = "session")]
244#[derive(Debug, Deserialize)]
245#[cfg_attr(
246 any(feature = "schema", feature = "schema-types"),
247 derive(schemars::JsonSchema)
248)]
249pub struct UpdateCitationParams {
250 pub session_id: Option<String>,
252 pub citation_id: String,
254 pub citation: CitationOccurrence,
256 pub position: Option<CitationInsertPosition>,
258}
259
260#[cfg(feature = "session")]
262#[derive(Debug, Deserialize)]
263#[cfg_attr(
264 any(feature = "schema", feature = "schema-types"),
265 derive(schemars::JsonSchema)
266)]
267pub struct DeleteCitationParams {
268 pub session_id: Option<String>,
270 pub citation_id: String,
272}
273
274#[cfg(feature = "session")]
276#[derive(Debug, Deserialize)]
277#[cfg_attr(
278 any(feature = "schema", feature = "schema-types"),
279 derive(schemars::JsonSchema)
280)]
281pub struct PreviewCitationParams {
282 pub session_id: Option<String>,
284 pub items: Vec<CitationOccurrenceItem>,
286 pub mode: Option<citum_schema::data::citation::CitationMode>,
288 pub position: Option<CitationInsertPosition>,
290}
291
292#[cfg(feature = "session")]
294#[derive(Debug, Deserialize)]
295#[cfg_attr(
296 any(feature = "schema", feature = "schema-types"),
297 derive(schemars::JsonSchema)
298)]
299pub struct SessionIdParams {
300 pub session_id: Option<String>,
302}
303
304#[derive(Debug, Serialize)]
305struct BibliographyResult {
306 format: OutputFormat,
307 content: String,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 entries: Option<Vec<String>>,
310}
311
312#[derive(Debug)]
313#[cfg(all(feature = "session", feature = "http"))]
314struct StoredSession {
315 session: DocumentSession,
316 last_access: SystemTime,
317}
318
319#[cfg(all(feature = "session", feature = "http"))]
320const HTTP_SESSION_TTL_SECS: u64 = 30 * 60;
321
322#[cfg(feature = "session")]
323#[derive(Debug)]
324enum SessionMode {
325 Stdio {
326 session: Box<Option<DocumentSession>>,
327 },
328 #[cfg(all(feature = "session", feature = "http"))]
329 Http {
330 sessions: HashMap<String, StoredSession>,
331 next_session_id: AtomicU64,
332 ttl: Duration,
333 },
334}
335
336#[derive(Debug)]
338pub struct RpcDispatcher {
339 #[cfg(feature = "session")]
340 session_mode: SessionMode,
341}
342
343#[derive(Debug)]
345pub enum RpcDispatchError {
346 Message(String),
348 Response(Box<Value>),
350}
351
352impl RpcDispatcher {
353 pub fn new_stdio() -> Self {
355 Self {
356 #[cfg(feature = "session")]
357 session_mode: SessionMode::Stdio {
358 session: Box::new(None),
359 },
360 }
361 }
362
363 #[cfg(feature = "http")]
365 pub fn new_http() -> Self {
366 Self {
367 #[cfg(feature = "session")]
368 session_mode: SessionMode::Http {
369 sessions: HashMap::new(),
370 next_session_id: AtomicU64::new(1),
371 ttl: Duration::from_secs(HTTP_SESSION_TTL_SECS),
372 },
373 }
374 }
375
376 pub fn dispatch(
383 &mut self,
384 req: RpcRequest,
385 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
386 let id = req.id.clone();
387
388 match req.method.as_str() {
389 "render_citation" => render_citation(&req.params, id)
390 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
391 "render_bibliography" => render_bibliography(&req.params, id)
392 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
393 "validate_style" => validate_style(&req.params, id)
394 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
395 "format_document" => format_document(&req.params, id)
396 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
397 #[cfg(feature = "session")]
398 "open_session" => self
399 .open_session(&req.params, id)
400 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
401 #[cfg(feature = "session")]
402 "put_references" => self.put_references(&req.params, id, req.id.clone()),
403 #[cfg(feature = "session")]
404 "insert_citations_batch" => {
405 self.insert_citations_batch(&req.params, id, req.id.clone())
406 }
407 #[cfg(feature = "session")]
408 "insert_citation" => self.insert_citation(&req.params, id, req.id.clone()),
409 #[cfg(feature = "session")]
410 "update_citation" => self.update_citation(&req.params, id, req.id.clone()),
411 #[cfg(feature = "session")]
412 "delete_citation" => self.delete_citation(&req.params, id, req.id.clone()),
413 #[cfg(feature = "session")]
414 "preview_citation" => self.preview_citation(&req.params, id, req.id.clone()),
415 #[cfg(feature = "session")]
416 "get_citations" => self.get_citations(&req.params, id, req.id.clone()),
417 #[cfg(feature = "session")]
418 "get_bibliography" => self.get_bibliography(&req.params, id, req.id.clone()),
419 #[cfg(feature = "session")]
420 "close_session" => self.close_session(&req.params, id, req.id.clone()),
421 _ => Err((
422 Some(req.id),
423 RpcDispatchError::Message(format!("unknown method: {}", req.method)),
424 )),
425 }
426 }
427
428 #[cfg(feature = "session")]
429 fn open_session(&mut self, params: &Value, id: Value) -> Result<Value, ServerError> {
430 let params: OpenSessionParams = serde_json::from_value(params.clone())
431 .map_err(|e| ServerError::CitationError(format!("Invalid request JSON: {e}")))?;
432 let mut style = resolve_style_input(¶ms.style)?;
433 if let Some(src) = ¶ms.style_overrides {
434 apply_style_overrides(&mut style, src)
435 .map_err(|e| ServerError::StyleValidation(e.to_string()))?;
436 }
437 let session = DocumentSession::new(
438 style,
439 params.style,
440 params.locale,
441 params.output_format.unwrap_or_default(),
442 params.document_options,
443 );
444 let session_id = match &mut self.session_mode {
445 SessionMode::Stdio { session: slot } => {
446 **slot = Some(session);
447 "default".to_string()
448 }
449 #[cfg(feature = "http")]
450 SessionMode::Http {
451 sessions,
452 next_session_id,
453 ..
454 } => {
455 let next = next_session_id.fetch_add(1, Ordering::Relaxed);
456 let session_id = format!("s-{next:016x}");
457 sessions.insert(
458 session_id.clone(),
459 StoredSession {
460 session,
461 last_access: SystemTime::now(),
462 },
463 );
464 session_id
465 }
466 };
467 let result = OpenSessionResult { session_id };
468 Ok(json!({ "id": id, "result": result }))
469 }
470
471 #[cfg(feature = "session")]
472 fn put_references(
473 &mut self,
474 params: &Value,
475 id: Value,
476 request_id: Value,
477 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
478 let params: PutReferencesParams = parse_session_params(params, &request_id)?;
479 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
480 session.put_references(params.refs);
481 Ok(json!({ "id": id, "result": {} }))
482 }
483
484 #[cfg(feature = "session")]
485 fn insert_citations_batch(
486 &mut self,
487 params: &Value,
488 id: Value,
489 request_id: Value,
490 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
491 let params: InsertCitationsBatchParams = parse_session_params(params, &request_id)?;
492 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
493 let result = session
494 .insert_citations_batch(params.citations)
495 .map_err(session_method_error(&request_id))?;
496 Ok(json!({ "id": id, "result": result }))
497 }
498
499 #[cfg(feature = "session")]
500 fn insert_citation(
501 &mut self,
502 params: &Value,
503 id: Value,
504 request_id: Value,
505 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
506 let params: InsertCitationParams = parse_session_params(params, &request_id)?;
507 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
508 let result = session
509 .insert_citation(params.citation, params.position)
510 .map_err(session_method_error(&request_id))?;
511 Ok(json!({ "id": id, "result": result }))
512 }
513
514 #[cfg(feature = "session")]
515 fn update_citation(
516 &mut self,
517 params: &Value,
518 id: Value,
519 request_id: Value,
520 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
521 let params: UpdateCitationParams = parse_session_params(params, &request_id)?;
522 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
523 let result = session
524 .update_citation(¶ms.citation_id, params.citation, params.position)
525 .map_err(session_method_error(&request_id))?;
526 Ok(json!({ "id": id, "result": result }))
527 }
528
529 #[cfg(feature = "session")]
530 fn delete_citation(
531 &mut self,
532 params: &Value,
533 id: Value,
534 request_id: Value,
535 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
536 let params: DeleteCitationParams = parse_session_params(params, &request_id)?;
537 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
538 let result = session
539 .delete_citation(¶ms.citation_id)
540 .map_err(session_method_error(&request_id))?;
541 Ok(json!({ "id": id, "result": result }))
542 }
543
544 #[cfg(feature = "session")]
545 fn preview_citation(
546 &mut self,
547 params: &Value,
548 id: Value,
549 request_id: Value,
550 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
551 let params: PreviewCitationParams = parse_session_params(params, &request_id)?;
552 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
553 let result = session
554 .preview_citation(params.items, params.mode, params.position)
555 .map_err(session_method_error(&request_id))?;
556 Ok(json!({ "id": id, "result": result }))
557 }
558
559 #[cfg(feature = "session")]
560 fn get_citations(
561 &mut self,
562 params: &Value,
563 id: Value,
564 request_id: Value,
565 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
566 let params: SessionIdParams = parse_session_params(params, &request_id)?;
567 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
568 Ok(json!({
569 "id": id,
570 "result": { "formatted_citations": session.get_citations() }
571 }))
572 }
573
574 #[cfg(feature = "session")]
575 fn get_bibliography(
576 &mut self,
577 params: &Value,
578 id: Value,
579 request_id: Value,
580 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
581 let params: SessionIdParams = parse_session_params(params, &request_id)?;
582 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
583 Ok(json!({
584 "id": id,
585 "result": { "bibliography": session.get_bibliography() }
586 }))
587 }
588
589 #[cfg(feature = "session")]
590 fn close_session(
591 &mut self,
592 params: &Value,
593 id: Value,
594 request_id: Value,
595 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
596 let params: SessionIdParams = parse_session_params(params, &request_id)?;
597 #[cfg(not(feature = "http"))]
598 let _ = ¶ms;
599 match &mut self.session_mode {
600 SessionMode::Stdio { session } => {
601 **session = None;
602 }
603 #[cfg(feature = "http")]
604 SessionMode::Http { sessions, .. } => {
605 let session_id = params.session_id.as_deref().ok_or_else(|| {
606 (
607 Some(request_id.clone()),
608 RpcDispatchError::Message("missing required field: session_id".to_string()),
609 )
610 })?;
611 sessions.remove(session_id);
612 }
613 }
614 Ok(json!({ "id": id, "result": {} }))
615 }
616
617 #[cfg(feature = "session")]
618 fn session_mut(
619 &mut self,
620 session_id: Option<&str>,
621 request_id: &Value,
622 ) -> Result<&mut DocumentSession, (Option<Value>, RpcDispatchError)> {
623 #[cfg(not(feature = "http"))]
624 let _ = session_id;
625 match &mut self.session_mode {
626 SessionMode::Stdio { session } => session.as_mut().as_mut().ok_or_else(|| {
627 (
628 Some(request_id.clone()),
629 RpcDispatchError::Message("session not open".to_string()),
630 )
631 }),
632 #[cfg(feature = "http")]
633 SessionMode::Http { sessions, ttl, .. } => {
634 let session_id = session_id.ok_or_else(|| {
635 (
636 Some(request_id.clone()),
637 RpcDispatchError::Message("missing required field: session_id".to_string()),
638 )
639 })?;
640 if let Some(stored) = sessions.get(session_id)
641 && stored.last_access.elapsed().unwrap_or_default() > *ttl
642 {
643 let expired_at = format_system_time(stored.last_access + *ttl);
644 sessions.remove(session_id);
645 return Err((
646 Some(request_id.clone()),
647 RpcDispatchError::Response(Box::new(json!({
648 "id": request_id,
649 "error": "session_expired",
650 "session_id": session_id,
651 "expired_at": expired_at
652 }))),
653 ));
654 }
655 let stored = sessions.get_mut(session_id).ok_or_else(|| {
656 (
657 Some(request_id.clone()),
658 RpcDispatchError::Message(format!("session not found: {session_id}")),
659 )
660 })?;
661 stored.last_access = SystemTime::now();
662 Ok(&mut stored.session)
663 }
664 }
665 }
666}
667
668impl Default for RpcDispatcher {
669 fn default() -> Self {
670 Self::new_stdio()
671 }
672}
673
674fn require_field(params: &Value, field: &'static str) -> Result<(), ServerError> {
676 if params.get(field).is_none() {
677 return Err(ServerError::MissingField(field.into()));
678 }
679 Ok(())
680}
681
682fn validate_output_format(params: &Value) -> Result<(), ServerError> {
684 if let Some(v) = params.get("output_format") {
685 serde_json::from_value::<OutputFormat>(v.clone()).map_err(|_| {
686 let raw = v.as_str().unwrap_or("unknown").to_string();
687 ServerError::UnsupportedOutputFormat(raw.into())
688 })?;
689 }
690 Ok(())
691}
692
693pub fn dispatch(req: RpcRequest) -> Result<Value, (Option<Value>, String)> {
704 let mut dispatcher = RpcDispatcher::new_stdio();
705 dispatcher.dispatch(req).map_err(|(id, error)| match error {
706 RpcDispatchError::Message(message) => (id, message),
707 RpcDispatchError::Response(value) => (id, value.to_string()),
708 })
709}
710
711fn render_citation(params: &Value, id: Value) -> Result<Value, ServerError> {
713 require_field(params, "style_path")?;
714 require_field(params, "refs")?;
715 require_field(params, "citation")?;
716 validate_output_format(params)?;
717 let params: RenderCitationParams = serde_json::from_value(params.clone())
718 .map_err(|e| ServerError::CitationError(e.to_string()))?;
719
720 let style = load_style(¶ms.style_path)?;
722
723 let bibliography: Bibliography = serde_json::from_value(params.refs.clone())
725 .map_err(|e| ServerError::BibliographyError(e.to_string()))?;
726
727 let citation: Citation = serde_json::from_value(params.citation.clone())
728 .map_err(|e| ServerError::CitationError(e.to_string()))?;
729
730 let mut processor = Processor::new(style, bibliography);
732 let inject_ast_indices = params.inject_ast_indices.unwrap_or(false);
733 processor.set_inject_ast_indices(inject_ast_indices);
734
735 let output_format = params.output_format.unwrap_or_default();
736 let result = render_citation_with_format(&processor, &citation, output_format)
737 .map_err(|e| ServerError::CitationError(e.to_string()))?;
738
739 Ok(json!({
740 "id": id,
741 "result": result
742 }))
743}
744
745fn render_bibliography(params: &Value, id: Value) -> Result<Value, ServerError> {
747 require_field(params, "style_path")?;
748 require_field(params, "refs")?;
749 validate_output_format(params)?;
750 let params: RenderBibliographyParams = serde_json::from_value(params.clone())
751 .map_err(|e| ServerError::CitationError(e.to_string()))?;
752
753 let style = load_style(¶ms.style_path)?;
755
756 let bibliography: Bibliography = serde_json::from_value(params.refs.clone())
758 .map_err(|e| ServerError::BibliographyError(e.to_string()))?;
759
760 let mut processor = Processor::new(style, bibliography);
762 let inject_ast_indices = params.inject_ast_indices.unwrap_or(false);
763 processor.set_inject_ast_indices(inject_ast_indices);
764
765 let output_format = params.output_format.unwrap_or_default();
766 let content = render_bibliography_with_format(&processor, output_format)?;
767 let entries = matches!(output_format, OutputFormat::Plain).then(|| {
768 content
769 .lines()
770 .filter(|line| !line.is_empty())
771 .map(std::string::ToString::to_string)
772 .collect()
773 });
774 let result = BibliographyResult {
775 format: output_format,
776 content,
777 entries,
778 };
779
780 Ok(json!({
781 "id": id,
782 "result": result
783 }))
784}
785
786fn render_citation_with_format(
787 processor: &Processor,
788 citation: &Citation,
789 format: OutputFormat,
790) -> Result<String, ServerError> {
791 match format {
792 OutputFormat::Plain => Ok(processor.process_citation_with_format::<PlainText>(citation)?),
793 OutputFormat::Html => Ok(processor.process_citation_with_format::<Html>(citation)?),
794 OutputFormat::Djot => Ok(processor.process_citation_with_format::<Djot>(citation)?),
795 OutputFormat::Latex => Ok(processor.process_citation_with_format::<Latex>(citation)?),
796 OutputFormat::Typst => Ok(processor.process_citation_with_format::<Typst>(citation)?),
797 }
798}
799
800fn render_bibliography_with_format(
801 processor: &Processor,
802 format: OutputFormat,
803) -> Result<String, ServerError> {
804 match format {
805 OutputFormat::Plain => Ok(processor.render_bibliography_with_format::<PlainText>()),
806 OutputFormat::Html => Ok(processor.render_bibliography_with_format::<Html>()),
807 OutputFormat::Djot => Ok(processor.render_bibliography_with_format::<Djot>()),
808 OutputFormat::Latex => Ok(processor.render_bibliography_with_format::<Latex>()),
809 OutputFormat::Typst => Ok(processor.render_bibliography_with_format::<Typst>()),
810 }
811}
812
813fn validate_style(params: &Value, id: Value) -> Result<Value, ServerError> {
815 require_field(params, "style_path")?;
816 let params: ValidateStyleParams = serde_json::from_value(params.clone())
817 .map_err(|e| ServerError::CitationError(e.to_string()))?;
818
819 match load_style(¶ms.style_path) {
820 Ok(_) => Ok(json!({
821 "id": id,
822 "result": {
823 "valid": true,
824 "warnings": []
825 }
826 })),
827 Err(e) => Ok(json!({
828 "id": id,
829 "result": {
830 "valid": false,
831 "warnings": [e.to_string()]
832 }
833 })),
834 }
835}
836
837fn format_document(params: &Value, id: Value) -> Result<Value, ServerError> {
839 let request: citum_engine::FormatDocumentRequest = serde_json::from_value(params.clone())
840 .map_err(|e| ServerError::CitationError(format!("Invalid request JSON: {}", e)))?;
841
842 let result = match &request.style {
843 citum_engine::StyleInput::Yaml(_) => citum_engine::format_document(request)
844 .map_err(|e| ServerError::CitationError(e.to_string()))?,
845 citum_engine::StyleInput::Id(s)
846 | citum_engine::StyleInput::Uri(s)
847 | citum_engine::StyleInput::Path(s) => {
848 let style = load_style(s)?;
849 citum_engine::format_document_with_style(style, request)
850 .map_err(|e| ServerError::CitationError(e.to_string()))?
851 }
852 };
853
854 let result_json =
855 serde_json::to_value(&result).map_err(|e| ServerError::CitationError(e.to_string()))?;
856
857 Ok(json!({
858 "id": id,
859 "result": result_json
860 }))
861}
862
863#[cfg(feature = "session")]
864fn resolve_style_input(style_input: &StyleInput) -> Result<Style, ServerError> {
865 match style_input {
866 StyleInput::Yaml(_) => style_input
867 .resolve_local()
868 .map_err(|e| ServerError::CitationError(e.to_string())),
869 StyleInput::Id(s) | StyleInput::Uri(s) | StyleInput::Path(s) => load_style(s),
870 }
871}
872
873fn load_style(style_input: &str) -> Result<Style, ServerError> {
879 use citum_store::resolver::{ResolverError, StyleResolver};
880
881 let chain = citum_store::build_standard_chain()
882 .map_err(|e| ServerError::ResolverError(e.to_string()))?;
883
884 match chain.resolve_style(style_input) {
885 Ok(style) => {
886 let mut resolved = style
887 .try_into_resolved_with(Some(&chain))
888 .map_err(|e| ServerError::StyleResolution(e.to_string()))?;
889 resolved.extends = None;
890 Ok(resolved)
891 }
892 Err(ResolverError::StyleNotFound(_)) => {
893 Err(ServerError::StyleNotFound(style_input.to_string()))
894 }
895 Err(e) => Err(ServerError::ResolverError(e.to_string())),
896 }
897}
898
899#[cfg(feature = "session")]
900fn parse_session_params<T>(
901 params: &Value,
902 request_id: &Value,
903) -> Result<T, (Option<Value>, RpcDispatchError)>
904where
905 T: for<'de> Deserialize<'de>,
906{
907 serde_json::from_value(params.clone()).map_err(|e| {
908 (
909 Some(request_id.clone()),
910 RpcDispatchError::Message(format!("Invalid request JSON: {e}")),
911 )
912 })
913}
914
915#[cfg(feature = "session")]
916fn session_method_error(
917 request_id: &Value,
918) -> impl FnOnce(citum_engine::DocumentSessionError) -> (Option<Value>, RpcDispatchError) + '_ {
919 |err| {
920 (
921 Some(request_id.clone()),
922 RpcDispatchError::Message(err.to_string()),
923 )
924 }
925}
926
927#[cfg(all(feature = "session", feature = "http"))]
928fn format_system_time(time: SystemTime) -> String {
929 let seconds = time
930 .duration_since(UNIX_EPOCH)
931 .unwrap_or_default()
932 .as_secs();
933 if let Ok(seconds) = i64::try_from(seconds)
934 && let Ok(datetime) = time::OffsetDateTime::from_unix_timestamp(seconds)
935 && let Ok(formatted) = datetime.format(&time::format_description::well_known::Rfc3339)
936 {
937 return formatted;
938 }
939 format!("unix:{seconds}")
940}
941
942pub fn error_response(id: Option<Value>, error: RpcDispatchError) -> Value {
944 match error {
945 RpcDispatchError::Message(error) => json!({
946 "id": id,
947 "error": error
948 }),
949 RpcDispatchError::Response(response) => *response,
950 }
951}
952
953pub fn run_stdio() -> io::Result<()> {
961 let stdin = io::stdin();
962 let mut stdout = io::stdout();
963 let mut dispatcher = RpcDispatcher::new_stdio();
964
965 let reader = stdin.lock();
966 for line in reader.lines() {
967 let line = line?;
968
969 if line.is_empty() {
971 continue;
972 }
973
974 let response = match serde_json::from_str::<RpcRequest>(&line) {
976 Ok(req) => match dispatcher.dispatch(req.clone()) {
977 Ok(result) => result,
978 Err((id, error)) => error_response(id, error),
979 },
980 Err(e) => {
981 json!({
983 "id": Value::Null,
984 "error": format!("invalid JSON: {}", e)
985 })
986 }
987 };
988
989 writeln!(stdout, "{response}")?;
991 stdout.flush()?;
992 }
993
994 Ok(())
995}
996
997impl Clone for RpcRequest {
999 fn clone(&self) -> Self {
1000 RpcRequest {
1001 id: self.id.clone(),
1002 method: self.method.clone(),
1003 params: self.params.clone(),
1004 }
1005 }
1006}