use beancount_parser_lima::{
self as parser, BeancountParser, BeancountSources, ParseError, ParseSuccess,
};
use serde::Serialize;
use std::{
borrow::Cow,
io::{self, BufRead, BufReader, Read, Write, stdin, stdout},
path::Path,
};
use crate::api::{
booking::{self, BookingFailure},
json_rpc::*,
types::{
IndexedReport, Report, SyntheticSpan,
parser_type_conversions::{
create_reports_from_booking_errors, create_reports_from_parser_errors,
},
raw::*,
},
};
pub fn serve(path: &Path) {
match BeancountSources::try_from(path) {
Ok(sources) => {
let parser = BeancountParser::new(&sources);
let mut sources = parser::SyntheticSources::new(&sources);
Server(Ok(HealthyServer::new(&mut sources, &parser))).serve(&stdin(), &mut stdout());
}
Err(e) => Server(Err(format!(
"Can't read {}: {}",
path.to_string_lossy(),
&e
)))
.serve(&stdin(), &mut stdout()),
};
}
struct Server<'a>(Result<HealthyServer<'a>, String>);
struct HealthyServer<'a> {
sources: &'a mut parser::SyntheticSources<'a>,
_parser: &'a BeancountParser<'a>,
parsed: Result<Parsed<'a>, Vec<parser::Error>>,
}
struct Parsed<'a> {
pub directives: Vec<parser::Spanned<parser::Directive<'a>>>,
pub options: parser::Options<'a>,
pub plugins: Vec<parser::Plugin<'a>>,
pub warnings: Vec<parser::Warning>,
}
impl<'a> HealthyServer<'a> {
fn new(sources: &'a mut parser::SyntheticSources<'a>, parser: &'a BeancountParser<'a>) -> Self {
let parsed = match parser.parse() {
Ok(ParseSuccess {
directives,
options,
plugins,
warnings,
}) => Ok(Parsed {
directives,
options,
plugins,
warnings,
}),
Err(ParseError { errors, .. }) => Err(errors),
};
Self {
sources,
_parser: parser,
parsed,
}
}
}
impl<'a> Server<'a> {
fn serve<R, W>(&mut self, r: R, w: &mut W)
where
R: Read,
W: Write,
{
let mut buf = String::new();
let mut reader = BufReader::new(r);
tracing::debug!("starting");
loop {
buf.clear();
match reader.read_line(&mut buf) {
Ok(n) => {
if n > 0 {
let json = buf.trim();
tracing::debug!("<- {}", json);
self.dispatch(json, w);
} else {
tracing::debug!("<- EOF");
tracing::debug!("exit");
std::process::exit(0);
}
}
Err(e) => {
tracing::error!("<- {}", &e);
std::process::exit(1);
}
}
}
}
fn dispatch<W>(&mut self, request: &str, w: &mut W)
where
W: Write,
{
use RequestMethod as Method;
match serde_json::from_str::<Request>(request) {
Err(e) => write_other_error(None, ERROR_PARSE, Cow::Owned(e.to_string()), w).unwrap(),
Ok(Request {
id,
jsonrpc,
method,
}) => {
if jsonrpc != JSONRPC_VERSION {
write_other_error(
id,
ERROR_INVALID_REQUEST,
Cow::Owned(format!("JSON-RPC protocol must be 2.0, found {}", jsonrpc)),
w,
)
.unwrap()
} else {
match (&mut self.0, method) {
(Ok(healthy), Method::Status) => healthy.status(id, w).unwrap(),
(Ok(healthy), Method::ParserPlugins) => {
healthy.parser_plugins(id, w).unwrap()
}
(Ok(healthy), Method::ParserDirectives) => {
healthy.parser_directives(id, w).unwrap()
}
(Ok(healthy), Method::ParserOptions) => {
healthy.parser_options(id, w).unwrap()
}
(Ok(healthy), Method::ParserFormatErrors(Params { params })) => healthy
.parser_format_report::<parser::ErrorKind, W>(id, ¶ms, w)
.unwrap(),
(Ok(healthy), Method::ParserFormatWarnings(Params { params })) => healthy
.parser_format_report::<parser::WarningKind, W>(id, ¶ms, w)
.unwrap(),
(Ok(healthy), Method::ParserResolveSpan(Params { params })) => {
healthy.parser_resolve_span(id, ¶ms, w).unwrap()
}
(Ok(healthy), Method::ParserCreateSyntheticSpans(Params { params })) => {
healthy
.parser_create_synthetic_spans(id, ¶ms, w)
.unwrap()
}
(Ok(healthy), Method::Book(optional)) => {
healthy.book(id, optional.params.as_ref(), w).unwrap()
}
(Err(unhealthy), _) => write_other_error(
id,
ERROR_BEANFILE_IO_ERROR,
Cow::Borrowed(unhealthy.as_str()),
w,
)
.unwrap(),
}
}
}
}
}
}
impl<'a> HealthyServer<'a> {
fn status<W>(&self, id: Option<Id>, w: &mut W) -> io::Result<()>
where
W: Write,
{
let response = ResultResponse::new(id, ResultData::Ok);
write_response(&response, w)
}
}
impl<'a> HealthyServer<'a> {
fn parser_plugins<W>(&self, id: Option<Id>, w: &mut W) -> io::Result<()>
where
W: Write,
{
match &self.parsed {
Ok(Parsed { plugins, .. }) => {
let plugins = plugins
.iter()
.map(|plugin| plugin.into())
.collect::<Vec<_>>();
let response = ResultResponse::new(id, ResultData::Plugins(&plugins));
write_response(&response, w)
}
Err(errors) => {
let reports = create_reports_from_parser_errors(errors);
write_error_reports(None, reports, w)
}
}
}
fn parser_directives<W>(&self, id: Option<Id>, w: &mut W) -> io::Result<()>
where
W: Write,
{
match &self.parsed {
Ok(Parsed { directives, .. }) => {
let response = ResultResponse::new(
id,
ResultData::RawDirectives(RawDirectives {
directives: directives
.iter()
.map(Into::<Directive>::into)
.collect::<Vec<_>>(),
warnings: None,
}),
);
write_response(&response, w)
}
Err(errors) => {
let reports = create_reports_from_parser_errors(errors);
write_error_reports(None, reports, w)
}
}
}
fn parser_options<W>(&self, id: Option<Id>, w: &mut W) -> io::Result<()>
where
W: Write,
{
match &self.parsed {
Ok(Parsed { options, .. }) => {
let options = options.into();
let response = ResultResponse::new(id, ResultData::Options(&options));
write_response(&response, w)
}
Err(errors) => {
let reports = create_reports_from_parser_errors(errors);
write_error_reports(None, reports, w)
}
}
}
fn parser_format_report<'r, K, W>(
&self,
id: Option<Id>,
reports: &[Report<'r>],
w: &mut W,
) -> io::Result<()>
where
K: parser::ErrorOrWarningKind,
W: Write,
{
let mut buf = Vec::new();
let mut sep = false;
for report in reports {
if sep {
buf.write_all(b"\n")?;
}
self.sources
.write_report::<_, K, Report>(&mut buf, report)?;
if let Some(annotation) = report.annotation.as_ref() {
buf.write_fmt(core::format_args!("{}\n", annotation))?;
}
sep = true;
}
let response = ResultResponse::new(id, ResultData::Report(String::from_utf8_lossy(&buf)));
write_response(&response, w)
}
fn parser_resolve_span<W>(&self, id: Option<Id>, span: &Span, w: &mut W) -> io::Result<()>
where
W: Write,
{
let span = span.into();
let spanned_source = self.sources.resolve_span(&span);
let response = ResultResponse::new(id, ResultData::ResolvedSpan(spanned_source.into()));
write_response(&response, w)
}
fn parser_create_synthetic_spans<W>(
&mut self,
id: Option<Id>,
synthetic_spans: &[SyntheticSpan],
w: &mut W,
) -> io::Result<()>
where
W: Write,
{
let mut spans = Vec::default();
for s in synthetic_spans {
spans.push(
self.sources
.create_synthetic_span(s.name.as_ref(), s.content.as_ref())
.into(),
)
}
let response = ResultResponse::new(id, ResultData::Spans(spans));
write_response(&response, w)
}
fn book<W>(
&self,
id: Option<Id>,
param_directives: Option<&Vec<Directive>>,
w: &mut W,
) -> io::Result<()>
where
W: Write,
{
match &self.parsed {
Ok(Parsed {
directives: parsed_directives,
options,
..
}) => {
let raw_parsed_directives = if param_directives.is_none() {
Some(
parsed_directives
.iter()
.map(Into::<Directive>::into)
.collect::<Vec<_>>(),
)
} else {
None
};
let (directives_to_book, convert_to_spans) =
match (param_directives, raw_parsed_directives.as_ref()) {
(Some(param_directives), _) => (param_directives, false),
(None, Some(raw_parsed_directives)) => (raw_parsed_directives, true),
_ => panic!("impossible"),
};
match booking::book(directives_to_book, options) {
Ok(booking::BookingSuccess {
directives,
warnings: _,
}) => {
let response = ResultResponse::new(
id,
ResultData::BookedDirectives(BookedDirectives { directives }),
);
write_response(&response, w)
}
Err(BookingFailure { errors, .. }) => {
if convert_to_spans {
write_error_reports(
id,
create_reports_from_booking_errors(errors, directives_to_book),
w,
)
} else {
write_indexed_error_reports(id, errors, w)
}
}
}
}
Err(errors) => {
let reports = create_reports_from_parser_errors(errors);
write_error_reports(id, reports, w)
}
}
}
}
fn write_response<W>(response: &ResultResponse, w: &mut W) -> io::Result<()>
where
W: Write,
{
match serde_json::to_string(&response) {
Ok(json) => {
tracing::debug!("-> {}", &json);
writeln!(w, "{}", &json)
}
Err(e) => {
tracing::error!("{}", &e);
if let serde_json::error::Category::Io = e.classify() {
Err(io::Error::new(
e.io_error_kind().unwrap(),
"while writing JSON",
))
} else {
write_error::<Report, W>(
response.id,
ERROR_INTERNAL,
Cow::Owned(e.to_string()),
None,
w,
)
}
}
}
}
fn write_error_reports<'a, W>(
id: Option<Id<'a>>,
reports: Vec<Report<'a>>,
w: &mut W,
) -> io::Result<()>
where
W: Write,
{
write_error(
id,
ERROR_REPORT,
Cow::Borrowed("Error reports"),
Some(reports),
w,
)
}
fn write_indexed_error_reports<'a, W>(
id: Option<Id<'a>>,
reports: Vec<IndexedReport>,
w: &mut W,
) -> io::Result<()>
where
W: Write,
{
write_error(
id,
ERROR_INDEXED_REPORT,
Cow::Borrowed("Error reports"),
Some(reports),
w,
)
}
fn write_other_error<'a, 'b, W>(
id: Option<Id<'a>>,
code: ErrorCode,
message: Cow<'b, str>,
w: &mut W,
) -> io::Result<()>
where
W: Write,
{
write_error::<Report, W>(id, code, message, None, w)
}
fn write_error<'a, 'b, T, W>(
id: Option<Id<'a>>,
code: ErrorCode,
message: Cow<'b, str>,
data: Option<T>,
w: &mut W,
) -> io::Result<()>
where
T: Serialize,
W: Write,
{
let response = ErrorResponse::new(id, code, message, data);
match serde_json::to_string(&response) {
Ok(json) => {
tracing::debug!("-> {}", &json);
writeln!(w, "{}", &json)
}
Err(e) => {
tracing::error!("failed writing error {}", &e);
panic!("can't even write error {}", &e)
}
}
}