1#[cfg(feature = "abc")]
6pub mod abc;
7mod error;
8#[cfg(feature = "mei")]
9pub mod mei;
10#[cfg(feature = "midi")]
11pub mod midi;
12#[cfg(feature = "mscz")]
13pub mod mscz;
14#[cfg(feature = "musicxml")]
15pub mod musicxml;
16mod report;
17
18pub(crate) const MAX_INPUT_BYTES: usize = 64 * 1024 * 1024;
20#[cfg(feature = "abc")]
22pub(crate) const MAX_ABC_LINE_BYTES: usize = 1024 * 1024;
23pub(crate) const MAX_MIDI_EVENTS: usize = 500_000;
25
26pub use error::Error;
27pub use report::{
28 Diagnostic, DiagnosticSeverity, ExportReport, ImportReport, REPORT_SCHEMA_VERSION,
29};
30
31#[cfg(test)]
32mod security_tests {
33 use super::Error;
34
35 #[cfg(feature = "midi")]
36 #[test]
37 fn midi_rejects_input_over_baseline_limit() {
38 let data = vec![0_u8; super::MAX_INPUT_BYTES + 1];
39 assert!(matches!(
40 super::midi::parse_midi(&data),
41 Err(Error::TooLarge(size)) if size == super::MAX_INPUT_BYTES + 1
42 ));
43 }
44
45 #[cfg(feature = "abc")]
46 #[test]
47 fn abc_rejects_pathological_line_length() {
48 let text = format!("X:1\n{}", "x".repeat(super::MAX_ABC_LINE_BYTES + 1));
49 assert!(matches!(
50 super::abc::parse_abc(&text),
51 Err(Error::Abc(message)) if message.contains("line exceeds")
52 ));
53 }
54
55 #[cfg(feature = "abc")]
56 #[test]
57 fn abc_rejects_input_over_baseline_limit() {
58 let text = "x".repeat(super::MAX_INPUT_BYTES + 1);
59 assert!(matches!(
60 super::abc::parse_abc(&text),
61 Err(Error::TooLarge(size)) if size == super::MAX_INPUT_BYTES + 1
62 ));
63 }
64}
65
66#[cfg(feature = "musicxml")]
67pub use musicxml::{parse_musicxml, parse_mxl, serialize_musicxml};
68
69#[cfg(feature = "musicxml")]
70pub fn parse_musicxml_with_report(xml: &str) -> Result<ImportReport, Error> {
71 let score = parse_musicxml(xml)?;
72 Ok(ImportReport {
73 schema_version: REPORT_SCHEMA_VERSION,
74 format: "musicxml".to_string(),
75 score,
76 diagnostics: musicxml::loss_diagnostics(xml),
77 })
78}
79
80#[cfg(feature = "musicxml")]
81pub fn parse_mxl_with_report(data: &[u8]) -> Result<ImportReport, Error> {
82 Ok(ImportReport::for_format(parse_mxl(data)?, "mxl"))
83}
84
85#[cfg(feature = "mei")]
86pub use mei::{parse_mei, serialize_mei};
87
88#[cfg(feature = "mei")]
89pub fn parse_mei_with_report(text: &str) -> Result<ImportReport, Error> {
90 mei::parse_mei_with_report(text)
91}
92
93#[cfg(feature = "mei")]
94pub fn serialize_mei_with_report(
95 score: &acorde_core::Score,
96) -> Result<ExportReport<String>, Error> {
97 Ok(ExportReport {
98 schema_version: REPORT_SCHEMA_VERSION,
99 format: "mei".to_string(),
100 output: serialize_mei(score)?,
101 diagnostics: mei::export_loss_diagnostics(score),
102 })
103}
104
105#[cfg(feature = "musicxml")]
106pub fn serialize_musicxml_with_report(
107 score: &acorde_core::Score,
108) -> Result<ExportReport<String>, Error> {
109 Ok(ExportReport {
110 schema_version: REPORT_SCHEMA_VERSION,
111 format: "musicxml".to_string(),
112 output: serialize_musicxml(score)?,
113 diagnostics: musicxml::export_loss_diagnostics(score),
114 })
115}
116
117#[cfg(feature = "midi")]
118pub use midi::{parse_midi, serialize_midi, serialize_midi_region};
119
120#[cfg(feature = "midi")]
121pub fn parse_midi_with_report(data: &[u8]) -> Result<ImportReport, Error> {
122 let score = parse_midi(data)?;
123 let diagnostics = midi::loss_diagnostics(data)?;
124 Ok(ImportReport {
125 schema_version: REPORT_SCHEMA_VERSION,
126 format: "midi".to_string(),
127 score,
128 diagnostics,
129 })
130}
131
132#[cfg(feature = "midi")]
133pub fn serialize_midi_with_report(
134 score: &acorde_core::Score,
135) -> Result<ExportReport<Vec<u8>>, Error> {
136 Ok(ExportReport {
137 schema_version: REPORT_SCHEMA_VERSION,
138 format: "midi".to_string(),
139 output: serialize_midi(score)?,
140 diagnostics: midi::export_loss_diagnostics(score),
141 })
142}
143
144#[cfg(feature = "abc")]
145pub use abc::{parse_abc, serialize_abc};
146
147#[cfg(feature = "abc")]
148pub fn parse_abc_with_report(text: &str) -> Result<ImportReport, Error> {
149 let score = parse_abc(text)?;
150 Ok(ImportReport {
151 schema_version: REPORT_SCHEMA_VERSION,
152 format: "abc".to_string(),
153 score,
154 diagnostics: abc::loss_diagnostics(text),
155 })
156}
157
158#[cfg(feature = "abc")]
159pub fn serialize_abc_with_report(
160 score: &acorde_core::Score,
161) -> Result<ExportReport<String>, Error> {
162 Ok(ExportReport {
163 schema_version: REPORT_SCHEMA_VERSION,
164 format: "abc".to_string(),
165 output: serialize_abc(score)?,
166 diagnostics: abc::export_loss_diagnostics(score),
167 })
168}
169
170#[cfg(feature = "mscz")]
171pub use mscz::{parse_mscx, parse_mscz};
172
173#[cfg(feature = "mscz")]
174pub fn parse_mscx_with_report(text: &str) -> Result<ImportReport, Error> {
175 let score = parse_mscx(text)?;
176 let mut diagnostics = mscz::loss_diagnostics(text);
177 diagnostics.extend(mscz::tab_position_diagnostics(&score));
178 Ok(ImportReport {
179 schema_version: REPORT_SCHEMA_VERSION,
180 format: "mscx".to_string(),
181 score,
182 diagnostics,
183 })
184}
185
186#[cfg(feature = "mscz")]
187pub fn parse_mscz_with_report(data: &[u8]) -> Result<ImportReport, Error> {
188 let score = parse_mscz(data)?;
189 let mut diagnostics = mscz::loss_diagnostics_mscz(data)?;
190 diagnostics.extend(mscz::tab_position_diagnostics(&score));
191 Ok(ImportReport {
192 schema_version: REPORT_SCHEMA_VERSION,
193 format: "mscz".to_string(),
194 score,
195 diagnostics,
196 })
197}