rustledger_plugin/
types.rs

1//! Plugin interface types.
2//!
3//! These types define the contract between the plugin host and plugins.
4//! They are serialized via `MessagePack` across the WASM boundary.
5
6use serde::{Deserialize, Serialize};
7
8/// Input passed to a plugin.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct PluginInput {
11    /// All directives to process.
12    pub directives: Vec<DirectiveWrapper>,
13    /// Ledger options.
14    pub options: PluginOptions,
15    /// Plugin-specific configuration string.
16    pub config: Option<String>,
17}
18
19/// Output returned from a plugin.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct PluginOutput {
22    /// Processed directives (may be modified, added, or removed).
23    pub directives: Vec<DirectiveWrapper>,
24    /// Errors generated by the plugin.
25    pub errors: Vec<PluginError>,
26}
27
28/// A wrapper around directives for serialization.
29///
30/// This wrapper exists because `Directive` contains types that need
31/// special serialization handling (like `Decimal` and `NaiveDate`).
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct DirectiveWrapper {
34    /// The type of directive (derived from data, not serialized to avoid duplicate keys).
35    #[serde(skip_serializing, default)]
36    pub directive_type: String,
37    /// The directive date (YYYY-MM-DD).
38    pub date: String,
39    /// Directive-specific data as a nested structure.
40    #[serde(flatten)]
41    pub data: DirectiveData,
42}
43
44/// Directive-specific data.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(tag = "type")]
47pub enum DirectiveData {
48    /// Transaction data.
49    #[serde(rename = "transaction")]
50    Transaction(TransactionData),
51    /// Balance assertion data.
52    #[serde(rename = "balance")]
53    Balance(BalanceData),
54    /// Open account data.
55    #[serde(rename = "open")]
56    Open(OpenData),
57    /// Close account data.
58    #[serde(rename = "close")]
59    Close(CloseData),
60    /// Commodity declaration data.
61    #[serde(rename = "commodity")]
62    Commodity(CommodityData),
63    /// Pad directive data.
64    #[serde(rename = "pad")]
65    Pad(PadData),
66    /// Event data.
67    #[serde(rename = "event")]
68    Event(EventData),
69    /// Note data.
70    #[serde(rename = "note")]
71    Note(NoteData),
72    /// Document data.
73    #[serde(rename = "document")]
74    Document(DocumentData),
75    /// Price data.
76    #[serde(rename = "price")]
77    Price(PriceData),
78    /// Query data.
79    #[serde(rename = "query")]
80    Query(QueryData),
81    /// Custom directive data.
82    #[serde(rename = "custom")]
83    Custom(CustomData),
84}
85
86/// Transaction data for serialization.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct TransactionData {
89    /// Transaction flag (* or !).
90    pub flag: String,
91    /// Optional payee.
92    pub payee: Option<String>,
93    /// Narration/description.
94    pub narration: String,
95    /// Tags without the # prefix.
96    pub tags: Vec<String>,
97    /// Links without the ^ prefix.
98    pub links: Vec<String>,
99    /// Metadata key-value pairs.
100    pub metadata: Vec<(String, MetaValueData)>,
101    /// Postings.
102    pub postings: Vec<PostingData>,
103}
104
105/// Posting data for serialization.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct PostingData {
108    /// Account name.
109    pub account: String,
110    /// Units (amount + currency).
111    pub units: Option<AmountData>,
112    /// Cost specification.
113    pub cost: Option<CostData>,
114    /// Price annotation.
115    pub price: Option<PriceAnnotationData>,
116    /// Optional posting flag.
117    pub flag: Option<String>,
118    /// Posting metadata.
119    pub metadata: Vec<(String, MetaValueData)>,
120}
121
122/// Amount data for serialization.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct AmountData {
125    /// Number as string (preserves precision).
126    pub number: String,
127    /// Currency code.
128    pub currency: String,
129}
130
131/// Cost data for serialization.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct CostData {
134    /// Per-unit cost number.
135    pub number_per: Option<String>,
136    /// Total cost number.
137    pub number_total: Option<String>,
138    /// Cost currency.
139    pub currency: Option<String>,
140    /// Acquisition date.
141    pub date: Option<String>,
142    /// Lot label.
143    pub label: Option<String>,
144    /// Merge lots flag.
145    pub merge: bool,
146}
147
148/// Price annotation data.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct PriceAnnotationData {
151    /// Whether this is a total price (@@) vs per-unit (@).
152    pub is_total: bool,
153    /// The price amount (optional for incomplete/empty prices).
154    pub amount: Option<AmountData>,
155    /// The number only (for incomplete prices).
156    pub number: Option<String>,
157    /// The currency only (for incomplete prices).
158    pub currency: Option<String>,
159}
160
161/// Metadata value for serialization.
162#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(tag = "type", content = "value")]
164pub enum MetaValueData {
165    /// String value.
166    #[serde(rename = "string")]
167    String(String),
168    /// Number value.
169    #[serde(rename = "number")]
170    Number(String),
171    /// Date value.
172    #[serde(rename = "date")]
173    Date(String),
174    /// Account reference.
175    #[serde(rename = "account")]
176    Account(String),
177    /// Currency reference.
178    #[serde(rename = "currency")]
179    Currency(String),
180    /// Tag reference.
181    #[serde(rename = "tag")]
182    Tag(String),
183    /// Link reference.
184    #[serde(rename = "link")]
185    Link(String),
186    /// Amount value.
187    #[serde(rename = "amount")]
188    Amount(AmountData),
189    /// Boolean value.
190    #[serde(rename = "bool")]
191    Bool(bool),
192}
193
194/// Balance assertion data.
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct BalanceData {
197    /// Account name.
198    pub account: String,
199    /// Expected balance.
200    pub amount: AmountData,
201    /// Tolerance.
202    pub tolerance: Option<String>,
203}
204
205/// Open account data.
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct OpenData {
208    /// Account name.
209    pub account: String,
210    /// Allowed currencies.
211    pub currencies: Vec<String>,
212    /// Booking method.
213    pub booking: Option<String>,
214}
215
216/// Close account data.
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct CloseData {
219    /// Account name.
220    pub account: String,
221}
222
223/// Commodity declaration data.
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct CommodityData {
226    /// Currency code.
227    pub currency: String,
228    /// Metadata key-value pairs.
229    #[serde(default)]
230    pub metadata: Vec<(String, MetaValueData)>,
231}
232
233/// Pad directive data.
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct PadData {
236    /// Account to pad.
237    pub account: String,
238    /// Source account for padding.
239    pub source_account: String,
240}
241
242/// Event data.
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct EventData {
245    /// Event type.
246    pub event_type: String,
247    /// Event value.
248    pub value: String,
249}
250
251/// Note data.
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct NoteData {
254    /// Account name.
255    pub account: String,
256    /// Note comment.
257    pub comment: String,
258}
259
260/// Document data.
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct DocumentData {
263    /// Account name.
264    pub account: String,
265    /// Document path.
266    pub path: String,
267}
268
269/// Price directive data.
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct PriceData {
272    /// Currency being priced.
273    pub currency: String,
274    /// Price amount.
275    pub amount: AmountData,
276}
277
278/// Query directive data.
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct QueryData {
281    /// Query name.
282    pub name: String,
283    /// Query string.
284    pub query: String,
285}
286
287/// Custom directive data.
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct CustomData {
290    /// Custom type.
291    pub custom_type: String,
292    /// Values as strings.
293    pub values: Vec<String>,
294}
295
296/// Ledger options passed to plugins.
297#[derive(Debug, Clone, Default, Serialize, Deserialize)]
298pub struct PluginOptions {
299    /// Operating currencies.
300    pub operating_currencies: Vec<String>,
301    /// Ledger title.
302    pub title: Option<String>,
303}
304
305/// Error generated by a plugin.
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct PluginError {
308    /// Error message.
309    pub message: String,
310    /// Source file (if known).
311    pub source_file: Option<String>,
312    /// Line number (if known).
313    pub line_number: Option<u32>,
314    /// Error severity.
315    pub severity: PluginErrorSeverity,
316}
317
318/// Severity of a plugin error.
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
320pub enum PluginErrorSeverity {
321    /// Warning - processing continues.
322    #[serde(rename = "warning")]
323    Warning,
324    /// Error - ledger is marked invalid.
325    #[serde(rename = "error")]
326    Error,
327}
328
329impl PluginError {
330    /// Create a new error.
331    pub fn error(message: impl Into<String>) -> Self {
332        Self {
333            message: message.into(),
334            source_file: None,
335            line_number: None,
336            severity: PluginErrorSeverity::Error,
337        }
338    }
339
340    /// Create a new warning.
341    pub fn warning(message: impl Into<String>) -> Self {
342        Self {
343            message: message.into(),
344            source_file: None,
345            line_number: None,
346            severity: PluginErrorSeverity::Warning,
347        }
348    }
349
350    /// Set the source location.
351    pub fn at(mut self, file: impl Into<String>, line: u32) -> Self {
352        self.source_file = Some(file.into());
353        self.line_number = Some(line);
354        self
355    }
356}
357
358impl PluginOutput {
359    /// Create an empty output with the original directives.
360    pub const fn passthrough(directives: Vec<DirectiveWrapper>) -> Self {
361        Self {
362            directives,
363            errors: Vec::new(),
364        }
365    }
366}