use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
#[cfg_attr(
feature = "json-schema",
schemars(extend("required" = ["ledger", "errors"]))
)]
pub struct ParseResult {
pub ledger: Option<Ledger>,
pub errors: Vec<Error>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "bindings/", rename = "LedgerJson")
)]
#[cfg_attr(feature = "json-schema", schemars(rename = "LedgerJson"))]
pub struct Ledger {
pub directives: Vec<DirectiveJson>,
pub options: LedgerOptions,
}
#[derive(
Debug, Clone, Default, Serialize, Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
#[cfg_attr(
feature = "json-schema",
schemars(extend("required" = ["operating_currencies", "title"]))
)]
pub struct LedgerOptions {
pub operating_currencies: Vec<String>,
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum MetaValueJson {
String(String),
Bool(bool),
Amount {
number: String,
currency: String,
},
Null,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct TypedValueJson {
#[serde(rename = "type")]
#[cfg_attr(
feature = "ts-export",
ts(
type = "\"string\" | \"account\" | \"currency\" | \"tag\" | \"link\" | \"date\" | \"number\" | \"bool\" | \"amount\" | \"null\""
)
)]
pub value_type: String,
pub value: MetaValueJson,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
#[allow(missing_docs)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum DirectiveJson {
#[serde(rename = "transaction")]
Transaction {
date: String,
flag: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
payee: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
narration: Option<String>,
tags: Vec<String>,
links: Vec<String>,
postings: Vec<PostingJson>,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "balance")]
Balance {
date: String,
account: String,
amount: AmountValue,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
tolerance: Option<String>,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "open")]
Open {
date: String,
account: String,
currencies: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
booking: Option<String>,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "close")]
Close {
date: String,
account: String,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "commodity")]
Commodity {
date: String,
currency: String,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "pad")]
Pad {
date: String,
account: String,
source_account: String,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "event")]
Event {
date: String,
event_type: String,
value: String,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "note")]
Note {
date: String,
account: String,
comment: String,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "document")]
Document {
date: String,
account: String,
path: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
tags: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
links: Vec<String>,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "price")]
Price {
date: String,
currency: String,
amount: AmountValue,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "query")]
Query {
date: String,
name: String,
query_string: String,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
#[serde(rename = "custom")]
Custom {
date: String,
custom_type: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
values: Vec<TypedValueJson>,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
meta: HashMap<String, MetaValueJson>,
},
}
impl DirectiveJson {
#[must_use]
pub fn meta(&self) -> &HashMap<String, MetaValueJson> {
match self {
Self::Transaction { meta, .. }
| Self::Balance { meta, .. }
| Self::Open { meta, .. }
| Self::Close { meta, .. }
| Self::Commodity { meta, .. }
| Self::Pad { meta, .. }
| Self::Event { meta, .. }
| Self::Note { meta, .. }
| Self::Document { meta, .. }
| Self::Price { meta, .. }
| Self::Query { meta, .. }
| Self::Custom { meta, .. } => meta,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct PostingJson {
pub account: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub units: Option<AmountValue>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub cost: Option<PostingCostJson>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub price: Option<AmountValue>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub flag: Option<String>,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
pub meta: HashMap<String, MetaValueJson>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum CostNumberJson {
PerUnit {
value: String,
},
Total {
value: String,
},
Compound {
per_unit: String,
total: String,
},
PerUnitFromTotal {
per_unit: String,
total: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct PostingCostJson {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub number: Option<CostNumberJson>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub currency: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub label: Option<String>,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Serialize,
Deserialize,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum Severity {
Error,
Warning,
}
#[derive(
Debug, Clone, Serialize, Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "bindings/", rename = "BeancountError")
)]
#[cfg_attr(
feature = "json-schema",
schemars(
rename = "BeancountError",
extend("required" = ["message", "code", "phase", "hint", "file", "line", "column", "end_line", "end_column", "severity"])
)
)]
pub struct Error {
pub message: String,
pub code: Option<String>,
#[cfg_attr(
feature = "ts-export",
ts(type = "\"parse\" | \"validate\" | \"plugin\" | \"lint\" | (string & {}) | null")
)]
pub phase: Option<String>,
pub hint: Option<String>,
pub file: Option<String>,
#[cfg_attr(feature = "json-schema", schemars(range(min = 1)))]
pub line: Option<u32>,
#[cfg_attr(feature = "json-schema", schemars(range(min = 1)))]
pub column: Option<u32>,
#[cfg_attr(feature = "json-schema", schemars(range(min = 1)))]
pub end_line: Option<u32>,
#[cfg_attr(feature = "json-schema", schemars(range(min = 1)))]
pub end_column: Option<u32>,
pub severity: Severity,
}
impl Error {
fn base(message: impl Into<String>, severity: Severity) -> Self {
Self {
message: message.into(),
code: None,
phase: None,
hint: None,
file: None,
line: None,
column: None,
end_line: None,
end_column: None,
severity,
}
}
pub fn new(message: impl Into<String>) -> Self {
Self::base(message, Severity::Error)
}
pub fn with_line(message: impl Into<String>, line: u32) -> Self {
Self {
line: Some(line),
..Self::base(message, Severity::Error)
}
}
pub fn warning(message: impl Into<String>) -> Self {
Self::base(message, Severity::Warning)
}
#[must_use]
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
#[must_use]
pub fn with_phase(mut self, phase: impl Into<String>) -> Self {
self.phase = Some(phase.into());
self
}
#[must_use]
pub fn with_hint(mut self, hint: Option<String>) -> Self {
self.hint = hint;
self
}
#[must_use]
pub fn with_file(mut self, file: Option<String>) -> Self {
self.file = file;
self
}
#[must_use]
pub fn with_span(mut self, start: (u32, u32), end: (u32, u32)) -> Self {
self.line = Some(start.0);
self.column = Some(start.1);
self.end_line = Some(end.0);
self.end_column = Some(end.1);
self
}
}
impl From<rustledger_loader::LedgerError> for Error {
fn from(e: rustledger_loader::LedgerError) -> Self {
Self {
code: (!e.code.is_empty()).then(|| e.code.clone()),
phase: (!e.phase.is_empty()).then(|| e.phase.clone()),
hint: None,
file: e
.location
.as_ref()
.map(|loc| loc.file.display().to_string()),
line: e.location.as_ref().map(|loc| loc.line as u32),
column: e.location.as_ref().map(|loc| loc.column as u32),
end_line: None,
end_column: None,
severity: match e.severity {
rustledger_loader::ErrorSeverity::Error => Severity::Error,
rustledger_loader::ErrorSeverity::Warning => Severity::Warning,
},
message: e.message,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct ValidationResult {
pub valid: bool,
pub errors: Vec<Error>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct QueryResult {
pub columns: Vec<String>,
pub rows: Vec<Vec<CellValue>>,
pub errors: Vec<Error>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
#[allow(missing_docs)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum CellValue {
Null,
String(String),
Integer(#[cfg_attr(feature = "ts-export", ts(type = "number"))] i64),
Boolean(bool),
Amount { number: String, currency: String },
Position {
units: AmountValue,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
cost: Option<CostValue>,
},
Inventory { positions: Vec<PositionValue> },
StringSet(Vec<String>),
Set(Vec<Box<Self>>),
Object(std::collections::HashMap<String, Box<Self>>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct AmountValue {
pub number: String,
pub currency: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct PositionValue {
pub units: AmountValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct CostValue {
pub number: String,
pub currency: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub label: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
#[cfg_attr(
feature = "json-schema",
schemars(extend("required" = ["formatted", "errors"]))
)]
pub struct FormatResult {
pub formatted: Option<String>,
pub errors: Vec<Error>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct PadResult {
pub directives: Vec<DirectiveJson>,
pub padding_transactions: Vec<DirectiveJson>,
pub errors: Vec<Error>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct PluginResult {
pub directives: Vec<DirectiveJson>,
pub errors: Vec<Error>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct PluginInfo {
pub name: String,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct CompletionJson {
pub text: String,
pub category: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct CompletionResultJson {
pub completions: Vec<CompletionJson>,
pub context: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorCompletion {
pub label: String,
pub kind: CompletionKind,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub detail: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub insert_text: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum CompletionKind {
Keyword,
Account,
AccountSegment,
Currency,
Payee,
Date,
Text,
Tag,
Link,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorCompletionResult {
pub completions: Vec<EditorCompletion>,
pub context: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorHoverInfo {
pub contents: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub range: Option<EditorRange>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorRange {
pub start_line: u32,
pub start_character: u32,
pub end_line: u32,
pub end_character: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorLocation {
pub line: u32,
pub character: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorDocumentSymbol {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub detail: Option<String>,
pub kind: SymbolKind,
pub range: EditorRange,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub children: Option<Vec<Self>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub deprecated: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum SymbolKind {
Transaction,
Account,
Balance,
Commodity,
Posting,
Pad,
Event,
Note,
Document,
Price,
Query,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub enum ReferenceKind {
Account,
Currency,
Payee,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorReference {
pub range: EditorRange,
pub kind: ReferenceKind,
pub is_definition: bool,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "ts-export", ts(optional))]
pub context: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "ts-export", ts(export, export_to = "bindings/"))]
pub struct EditorReferencesResult {
pub symbol: String,
pub kind: ReferenceKind,
pub references: Vec<EditorReference>,
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod cost_number_wire_tests {
use super::*;
#[test]
fn per_unit_serializes_with_kind_tag() {
let cn = CostNumberJson::PerUnit {
value: "100".into(),
};
let json = serde_json::to_value(&cn).unwrap();
assert_eq!(
json,
serde_json::json!({"kind": "per_unit", "value": "100"})
);
}
#[test]
fn total_serializes_with_kind_tag() {
let cn = CostNumberJson::Total {
value: "1500".into(),
};
let json = serde_json::to_value(&cn).unwrap();
assert_eq!(json, serde_json::json!({"kind": "total", "value": "1500"}));
}
#[test]
fn per_unit_from_total_carries_both_values() {
let cn = CostNumberJson::PerUnitFromTotal {
per_unit: "150".into(),
total: "300".into(),
};
let json = serde_json::to_value(&cn).unwrap();
assert_eq!(
json,
serde_json::json!({
"kind": "per_unit_from_total",
"per_unit": "150",
"total": "300",
})
);
}
#[test]
fn round_trip_all_variants() {
for cn in [
CostNumberJson::PerUnit { value: "1".into() },
CostNumberJson::Total { value: "10".into() },
CostNumberJson::PerUnitFromTotal {
per_unit: "1".into(),
total: "10".into(),
},
] {
let json = serde_json::to_string(&cn).unwrap();
let back: CostNumberJson = serde_json::from_str(&json).unwrap();
assert_eq!(serde_json::to_string(&back).unwrap(), json);
}
}
#[test]
fn posting_cost_with_total_pre_booking_distinguishes_from_bare_brace() {
let with_total = PostingCostJson {
number: Some(CostNumberJson::Total {
value: "1500".into(),
}),
currency: Some("USD".into()),
date: None,
label: None,
};
let bare = PostingCostJson {
number: None,
currency: Some("USD".into()),
date: None,
label: None,
};
let with_total_json = serde_json::to_value(&with_total).unwrap();
let bare_json = serde_json::to_value(&bare).unwrap();
assert_ne!(
with_total_json, bare_json,
"pre-booking Total and bare {{}} must serialize distinctly"
);
assert!(with_total_json["number"].is_object());
assert!(bare_json.get("number").is_none());
}
}
#[cfg(feature = "json-schema")]
#[derive(schemars::JsonSchema)]
#[allow(dead_code)]
struct RustledgerBindings {
parse_result: ParseResult,
validation_result: ValidationResult,
query_result: QueryResult,
format_result: FormatResult,
pad_result: PadResult,
plugin_result: PluginResult,
plugin_info: PluginInfo,
completion_result: CompletionResultJson,
editor_completion_result: EditorCompletionResult,
editor_hover_info: EditorHoverInfo,
editor_document_symbol: EditorDocumentSymbol,
editor_references_result: EditorReferencesResult,
editor_location: EditorLocation,
}
#[cfg(all(test, feature = "json-schema", not(target_arch = "wasm32")))]
mod export_json_schema {
use std::fs;
use std::path::PathBuf;
use super::RustledgerBindings;
pub const SUCCESS_SENTINEL: &str = "EXPORT_INDEX_SCHEMA_RAN_OK";
#[test]
#[ignore = "writes bindings/index.schema.json; driven by scripts/regen-bindings.sh"]
fn export_index_schema() {
assert!(
std::env::var_os("RUSTLEDGER_REGEN_SCHEMA").is_some(),
"export_index_schema mutates bindings/index.schema.json and \
must be driven by scripts/regen-bindings.sh, not invoked \
directly. Set RUSTLEDGER_REGEN_SCHEMA=1 to opt in."
);
let schema = schemars::schema_for!(RustledgerBindings);
let mut schema_value = serde_json::to_value(&schema)
.expect("schemars schema should round-trip through serde_json");
if let Some(obj) = schema_value.as_object_mut() {
obj.remove("type");
obj.remove("title");
obj.remove("properties");
obj.remove("required");
obj.remove("additionalProperties");
obj.remove("description");
}
let pretty = serde_json::to_string_pretty(&schema_value)
.expect("stripped schema should serialize cleanly");
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("bindings");
fs::create_dir_all(&path).expect("create bindings/ directory");
path.push("index.schema.json");
fs::write(&path, format!("{pretty}\n")).expect("write index.schema.json");
println!("{SUCCESS_SENTINEL}");
eprintln!("Wrote: {}", path.display());
}
}
#[cfg(all(test, feature = "json-schema", not(target_arch = "wasm32")))]
mod schema_required_invariants {
use std::collections::BTreeSet;
use super::RustledgerBindings;
fn assert_required_equals_all_properties(def_name: &str) {
let schema = schemars::schema_for!(RustledgerBindings);
let value = serde_json::to_value(&schema).expect("schema round-trips through serde_json");
let def = value
.get("$defs")
.and_then(|d| d.get(def_name))
.unwrap_or_else(|| panic!("{def_name} missing from $defs"));
let properties: BTreeSet<&str> = def
.get("properties")
.and_then(serde_json::Value::as_object)
.unwrap_or_else(|| panic!("{def_name} has no properties object"))
.keys()
.map(String::as_str)
.collect();
let required: BTreeSet<&str> = def
.get("required")
.and_then(serde_json::Value::as_array)
.unwrap_or_else(|| {
panic!("{def_name}.required is missing -- did schemars(extend) get dropped?")
})
.iter()
.map(|v| v.as_str().expect("required entry should be a string"))
.collect();
assert_eq!(
required, properties,
"{def_name}: the schemars(extend(\"required\" = [...])) list is out of \
sync with the struct's fields. Every field on this DTO is a required \
wire field, so `required` must list all of them. Update the \
extend(\"required\") attribute on the struct in types.rs (and this \
test, if you intentionally introduced an optional field)."
);
}
#[test]
fn parse_result_requires_all_fields() {
assert_required_equals_all_properties("ParseResult");
}
#[test]
fn ledger_options_requires_all_fields() {
assert_required_equals_all_properties("LedgerOptions");
}
#[test]
fn beancount_error_requires_all_fields() {
assert_required_equals_all_properties("BeancountError");
}
#[test]
fn format_result_requires_all_fields() {
assert_required_equals_all_properties("FormatResult");
}
}