1#![cfg_attr(not(test), warn(clippy::unwrap_used))]
2
3pub(crate) mod core;
4pub mod error;
5pub mod estarde;
6pub mod exporter;
7pub mod importer;
8pub mod manipulator;
9pub mod models;
10pub mod progress;
11#[cfg(test)]
12pub(crate) mod test_util;
13pub(crate) mod util;
14pub mod workflow;
15
16use std::{
17 fmt::Display,
18 path::{Path, PathBuf},
19};
20
21use documented::{Documented, DocumentedFields};
22use error::Result;
23use exporter::{
24 Exporter, conllu::ExportCoNLLU, exmaralda::ExportExmaralda, graphml::GraphMLExporter,
25 meta::ExportMeta, saltxml::ExportSaltXml, sequence::ExportSequence, table::ExportTable,
26 textgrid::ExportTextGrid, xlsx::ExportXlsx,
27};
28use graphannis::AnnotationGraph;
29use importer::{
30 Importer, conllu::ImportCoNLLU, exmaralda::ImportEXMARaLDA, file_nodes::CreateFileNodes,
31 graphml::GraphMLImporter, meta::AnnotateCorpus, none::CreateEmptyCorpus, opus::ImportOpusLinks,
32 ptb::ImportPTB, relannis::ImportRelAnnis, saltxml::ImportSaltXml, table::ImportTable,
33 textgrid::ImportTextgrid, toolbox::ImportToolBox, treetagger::ImportTreeTagger,
34 webanno::ImportWebAnnoTSV, whisper::ImportWhisper, xlsx::ImportSpreadsheet, xml::ImportXML,
35};
36use manipulator::{
37 Manipulator, align::AlignNodes, check::Check, chunker::Chunk, collapse::Collapse,
38 enumerate::EnumerateMatches, filter::FilterNodes, link::LinkNodes, map::MapAnnos, no_op::NoOp,
39 re::Revise, sleep::Sleep, split::SplitValues, time::Filltime, visualize::Visualize,
40};
41use serde::Serialize;
42use serde_derive::Deserialize;
43use struct_field_names_as_array::FieldNamesAsSlice;
44use strum::{AsRefStr, EnumDiscriminants, EnumIter};
45use tabled::Tabled;
46use workflow::StatusSender;
47
48use crate::importer::git::ImportGitMetadata;
49
50#[derive(Tabled)]
51pub struct ModuleConfiguration {
52 pub name: String,
53 pub description: String,
54}
55
56#[derive(Deserialize, EnumDiscriminants, AsRefStr, Serialize)]
57#[strum(serialize_all = "lowercase")]
58#[strum_discriminants(derive(EnumIter, AsRefStr), strum(serialize_all = "lowercase"))]
59#[serde(tag = "format", rename_all = "lowercase", content = "config")]
60pub enum WriteAs {
61 CoNLLU(#[serde(default)] Box<ExportCoNLLU>),
62 EXMARaLDA(#[serde(default)] ExportExmaralda),
63 GraphML(#[serde(default)] GraphMLExporter), Meta(#[serde(default)] ExportMeta),
65 SaltXml(#[serde(default)] ExportSaltXml),
66 Sequence(#[serde(default)] ExportSequence),
67 Table(#[serde(default)] ExportTable),
68 TextGrid(ExportTextGrid), Xlsx(#[serde(default)] ExportXlsx),
70}
71
72impl Default for WriteAs {
73 fn default() -> Self {
75 WriteAs::GraphML(GraphMLExporter::default())
76 }
77}
78
79impl WriteAs {
80 fn writer(&self) -> &dyn Exporter {
81 match self {
82 WriteAs::EXMARaLDA(m) => m,
83 WriteAs::GraphML(m) => m,
84 WriteAs::SaltXml(m) => m,
85 WriteAs::Sequence(m) => m,
86 WriteAs::Table(m) => m,
87 WriteAs::TextGrid(m) => m,
88 WriteAs::Xlsx(m) => m,
89 WriteAs::CoNLLU(m) => &**m,
90 WriteAs::Meta(m) => m,
91 }
92 }
93}
94
95impl WriteAsDiscriminants {
96 pub fn module_doc(&self) -> &str {
97 match self {
98 WriteAsDiscriminants::EXMARaLDA => ExportExmaralda::DOCS,
99 WriteAsDiscriminants::GraphML => GraphMLExporter::DOCS,
100 WriteAsDiscriminants::SaltXml => ExportSaltXml::DOCS,
101 WriteAsDiscriminants::Sequence => ExportSequence::DOCS,
102 WriteAsDiscriminants::Table => ExportTable::DOCS,
103 WriteAsDiscriminants::TextGrid => ExportTextGrid::DOCS,
104 WriteAsDiscriminants::Xlsx => ExportXlsx::DOCS,
105 WriteAsDiscriminants::CoNLLU => ExportCoNLLU::DOCS,
106 WriteAsDiscriminants::Meta => ExportMeta::DOCS,
107 }
108 }
109
110 pub fn module_configs(&self) -> Vec<ModuleConfiguration> {
111 let mut result = Vec::new();
112 let (field_names, field_docs) = match self {
113 WriteAsDiscriminants::EXMARaLDA => (
114 ExportExmaralda::FIELD_NAMES_AS_SLICE,
115 ExportExmaralda::FIELD_DOCS,
116 ),
117 WriteAsDiscriminants::GraphML => (
118 GraphMLExporter::FIELD_NAMES_AS_SLICE,
119 GraphMLExporter::FIELD_DOCS,
120 ),
121 WriteAsDiscriminants::SaltXml => (
122 ExportSaltXml::FIELD_NAMES_AS_SLICE,
123 ExportSaltXml::FIELD_DOCS,
124 ),
125 WriteAsDiscriminants::Sequence => (
126 ExportSequence::FIELD_NAMES_AS_SLICE,
127 ExportSequence::FIELD_DOCS,
128 ),
129 WriteAsDiscriminants::Table => {
130 (ExportTable::FIELD_NAMES_AS_SLICE, ExportTable::FIELD_DOCS)
131 }
132 WriteAsDiscriminants::TextGrid => (
133 ExportTextGrid::FIELD_NAMES_AS_SLICE,
134 ExportTextGrid::FIELD_DOCS,
135 ),
136 WriteAsDiscriminants::Xlsx => {
137 (ExportXlsx::FIELD_NAMES_AS_SLICE, ExportXlsx::FIELD_DOCS)
138 }
139 WriteAsDiscriminants::CoNLLU => {
140 (ExportCoNLLU::FIELD_NAMES_AS_SLICE, ExportCoNLLU::FIELD_DOCS)
141 }
142 WriteAsDiscriminants::Meta => {
143 (ExportMeta::FIELD_NAMES_AS_SLICE, ExportMeta::FIELD_DOCS)
144 }
145 };
146 for (idx, n) in field_names.iter().enumerate() {
147 if idx < field_docs.len() {
148 result.push(ModuleConfiguration {
149 name: n.to_string(),
150 description: field_docs[idx].to_string(),
151 });
152 } else {
153 result.push(ModuleConfiguration {
154 name: n.to_string(),
155 description: String::default(),
156 });
157 }
158 }
159 result
160 }
161}
162
163#[derive(Deserialize, EnumDiscriminants, AsRefStr, Serialize)]
164#[strum(serialize_all = "lowercase")]
165#[strum_discriminants(derive(EnumIter, AsRefStr), strum(serialize_all = "lowercase"))]
166#[serde(tag = "format", rename_all = "lowercase", content = "config")]
167pub enum ReadFrom {
168 CoNLLU(#[serde(default)] ImportCoNLLU),
169 EXMARaLDA(#[serde(default)] ImportEXMARaLDA),
170 Git(ImportGitMetadata),
171 GraphML(#[serde(default)] GraphMLImporter),
172 Meta(#[serde(default)] AnnotateCorpus),
173 None(#[serde(default)] CreateEmptyCorpus),
174 Opus(#[serde(default)] ImportOpusLinks),
175 Path(#[serde(default)] CreateFileNodes),
176 PTB(#[serde(default)] ImportPTB),
177 RelAnnis(#[serde(default)] ImportRelAnnis),
178 SaltXml(#[serde(default)] ImportSaltXml),
179 Table(#[serde(default)] ImportTable),
180 TextGrid(#[serde(default)] ImportTextgrid),
181 Toolbox(#[serde(default)] ImportToolBox),
182 TreeTagger(#[serde(default)] ImportTreeTagger),
183 Webanno(#[serde(default)] ImportWebAnnoTSV),
184 Whisper(#[serde(default)] ImportWhisper),
185 Xlsx(#[serde(default)] ImportSpreadsheet),
186 Xml(ImportXML),
187}
188
189impl Default for ReadFrom {
190 fn default() -> Self {
192 ReadFrom::None(CreateEmptyCorpus::default())
193 }
194}
195
196impl ReadFrom {
197 fn reader(&self) -> &dyn Importer {
198 match self {
199 ReadFrom::CoNLLU(m) => m,
200 ReadFrom::EXMARaLDA(m) => m,
201 ReadFrom::GraphML(m) => m,
202 ReadFrom::Meta(m) => m,
203 ReadFrom::None(m) => m,
204 ReadFrom::Opus(m) => m,
205 ReadFrom::Path(m) => m,
206 ReadFrom::PTB(m) => m,
207 ReadFrom::RelAnnis(m) => m,
208 ReadFrom::SaltXml(m) => m,
209 ReadFrom::Table(m) => m,
210 ReadFrom::TextGrid(m) => m,
211 ReadFrom::Toolbox(m) => m,
212 ReadFrom::TreeTagger(m) => m,
213 ReadFrom::Whisper(m) => m,
214 ReadFrom::Xlsx(m) => m,
215 ReadFrom::Xml(m) => m,
216 ReadFrom::Webanno(m) => m,
217 ReadFrom::Git(m) => m,
218 }
219 }
220}
221
222impl ReadFromDiscriminants {
223 pub fn module_doc(&self) -> &str {
224 match self {
225 ReadFromDiscriminants::CoNLLU => ImportCoNLLU::DOCS,
226 ReadFromDiscriminants::EXMARaLDA => ImportEXMARaLDA::DOCS,
227 ReadFromDiscriminants::GraphML => GraphMLImporter::DOCS,
228 ReadFromDiscriminants::Meta => AnnotateCorpus::DOCS,
229 ReadFromDiscriminants::None => CreateEmptyCorpus::DOCS,
230 ReadFromDiscriminants::Opus => ImportOpusLinks::DOCS,
231 ReadFromDiscriminants::Path => CreateFileNodes::DOCS,
232 ReadFromDiscriminants::PTB => ImportPTB::DOCS,
233 ReadFromDiscriminants::RelAnnis => ImportRelAnnis::DOCS,
234 ReadFromDiscriminants::SaltXml => ImportSaltXml::DOCS,
235 ReadFromDiscriminants::Table => ImportTable::DOCS,
236 ReadFromDiscriminants::TextGrid => ImportTextgrid::DOCS,
237 ReadFromDiscriminants::Toolbox => ImportToolBox::DOCS,
238 ReadFromDiscriminants::TreeTagger => ImportTreeTagger::DOCS,
239 ReadFromDiscriminants::Whisper => ImportWhisper::DOCS,
240 ReadFromDiscriminants::Xlsx => ImportSpreadsheet::DOCS,
241 ReadFromDiscriminants::Xml => ImportXML::DOCS,
242 ReadFromDiscriminants::Webanno => ImportWebAnnoTSV::DOCS,
243 ReadFromDiscriminants::Git => ImportGitMetadata::DOCS,
244 }
245 }
246
247 pub fn module_configs(&self) -> Vec<ModuleConfiguration> {
248 let mut result = Vec::new();
249 let (field_names, field_docs) = match self {
250 ReadFromDiscriminants::CoNLLU => {
251 (ImportCoNLLU::FIELD_NAMES_AS_SLICE, ImportCoNLLU::FIELD_DOCS)
252 }
253 ReadFromDiscriminants::EXMARaLDA => (
254 ImportEXMARaLDA::FIELD_NAMES_AS_SLICE,
255 ImportEXMARaLDA::FIELD_DOCS,
256 ),
257 ReadFromDiscriminants::GraphML => (
258 GraphMLImporter::FIELD_NAMES_AS_SLICE,
259 GraphMLImporter::FIELD_DOCS,
260 ),
261 ReadFromDiscriminants::Meta => (
262 AnnotateCorpus::FIELD_NAMES_AS_SLICE,
263 AnnotateCorpus::FIELD_DOCS,
264 ),
265 ReadFromDiscriminants::None => (
266 CreateEmptyCorpus::FIELD_NAMES_AS_SLICE,
267 CreateEmptyCorpus::FIELD_DOCS,
268 ),
269 ReadFromDiscriminants::Opus => (
270 ImportOpusLinks::FIELD_NAMES_AS_SLICE,
271 ImportOpusLinks::FIELD_DOCS,
272 ),
273 ReadFromDiscriminants::Path => (
274 CreateFileNodes::FIELD_NAMES_AS_SLICE,
275 CreateFileNodes::FIELD_DOCS,
276 ),
277 ReadFromDiscriminants::PTB => (ImportPTB::FIELD_NAMES_AS_SLICE, ImportPTB::FIELD_DOCS),
278 ReadFromDiscriminants::TextGrid => (
279 ImportTextgrid::FIELD_NAMES_AS_SLICE,
280 ImportTextgrid::FIELD_DOCS,
281 ),
282 ReadFromDiscriminants::Table => {
283 (ImportTable::FIELD_NAMES_AS_SLICE, ImportTable::FIELD_DOCS)
284 }
285 ReadFromDiscriminants::TreeTagger => (
286 ImportTreeTagger::FIELD_NAMES_AS_SLICE,
287 ImportTreeTagger::FIELD_DOCS,
288 ),
289 ReadFromDiscriminants::Xlsx => (
290 ImportSpreadsheet::FIELD_NAMES_AS_SLICE,
291 ImportSpreadsheet::FIELD_DOCS,
292 ),
293 ReadFromDiscriminants::Xml => (ImportXML::FIELD_NAMES_AS_SLICE, ImportXML::FIELD_DOCS),
294 ReadFromDiscriminants::Toolbox => (
295 ImportToolBox::FIELD_NAMES_AS_SLICE,
296 ImportToolBox::FIELD_DOCS,
297 ),
298 ReadFromDiscriminants::RelAnnis => (
299 ImportRelAnnis::FIELD_NAMES_AS_SLICE,
300 ImportRelAnnis::FIELD_DOCS,
301 ),
302 ReadFromDiscriminants::SaltXml => (
303 ImportSaltXml::FIELD_NAMES_AS_SLICE,
304 ImportSaltXml::FIELD_DOCS,
305 ),
306 ReadFromDiscriminants::Whisper => (
307 ImportWhisper::FIELD_NAMES_AS_SLICE,
308 ImportWhisper::FIELD_DOCS,
309 ),
310 ReadFromDiscriminants::Webanno => (
311 ImportWebAnnoTSV::FIELD_NAMES_AS_SLICE,
312 ImportWebAnnoTSV::FIELD_DOCS,
313 ),
314 ReadFromDiscriminants::Git => (
315 ImportGitMetadata::FIELD_NAMES_AS_SLICE,
316 ImportGitMetadata::FIELD_DOCS,
317 ),
318 };
319 for (idx, n) in field_names.iter().enumerate() {
320 if idx < field_docs.len() {
321 result.push(ModuleConfiguration {
322 name: n.to_string(),
323 description: field_docs[idx].to_string(),
324 });
325 } else {
326 result.push(ModuleConfiguration {
327 name: n.to_string(),
328 description: String::default(),
329 });
330 }
331 }
332 result
333 }
334}
335
336#[derive(Deserialize, EnumDiscriminants, AsRefStr, Serialize)]
337#[strum(serialize_all = "lowercase")]
338#[strum_discriminants(derive(EnumIter, AsRefStr), strum(serialize_all = "lowercase"))]
339#[serde(tag = "action", rename_all = "lowercase", content = "config")]
340pub enum GraphOp {
341 Align(AlignNodes), Check(Check), Collapse(Collapse), Filter(FilterNodes),
345 Visualize(#[serde(default)] Visualize),
346 Enumerate(#[serde(default)] EnumerateMatches),
347 Link(LinkNodes), Map(MapAnnos), Revise(#[serde(default)] Revise), Time(#[serde(default)] Filltime),
351 Chunk(#[serde(default)] Chunk),
352 Split(#[serde(default)] SplitValues), Sleep(#[serde(default)] Sleep),
354 None(#[serde(default)] NoOp), }
356
357impl Default for GraphOp {
358 fn default() -> Self {
360 GraphOp::None(NoOp::default())
361 }
362}
363
364impl GraphOp {
365 fn processor(&self) -> &dyn Manipulator {
366 match self {
367 GraphOp::Check(m) => m,
368 GraphOp::Collapse(m) => m,
369 GraphOp::Visualize(m) => m,
370 GraphOp::Link(m) => m,
371 GraphOp::Map(m) => m,
372 GraphOp::Revise(m) => m,
373 GraphOp::None(m) => m,
374 GraphOp::Enumerate(m) => m,
375 GraphOp::Chunk(m) => m,
376 GraphOp::Split(m) => m,
377 GraphOp::Filter(m) => m,
378 GraphOp::Time(m) => m,
379 GraphOp::Sleep(m) => m,
380 GraphOp::Align(m) => m,
381 }
382 }
383}
384
385impl GraphOpDiscriminants {
386 pub fn module_doc(&self) -> &str {
387 match self {
388 GraphOpDiscriminants::Check => Check::DOCS,
389 GraphOpDiscriminants::Collapse => Collapse::DOCS,
390 GraphOpDiscriminants::Visualize => Visualize::DOCS,
391 GraphOpDiscriminants::Enumerate => EnumerateMatches::DOCS,
392 GraphOpDiscriminants::Link => LinkNodes::DOCS,
393 GraphOpDiscriminants::Map => MapAnnos::DOCS,
394 GraphOpDiscriminants::Revise => Revise::DOCS,
395 GraphOpDiscriminants::Chunk => Chunk::DOCS,
396 GraphOpDiscriminants::None => NoOp::DOCS,
397 GraphOpDiscriminants::Split => SplitValues::DOCS,
398 GraphOpDiscriminants::Filter => FilterNodes::DOCS,
399 GraphOpDiscriminants::Time => Filltime::DOCS,
400 GraphOpDiscriminants::Sleep => Sleep::DOCS,
401 GraphOpDiscriminants::Align => AlignNodes::DOCS,
402 }
403 }
404
405 pub fn module_configs(&self) -> Vec<ModuleConfiguration> {
406 let mut result = Vec::new();
407 let (field_names, field_docs) = match self {
408 GraphOpDiscriminants::Check => (Check::FIELD_NAMES_AS_SLICE, Check::FIELD_DOCS),
409 GraphOpDiscriminants::Collapse => {
410 (Collapse::FIELD_NAMES_AS_SLICE, Collapse::FIELD_DOCS)
411 }
412 GraphOpDiscriminants::Visualize => {
413 (Visualize::FIELD_NAMES_AS_SLICE, Visualize::FIELD_DOCS)
414 }
415 GraphOpDiscriminants::Enumerate => (
416 EnumerateMatches::FIELD_NAMES_AS_SLICE,
417 EnumerateMatches::FIELD_DOCS,
418 ),
419 GraphOpDiscriminants::Link => (LinkNodes::FIELD_NAMES_AS_SLICE, LinkNodes::FIELD_DOCS),
420 GraphOpDiscriminants::Map => (MapAnnos::FIELD_NAMES_AS_SLICE, MapAnnos::FIELD_DOCS),
421 GraphOpDiscriminants::Revise => (Revise::FIELD_NAMES_AS_SLICE, Revise::FIELD_DOCS),
422 GraphOpDiscriminants::Chunk => (Chunk::FIELD_NAMES_AS_SLICE, Chunk::FIELD_DOCS),
423 GraphOpDiscriminants::None => (NoOp::FIELD_NAMES_AS_SLICE, NoOp::FIELD_DOCS),
424 GraphOpDiscriminants::Split => {
425 (SplitValues::FIELD_NAMES_AS_SLICE, SplitValues::FIELD_DOCS)
426 }
427 GraphOpDiscriminants::Filter => {
428 (FilterNodes::FIELD_NAMES_AS_SLICE, FilterNodes::FIELD_DOCS)
429 }
430 GraphOpDiscriminants::Time => (Filltime::FIELD_NAMES_AS_SLICE, Filltime::FIELD_DOCS),
431 GraphOpDiscriminants::Sleep => (Sleep::FIELD_NAMES_AS_SLICE, Sleep::FIELD_DOCS),
432 GraphOpDiscriminants::Align => {
433 (AlignNodes::FIELD_NAMES_AS_SLICE, AlignNodes::FIELD_DOCS)
434 }
435 };
436 for (idx, n) in field_names.iter().enumerate() {
437 if idx < field_docs.len() {
438 result.push(ModuleConfiguration {
439 name: n.to_string(),
440 description: field_docs[idx].to_string(),
441 });
442 } else {
443 result.push(ModuleConfiguration {
444 name: n.to_string(),
445 description: String::default(),
446 });
447 }
448 }
449 result
450 }
451}
452
453#[derive(Eq, PartialEq, Hash, Debug, Clone)]
455pub struct StepID {
456 pub module_name: String,
458 pub path: Option<PathBuf>,
460}
461
462impl StepID {
463 pub fn from_importer_step(step: &ImporterStep) -> StepID {
464 StepID {
465 module_name: format!("import_{}", step.module.as_ref().to_lowercase()),
466 path: Some(step.path.clone()),
467 }
468 }
469
470 pub fn from_graphop_step(step: &ManipulatorStep, position_in_workflow: usize) -> StepID {
471 StepID {
472 module_name: format!(
473 "{position_in_workflow}_{}",
474 step.module.as_ref().to_lowercase()
475 ),
476 path: None,
477 }
478 }
479
480 pub fn from_exporter_step(step: &ExporterStep) -> StepID {
481 StepID {
482 module_name: format!("export_{}", step.module.as_ref().to_lowercase()),
483 path: Some(step.path.clone()),
484 }
485 }
486}
487
488impl Display for StepID {
489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490 if let Some(path) = &self.path {
491 write!(f, "{} ({})", self.module_name, path.to_string_lossy())
492 } else {
493 write!(f, "{}", self.module_name)
494 }
495 }
496}
497
498pub trait Step {}
500
501#[derive(Deserialize, Serialize)]
502#[serde(deny_unknown_fields)]
503pub struct ImporterStep {
504 #[serde(flatten)]
505 module: ReadFrom,
506 path: PathBuf,
507}
508
509impl ImporterStep {
510 #[cfg(test)]
511 fn execute(
512 &self,
513 tx: Option<StatusSender>,
514 ) -> std::result::Result<graphannis::update::GraphUpdate, Box<dyn std::error::Error>> {
515 self.module
516 .reader()
517 .import_corpus(&self.path, StepID::from_importer_step(&self), tx)
518 }
519}
520
521impl Step for ImporterStep {}
522
523#[derive(Deserialize, Serialize)]
524#[serde(deny_unknown_fields)]
525pub struct ExporterStep {
526 #[serde(flatten)]
527 module: WriteAs,
528 path: PathBuf,
529}
530
531impl ExporterStep {
532 #[cfg(test)]
533 fn execute(
534 &self,
535 graph: &AnnotationGraph,
536 tx: Option<StatusSender>,
537 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
538 self.module
539 .writer()
540 .export_corpus(graph, &self.path, StepID::from_exporter_step(&self), tx)
541 }
542}
543
544impl Step for ExporterStep {}
545
546#[derive(Deserialize, Serialize)]
547#[serde(deny_unknown_fields)]
548pub struct ManipulatorStep {
549 #[serde(flatten)]
550 module: GraphOp,
551 workflow_directory: Option<PathBuf>,
552}
553
554impl ManipulatorStep {
555 fn execute(
556 &self,
557 graph: &mut AnnotationGraph,
558 workflow_directory: &Path,
559 position_in_workflow: usize,
560 tx: Option<StatusSender>,
561 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
562 let step_id = StepID::from_graphop_step(self, position_in_workflow);
563 self.module
564 .processor()
565 .validate_graph(graph, step_id.clone(), tx.clone())?;
566 self.module
567 .processor()
568 .manipulate_corpus(graph, workflow_directory, step_id, tx)
569 }
570}
571
572impl Step for ManipulatorStep {}
573
574#[cfg(test)]
575mod tests {
576 use std::fs;
577
578 use serde::de::DeserializeOwned;
579
580 use crate::{GraphOp, ReadFrom, WriteAs};
581
582 #[test]
583 fn deser_read_from_pass() {
584 assert!(deserialize_toml::<ReadFrom>("tests/deser/deser_read_from.toml").is_ok());
585 }
586
587 #[test]
588 fn deser_read_from_fail_unknown() {
589 assert!(deserialize_toml::<ReadFrom>("tests/deser/deser_read_from_fail.toml").is_err());
590 }
591
592 #[test]
593 fn deser_graph_op_pass() {
594 assert!(deserialize_toml::<GraphOp>("tests/deser/deser_graph_op.toml").is_ok());
595 }
596
597 #[test]
598 fn deser_graph_op_fail_unknown() {
599 assert!(deserialize_toml::<GraphOp>("tests/deser/deser_graph_op_fail.toml").is_err());
600 }
601
602 #[test]
603 fn deser_write_as_pass() {
604 assert!(deserialize_toml::<WriteAs>("tests/deser/deser_write_as.toml").is_ok());
605 }
606
607 #[test]
608 fn deser_write_as_fail_unknown() {
609 assert!(deserialize_toml::<WriteAs>("tests/deser/deser_write_as_fail.toml").is_err());
610 }
611
612 fn deserialize_toml<E: DeserializeOwned>(path: &str) -> Result<E, toml::de::Error> {
613 let toml_string = fs::read_to_string(path);
614 assert!(toml_string.is_ok());
615 toml::from_str(&toml_string.unwrap())
616 }
617}