use std::collections::HashMap;
use rustledger_core::{MetaValue, Metadata};
use serde::Serialize;
#[derive(Serialize, Default)]
pub struct Meta {
pub filename: String,
pub lineno: u32,
pub hash: String,
#[serde(flatten)]
pub user: HashMap<String, serde_json::Value>,
}
impl Meta {
pub fn new(filename: &str, lineno: u32, hash: String, directive_meta: &Metadata) -> Self {
let mut user = HashMap::new();
for (key, value) in directive_meta {
user.insert(key.clone(), meta_value_to_json(value));
}
Self {
filename: filename.to_string(),
lineno,
hash,
user,
}
}
}
pub fn meta_value_to_json(value: &MetaValue) -> serde_json::Value {
match value {
MetaValue::String(s) => serde_json::Value::String(s.clone()),
MetaValue::Account(a) => serde_json::Value::String(a.clone()),
MetaValue::Currency(c) => serde_json::Value::String(c.clone()),
MetaValue::Tag(t) => serde_json::Value::String(t.clone()),
MetaValue::Link(l) => serde_json::Value::String(l.clone()),
MetaValue::Date(d) => serde_json::Value::String(d.to_string()),
MetaValue::Number(n) => serde_json::json!(n.to_string()),
MetaValue::Bool(b) => serde_json::Value::Bool(*b),
MetaValue::Amount(a) => serde_json::json!({
"number": a.number.to_string(),
"currency": a.currency.to_string()
}),
MetaValue::None => serde_json::Value::Null,
}
}
#[derive(Serialize, Clone)]
pub struct Error {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub line: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub entry_index: Option<usize>,
pub severity: String,
}
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
line: None,
column: None,
field: None,
entry_index: None,
severity: "error".to_string(),
}
}
pub const fn with_line(mut self, line: u32) -> Self {
self.line = Some(line);
self
}
#[allow(dead_code)]
pub fn with_field(mut self, field: impl Into<String>) -> Self {
self.field = Some(field.into());
self
}
pub const fn with_entry_index(mut self, index: usize) -> Self {
self.entry_index = Some(index);
self
}
#[allow(dead_code)]
pub fn warning(mut self) -> Self {
self.severity = "warning".to_string();
self
}
}
#[derive(Serialize, Clone)]
pub struct Amount {
pub number: String,
pub currency: String,
}
#[derive(Serialize)]
pub struct PostingCost {
#[serde(skip_serializing_if = "Option::is_none")]
pub number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub number_total: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
pub struct TypedValue {
#[serde(rename = "type")]
pub value_type: &'static str,
pub value: serde_json::Value,
}
impl TypedValue {
pub fn from_meta_value(mv: &MetaValue) -> Self {
match mv {
MetaValue::String(s) => Self {
value_type: "string",
value: serde_json::Value::String(s.clone()),
},
MetaValue::Account(a) => Self {
value_type: "account",
value: serde_json::Value::String(a.clone()),
},
MetaValue::Currency(c) => Self {
value_type: "currency",
value: serde_json::Value::String(c.clone()),
},
MetaValue::Tag(t) => Self {
value_type: "tag",
value: serde_json::Value::String(t.clone()),
},
MetaValue::Link(l) => Self {
value_type: "link",
value: serde_json::Value::String(l.clone()),
},
MetaValue::Date(d) => Self {
value_type: "date",
value: serde_json::Value::String(d.to_string()),
},
MetaValue::Number(n) => Self {
value_type: "number",
value: serde_json::Value::String(n.to_string()),
},
MetaValue::Bool(b) => Self {
value_type: "bool",
value: serde_json::Value::Bool(*b),
},
MetaValue::Amount(a) => Self {
value_type: "amount",
value: serde_json::json!({
"number": a.number.to_string(),
"currency": a.currency.to_string()
}),
},
MetaValue::None => Self {
value_type: "null",
value: serde_json::Value::Null,
},
}
}
}
#[derive(Serialize)]
pub struct Posting {
pub account: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub units: Option<Amount>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<PostingCost>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<Amount>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub meta: HashMap<String, serde_json::Value>,
}
#[derive(Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DirectiveJson {
Transaction {
date: String,
flag: String,
#[serde(skip_serializing_if = "Option::is_none")]
payee: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
narration: Option<String>,
tags: Vec<String>,
links: Vec<String>,
postings: Vec<Posting>,
meta: Meta,
},
Open {
date: String,
account: String,
currencies: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
booking: Option<String>,
meta: Meta,
},
Close {
date: String,
account: String,
meta: Meta,
},
Balance {
date: String,
account: String,
amount: Amount,
meta: Meta,
},
Pad {
date: String,
account: String,
source_account: String,
meta: Meta,
},
Commodity {
date: String,
currency: String,
meta: Meta,
},
Price {
date: String,
currency: String,
amount: Amount,
meta: Meta,
},
Event {
date: String,
event_type: String,
value: String,
meta: Meta,
},
Note {
date: String,
account: String,
comment: String,
meta: Meta,
},
Document {
date: String,
account: String,
path: String,
meta: Meta,
},
Query {
date: String,
name: String,
query_string: String,
meta: Meta,
},
Custom {
date: String,
custom_type: String,
values: Vec<TypedValue>,
meta: Meta,
},
}
#[derive(Serialize)]
pub struct LedgerOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub operating_currency: Vec<String>,
pub name_assets: String,
pub name_liabilities: String,
pub name_equity: String,
pub name_income: String,
pub name_expenses: String,
pub documents: Vec<String>,
pub commodities: Vec<String>,
pub booking_method: String,
pub display_precision: HashMap<String, u32>,
}
impl Default for LedgerOptions {
fn default() -> Self {
Self {
title: None,
operating_currency: Vec::new(),
name_assets: "Assets".to_string(),
name_liabilities: "Liabilities".to_string(),
name_equity: "Equity".to_string(),
name_income: "Income".to_string(),
name_expenses: "Expenses".to_string(),
documents: Vec::new(),
commodities: Vec::new(),
booking_method: "STRICT".to_string(),
display_precision: HashMap::new(),
}
}
}
#[derive(Serialize)]
pub struct Plugin {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<String>,
}
#[derive(Serialize)]
pub struct Include {
pub path: String,
pub lineno: u32,
}
#[derive(Serialize)]
pub struct LoadOutput {
pub api_version: &'static str,
pub entries: Vec<DirectiveJson>,
pub errors: Vec<Error>,
pub options: LedgerOptions,
pub plugins: Vec<Plugin>,
pub includes: Vec<Include>,
}
#[derive(Serialize)]
pub struct ValidateOutput {
pub api_version: &'static str,
pub valid: bool,
pub errors: Vec<Error>,
}
#[derive(Serialize)]
pub struct ColumnInfo {
pub name: String,
pub datatype: String,
}
#[derive(Serialize)]
pub struct QueryOutput {
pub api_version: &'static str,
pub columns: Vec<ColumnInfo>,
pub rows: Vec<Vec<serde_json::Value>>,
pub errors: Vec<Error>,
}
#[derive(Serialize)]
pub struct VersionOutput {
pub api_version: &'static str,
pub version: String,
}
#[derive(Serialize)]
pub struct BatchOutput {
pub api_version: &'static str,
pub load: LoadOutput,
pub queries: Vec<QueryOutput>,
}