1use crate::error::ServerError;
44use citum_engine::{
45 Bibliography, 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,
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 locale: Option<String>,
158 pub output_format: Option<OutputFormat>,
160 pub refs: serde_json::Value,
163 pub citations: serde_json::Value,
165 pub document_options: Option<DocumentOptions>,
167}
168
169#[cfg(feature = "session")]
171#[derive(Debug, Deserialize)]
172#[cfg_attr(
173 any(feature = "schema", feature = "schema-types"),
174 derive(schemars::JsonSchema)
175)]
176pub struct OpenSessionParams {
177 pub style: StyleInput,
179 pub locale: Option<String>,
181 pub output_format: Option<OutputFormatKind>,
183 pub document_options: Option<DocumentOptions>,
185}
186
187#[cfg(feature = "session")]
189#[derive(Debug, Deserialize)]
190#[cfg_attr(
191 any(feature = "schema", feature = "schema-types"),
192 derive(schemars::JsonSchema)
193)]
194pub struct PutReferencesParams {
195 pub session_id: Option<String>,
197 pub refs: RefsInput,
199}
200
201#[cfg(feature = "session")]
203#[derive(Debug, Deserialize)]
204#[cfg_attr(
205 any(feature = "schema", feature = "schema-types"),
206 derive(schemars::JsonSchema)
207)]
208pub struct InsertCitationsBatchParams {
209 pub session_id: Option<String>,
211 pub citations: Vec<CitationOccurrence>,
213}
214
215#[cfg(feature = "session")]
217#[derive(Debug, Deserialize)]
218#[cfg_attr(
219 any(feature = "schema", feature = "schema-types"),
220 derive(schemars::JsonSchema)
221)]
222pub struct InsertCitationParams {
223 pub session_id: Option<String>,
225 pub citation: CitationOccurrence,
227 pub position: Option<CitationInsertPosition>,
229}
230
231#[cfg(feature = "session")]
233#[derive(Debug, Deserialize)]
234#[cfg_attr(
235 any(feature = "schema", feature = "schema-types"),
236 derive(schemars::JsonSchema)
237)]
238pub struct UpdateCitationParams {
239 pub session_id: Option<String>,
241 pub citation_id: String,
243 pub citation: CitationOccurrence,
245 pub position: Option<CitationInsertPosition>,
247}
248
249#[cfg(feature = "session")]
251#[derive(Debug, Deserialize)]
252#[cfg_attr(
253 any(feature = "schema", feature = "schema-types"),
254 derive(schemars::JsonSchema)
255)]
256pub struct DeleteCitationParams {
257 pub session_id: Option<String>,
259 pub citation_id: String,
261}
262
263#[cfg(feature = "session")]
265#[derive(Debug, Deserialize)]
266#[cfg_attr(
267 any(feature = "schema", feature = "schema-types"),
268 derive(schemars::JsonSchema)
269)]
270pub struct PreviewCitationParams {
271 pub session_id: Option<String>,
273 pub items: Vec<CitationOccurrenceItem>,
275 pub position: Option<CitationInsertPosition>,
277}
278
279#[cfg(feature = "session")]
281#[derive(Debug, Deserialize)]
282#[cfg_attr(
283 any(feature = "schema", feature = "schema-types"),
284 derive(schemars::JsonSchema)
285)]
286pub struct SessionIdParams {
287 pub session_id: Option<String>,
289}
290
291#[derive(Debug, Serialize)]
292struct BibliographyResult {
293 format: OutputFormat,
294 content: String,
295 #[serde(skip_serializing_if = "Option::is_none")]
296 entries: Option<Vec<String>>,
297}
298
299#[derive(Debug)]
300#[cfg(all(feature = "session", feature = "http"))]
301struct StoredSession {
302 session: DocumentSession,
303 last_access: SystemTime,
304}
305
306#[cfg(all(feature = "session", feature = "http"))]
307const HTTP_SESSION_TTL_SECS: u64 = 30 * 60;
308
309#[cfg(feature = "session")]
310#[derive(Debug)]
311enum SessionMode {
312 Stdio {
313 session: Box<Option<DocumentSession>>,
314 },
315 #[cfg(all(feature = "session", feature = "http"))]
316 Http {
317 sessions: HashMap<String, StoredSession>,
318 next_session_id: AtomicU64,
319 ttl: Duration,
320 },
321}
322
323#[derive(Debug)]
325pub struct RpcDispatcher {
326 #[cfg(feature = "session")]
327 session_mode: SessionMode,
328}
329
330#[derive(Debug)]
332pub enum RpcDispatchError {
333 Message(String),
335 Response(Box<Value>),
337}
338
339impl RpcDispatcher {
340 pub fn new_stdio() -> Self {
342 Self {
343 #[cfg(feature = "session")]
344 session_mode: SessionMode::Stdio {
345 session: Box::new(None),
346 },
347 }
348 }
349
350 #[cfg(feature = "http")]
352 pub fn new_http() -> Self {
353 Self {
354 #[cfg(feature = "session")]
355 session_mode: SessionMode::Http {
356 sessions: HashMap::new(),
357 next_session_id: AtomicU64::new(1),
358 ttl: Duration::from_secs(HTTP_SESSION_TTL_SECS),
359 },
360 }
361 }
362
363 pub fn dispatch(
370 &mut self,
371 req: RpcRequest,
372 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
373 let id = req.id.clone();
374
375 match req.method.as_str() {
376 "render_citation" => render_citation(&req.params, id)
377 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
378 "render_bibliography" => render_bibliography(&req.params, id)
379 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
380 "validate_style" => validate_style(&req.params, id)
381 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
382 "format_document" => format_document(&req.params, id)
383 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
384 #[cfg(feature = "session")]
385 "open_session" => self
386 .open_session(&req.params, id)
387 .map_err(|e| (Some(req.id), RpcDispatchError::Message(e.to_string()))),
388 #[cfg(feature = "session")]
389 "put_references" => self.put_references(&req.params, id, req.id.clone()),
390 #[cfg(feature = "session")]
391 "insert_citations_batch" => {
392 self.insert_citations_batch(&req.params, id, req.id.clone())
393 }
394 #[cfg(feature = "session")]
395 "insert_citation" => self.insert_citation(&req.params, id, req.id.clone()),
396 #[cfg(feature = "session")]
397 "update_citation" => self.update_citation(&req.params, id, req.id.clone()),
398 #[cfg(feature = "session")]
399 "delete_citation" => self.delete_citation(&req.params, id, req.id.clone()),
400 #[cfg(feature = "session")]
401 "preview_citation" => self.preview_citation(&req.params, id, req.id.clone()),
402 #[cfg(feature = "session")]
403 "get_citations" => self.get_citations(&req.params, id, req.id.clone()),
404 #[cfg(feature = "session")]
405 "get_bibliography" => self.get_bibliography(&req.params, id, req.id.clone()),
406 #[cfg(feature = "session")]
407 "close_session" => self.close_session(&req.params, id, req.id.clone()),
408 _ => Err((
409 Some(req.id),
410 RpcDispatchError::Message(format!("unknown method: {}", req.method)),
411 )),
412 }
413 }
414
415 #[cfg(feature = "session")]
416 fn open_session(&mut self, params: &Value, id: Value) -> Result<Value, ServerError> {
417 let params: OpenSessionParams = serde_json::from_value(params.clone())
418 .map_err(|e| ServerError::CitationError(format!("Invalid request JSON: {e}")))?;
419 let style = resolve_style_input(¶ms.style)?;
420 let session = DocumentSession::new(
421 style,
422 params.style,
423 params.locale,
424 params.output_format.unwrap_or_default(),
425 params.document_options,
426 );
427 let session_id = match &mut self.session_mode {
428 SessionMode::Stdio { session: slot } => {
429 **slot = Some(session);
430 "default".to_string()
431 }
432 #[cfg(feature = "http")]
433 SessionMode::Http {
434 sessions,
435 next_session_id,
436 ..
437 } => {
438 let next = next_session_id.fetch_add(1, Ordering::Relaxed);
439 let session_id = format!("s-{next:016x}");
440 sessions.insert(
441 session_id.clone(),
442 StoredSession {
443 session,
444 last_access: SystemTime::now(),
445 },
446 );
447 session_id
448 }
449 };
450 let result = OpenSessionResult { session_id };
451 Ok(json!({ "id": id, "result": result }))
452 }
453
454 #[cfg(feature = "session")]
455 fn put_references(
456 &mut self,
457 params: &Value,
458 id: Value,
459 request_id: Value,
460 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
461 let params: PutReferencesParams = parse_session_params(params, &request_id)?;
462 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
463 session.put_references(params.refs);
464 Ok(json!({ "id": id, "result": {} }))
465 }
466
467 #[cfg(feature = "session")]
468 fn insert_citations_batch(
469 &mut self,
470 params: &Value,
471 id: Value,
472 request_id: Value,
473 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
474 let params: InsertCitationsBatchParams = parse_session_params(params, &request_id)?;
475 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
476 let result = session
477 .insert_citations_batch(params.citations)
478 .map_err(session_method_error(&request_id))?;
479 Ok(json!({ "id": id, "result": result }))
480 }
481
482 #[cfg(feature = "session")]
483 fn insert_citation(
484 &mut self,
485 params: &Value,
486 id: Value,
487 request_id: Value,
488 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
489 let params: InsertCitationParams = parse_session_params(params, &request_id)?;
490 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
491 let result = session
492 .insert_citation(params.citation, params.position)
493 .map_err(session_method_error(&request_id))?;
494 Ok(json!({ "id": id, "result": result }))
495 }
496
497 #[cfg(feature = "session")]
498 fn update_citation(
499 &mut self,
500 params: &Value,
501 id: Value,
502 request_id: Value,
503 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
504 let params: UpdateCitationParams = parse_session_params(params, &request_id)?;
505 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
506 let result = session
507 .update_citation(¶ms.citation_id, params.citation, params.position)
508 .map_err(session_method_error(&request_id))?;
509 Ok(json!({ "id": id, "result": result }))
510 }
511
512 #[cfg(feature = "session")]
513 fn delete_citation(
514 &mut self,
515 params: &Value,
516 id: Value,
517 request_id: Value,
518 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
519 let params: DeleteCitationParams = parse_session_params(params, &request_id)?;
520 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
521 let result = session
522 .delete_citation(¶ms.citation_id)
523 .map_err(session_method_error(&request_id))?;
524 Ok(json!({ "id": id, "result": result }))
525 }
526
527 #[cfg(feature = "session")]
528 fn preview_citation(
529 &mut self,
530 params: &Value,
531 id: Value,
532 request_id: Value,
533 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
534 let params: PreviewCitationParams = parse_session_params(params, &request_id)?;
535 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
536 let result = session
537 .preview_citation(params.items, params.position)
538 .map_err(session_method_error(&request_id))?;
539 Ok(json!({ "id": id, "result": result }))
540 }
541
542 #[cfg(feature = "session")]
543 fn get_citations(
544 &mut self,
545 params: &Value,
546 id: Value,
547 request_id: Value,
548 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
549 let params: SessionIdParams = parse_session_params(params, &request_id)?;
550 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
551 Ok(json!({
552 "id": id,
553 "result": { "formatted_citations": session.get_citations() }
554 }))
555 }
556
557 #[cfg(feature = "session")]
558 fn get_bibliography(
559 &mut self,
560 params: &Value,
561 id: Value,
562 request_id: Value,
563 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
564 let params: SessionIdParams = parse_session_params(params, &request_id)?;
565 let session = self.session_mut(params.session_id.as_deref(), &request_id)?;
566 Ok(json!({
567 "id": id,
568 "result": { "bibliography": session.get_bibliography() }
569 }))
570 }
571
572 #[cfg(feature = "session")]
573 fn close_session(
574 &mut self,
575 params: &Value,
576 id: Value,
577 request_id: Value,
578 ) -> Result<Value, (Option<Value>, RpcDispatchError)> {
579 let params: SessionIdParams = parse_session_params(params, &request_id)?;
580 #[cfg(not(feature = "http"))]
581 let _ = ¶ms;
582 match &mut self.session_mode {
583 SessionMode::Stdio { session } => {
584 **session = None;
585 }
586 #[cfg(feature = "http")]
587 SessionMode::Http { sessions, .. } => {
588 let session_id = params.session_id.as_deref().ok_or_else(|| {
589 (
590 Some(request_id.clone()),
591 RpcDispatchError::Message("missing required field: session_id".to_string()),
592 )
593 })?;
594 sessions.remove(session_id);
595 }
596 }
597 Ok(json!({ "id": id, "result": {} }))
598 }
599
600 #[cfg(feature = "session")]
601 fn session_mut(
602 &mut self,
603 session_id: Option<&str>,
604 request_id: &Value,
605 ) -> Result<&mut DocumentSession, (Option<Value>, RpcDispatchError)> {
606 #[cfg(not(feature = "http"))]
607 let _ = session_id;
608 match &mut self.session_mode {
609 SessionMode::Stdio { session } => session.as_mut().as_mut().ok_or_else(|| {
610 (
611 Some(request_id.clone()),
612 RpcDispatchError::Message("session not open".to_string()),
613 )
614 }),
615 #[cfg(feature = "http")]
616 SessionMode::Http { sessions, ttl, .. } => {
617 let session_id = session_id.ok_or_else(|| {
618 (
619 Some(request_id.clone()),
620 RpcDispatchError::Message("missing required field: session_id".to_string()),
621 )
622 })?;
623 if let Some(stored) = sessions.get(session_id)
624 && stored.last_access.elapsed().unwrap_or_default() > *ttl
625 {
626 let expired_at = format_system_time(stored.last_access + *ttl);
627 sessions.remove(session_id);
628 return Err((
629 Some(request_id.clone()),
630 RpcDispatchError::Response(Box::new(json!({
631 "id": request_id,
632 "error": "session_expired",
633 "session_id": session_id,
634 "expired_at": expired_at
635 }))),
636 ));
637 }
638 let stored = sessions.get_mut(session_id).ok_or_else(|| {
639 (
640 Some(request_id.clone()),
641 RpcDispatchError::Message(format!("session not found: {session_id}")),
642 )
643 })?;
644 stored.last_access = SystemTime::now();
645 Ok(&mut stored.session)
646 }
647 }
648 }
649}
650
651impl Default for RpcDispatcher {
652 fn default() -> Self {
653 Self::new_stdio()
654 }
655}
656
657fn require_field(params: &Value, field: &'static str) -> Result<(), ServerError> {
659 if params.get(field).is_none() {
660 return Err(ServerError::MissingField(field.into()));
661 }
662 Ok(())
663}
664
665fn validate_output_format(params: &Value) -> Result<(), ServerError> {
667 if let Some(v) = params.get("output_format") {
668 serde_json::from_value::<OutputFormat>(v.clone()).map_err(|_| {
669 let raw = v.as_str().unwrap_or("unknown").to_string();
670 ServerError::UnsupportedOutputFormat(raw.into())
671 })?;
672 }
673 Ok(())
674}
675
676pub fn dispatch(req: RpcRequest) -> Result<Value, (Option<Value>, String)> {
687 let mut dispatcher = RpcDispatcher::new_stdio();
688 dispatcher.dispatch(req).map_err(|(id, error)| match error {
689 RpcDispatchError::Message(message) => (id, message),
690 RpcDispatchError::Response(value) => (id, value.to_string()),
691 })
692}
693
694fn render_citation(params: &Value, id: Value) -> Result<Value, ServerError> {
696 require_field(params, "style_path")?;
697 require_field(params, "refs")?;
698 require_field(params, "citation")?;
699 validate_output_format(params)?;
700 let params: RenderCitationParams = serde_json::from_value(params.clone())
701 .map_err(|e| ServerError::CitationError(e.to_string()))?;
702
703 let style = load_style(¶ms.style_path)?;
705
706 let bibliography: Bibliography = serde_json::from_value(params.refs.clone())
708 .map_err(|e| ServerError::BibliographyError(e.to_string()))?;
709
710 let citation: Citation = serde_json::from_value(params.citation.clone())
711 .map_err(|e| ServerError::CitationError(e.to_string()))?;
712
713 let mut processor = Processor::new(style, bibliography);
715 let inject_ast_indices = params.inject_ast_indices.unwrap_or(false);
716 processor.set_inject_ast_indices(inject_ast_indices);
717
718 let output_format = params.output_format.unwrap_or_default();
719 let result = render_citation_with_format(&processor, &citation, output_format)
720 .map_err(|e| ServerError::CitationError(e.to_string()))?;
721
722 Ok(json!({
723 "id": id,
724 "result": result
725 }))
726}
727
728fn render_bibliography(params: &Value, id: Value) -> Result<Value, ServerError> {
730 require_field(params, "style_path")?;
731 require_field(params, "refs")?;
732 validate_output_format(params)?;
733 let params: RenderBibliographyParams = serde_json::from_value(params.clone())
734 .map_err(|e| ServerError::CitationError(e.to_string()))?;
735
736 let style = load_style(¶ms.style_path)?;
738
739 let bibliography: Bibliography = serde_json::from_value(params.refs.clone())
741 .map_err(|e| ServerError::BibliographyError(e.to_string()))?;
742
743 let mut processor = Processor::new(style, bibliography);
745 let inject_ast_indices = params.inject_ast_indices.unwrap_or(false);
746 processor.set_inject_ast_indices(inject_ast_indices);
747
748 let output_format = params.output_format.unwrap_or_default();
749 let content = render_bibliography_with_format(&processor, output_format)?;
750 let entries = matches!(output_format, OutputFormat::Plain).then(|| {
751 content
752 .lines()
753 .filter(|line| !line.is_empty())
754 .map(std::string::ToString::to_string)
755 .collect()
756 });
757 let result = BibliographyResult {
758 format: output_format,
759 content,
760 entries,
761 };
762
763 Ok(json!({
764 "id": id,
765 "result": result
766 }))
767}
768
769fn render_citation_with_format(
770 processor: &Processor,
771 citation: &Citation,
772 format: OutputFormat,
773) -> Result<String, ServerError> {
774 match format {
775 OutputFormat::Plain => Ok(processor.process_citation_with_format::<PlainText>(citation)?),
776 OutputFormat::Html => Ok(processor.process_citation_with_format::<Html>(citation)?),
777 OutputFormat::Djot => Ok(processor.process_citation_with_format::<Djot>(citation)?),
778 OutputFormat::Latex => Ok(processor.process_citation_with_format::<Latex>(citation)?),
779 OutputFormat::Typst => Ok(processor.process_citation_with_format::<Typst>(citation)?),
780 }
781}
782
783fn render_bibliography_with_format(
784 processor: &Processor,
785 format: OutputFormat,
786) -> Result<String, ServerError> {
787 match format {
788 OutputFormat::Plain => Ok(processor.render_bibliography_with_format::<PlainText>()),
789 OutputFormat::Html => Ok(processor.render_bibliography_with_format::<Html>()),
790 OutputFormat::Djot => Ok(processor.render_bibliography_with_format::<Djot>()),
791 OutputFormat::Latex => Ok(processor.render_bibliography_with_format::<Latex>()),
792 OutputFormat::Typst => Ok(processor.render_bibliography_with_format::<Typst>()),
793 }
794}
795
796fn validate_style(params: &Value, id: Value) -> Result<Value, ServerError> {
798 require_field(params, "style_path")?;
799 let params: ValidateStyleParams = serde_json::from_value(params.clone())
800 .map_err(|e| ServerError::CitationError(e.to_string()))?;
801
802 match load_style(¶ms.style_path) {
803 Ok(_) => Ok(json!({
804 "id": id,
805 "result": {
806 "valid": true,
807 "warnings": []
808 }
809 })),
810 Err(e) => Ok(json!({
811 "id": id,
812 "result": {
813 "valid": false,
814 "warnings": [e.to_string()]
815 }
816 })),
817 }
818}
819
820fn format_document(params: &Value, id: Value) -> Result<Value, ServerError> {
822 let request: citum_engine::FormatDocumentRequest = serde_json::from_value(params.clone())
823 .map_err(|e| ServerError::CitationError(format!("Invalid request JSON: {}", e)))?;
824
825 let result = match &request.style {
826 citum_engine::StyleInput::Yaml(_) => citum_engine::format_document(request)
827 .map_err(|e| ServerError::CitationError(e.to_string()))?,
828 citum_engine::StyleInput::Id(s)
829 | citum_engine::StyleInput::Uri(s)
830 | citum_engine::StyleInput::Path(s) => {
831 let style = load_style(s)?;
832 citum_engine::format_document_with_style(style, request)
833 .map_err(|e| ServerError::CitationError(e.to_string()))?
834 }
835 };
836
837 let result_json =
838 serde_json::to_value(&result).map_err(|e| ServerError::CitationError(e.to_string()))?;
839
840 Ok(json!({
841 "id": id,
842 "result": result_json
843 }))
844}
845
846#[cfg(feature = "session")]
847fn resolve_style_input(style_input: &StyleInput) -> Result<Style, ServerError> {
848 match style_input {
849 StyleInput::Yaml(_) => style_input
850 .resolve_local()
851 .map_err(|e| ServerError::CitationError(e.to_string())),
852 StyleInput::Id(s) | StyleInput::Uri(s) | StyleInput::Path(s) => load_style(s),
853 }
854}
855
856fn load_style(style_input: &str) -> Result<Style, ServerError> {
862 use citum_store::resolver::{ResolverError, StyleResolver};
863
864 let chain = citum_store::build_standard_chain()
865 .map_err(|e| ServerError::ResolverError(e.to_string()))?;
866
867 match chain.resolve_style(style_input) {
868 Ok(style) => {
869 let mut resolved = style
870 .try_into_resolved_with(Some(&chain))
871 .map_err(|e| ServerError::StyleResolution(e.to_string()))?;
872 resolved.extends = None;
873 Ok(resolved)
874 }
875 Err(ResolverError::StyleNotFound(_)) => {
876 Err(ServerError::StyleNotFound(style_input.to_string()))
877 }
878 Err(e) => Err(ServerError::ResolverError(e.to_string())),
879 }
880}
881
882#[cfg(feature = "session")]
883fn parse_session_params<T>(
884 params: &Value,
885 request_id: &Value,
886) -> Result<T, (Option<Value>, RpcDispatchError)>
887where
888 T: for<'de> Deserialize<'de>,
889{
890 serde_json::from_value(params.clone()).map_err(|e| {
891 (
892 Some(request_id.clone()),
893 RpcDispatchError::Message(format!("Invalid request JSON: {e}")),
894 )
895 })
896}
897
898#[cfg(feature = "session")]
899fn session_method_error(
900 request_id: &Value,
901) -> impl FnOnce(citum_engine::DocumentSessionError) -> (Option<Value>, RpcDispatchError) + '_ {
902 |err| {
903 (
904 Some(request_id.clone()),
905 RpcDispatchError::Message(err.to_string()),
906 )
907 }
908}
909
910#[cfg(all(feature = "session", feature = "http"))]
911fn format_system_time(time: SystemTime) -> String {
912 let seconds = time
913 .duration_since(UNIX_EPOCH)
914 .unwrap_or_default()
915 .as_secs();
916 if let Ok(seconds) = i64::try_from(seconds)
917 && let Ok(datetime) = time::OffsetDateTime::from_unix_timestamp(seconds)
918 && let Ok(formatted) = datetime.format(&time::format_description::well_known::Rfc3339)
919 {
920 return formatted;
921 }
922 format!("unix:{seconds}")
923}
924
925pub fn error_response(id: Option<Value>, error: RpcDispatchError) -> Value {
927 match error {
928 RpcDispatchError::Message(error) => json!({
929 "id": id,
930 "error": error
931 }),
932 RpcDispatchError::Response(response) => *response,
933 }
934}
935
936pub fn run_stdio() -> io::Result<()> {
944 let stdin = io::stdin();
945 let mut stdout = io::stdout();
946 let mut dispatcher = RpcDispatcher::new_stdio();
947
948 let reader = stdin.lock();
949 for line in reader.lines() {
950 let line = line?;
951
952 if line.is_empty() {
954 continue;
955 }
956
957 let response = match serde_json::from_str::<RpcRequest>(&line) {
959 Ok(req) => match dispatcher.dispatch(req.clone()) {
960 Ok(result) => result,
961 Err((id, error)) => error_response(id, error),
962 },
963 Err(e) => {
964 json!({
966 "id": Value::Null,
967 "error": format!("invalid JSON: {}", e)
968 })
969 }
970 };
971
972 writeln!(stdout, "{response}")?;
974 stdout.flush()?;
975 }
976
977 Ok(())
978}
979
980impl Clone for RpcRequest {
982 fn clone(&self) -> Self {
983 RpcRequest {
984 id: self.id.clone(),
985 method: self.method.clone(),
986 params: self.params.clone(),
987 }
988 }
989}