movie detail page + importer architecture fix
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ImportError {
|
||||
#[error("CSV parse error: {0}")]
|
||||
Csv(String),
|
||||
#[error("JSON parse error: {0}")]
|
||||
Json(String),
|
||||
#[error("XLSX parse error: {0}")]
|
||||
Xlsx(String),
|
||||
#[error("Empty file")]
|
||||
Empty,
|
||||
#[error("Missing header row")]
|
||||
NoHeader,
|
||||
}
|
||||
@@ -1,12 +1,28 @@
|
||||
pub mod error;
|
||||
pub mod mapper;
|
||||
pub mod parsers;
|
||||
pub mod types;
|
||||
mod mapper;
|
||||
mod parsers;
|
||||
|
||||
pub use error::ImportError;
|
||||
pub use mapper::apply_mapping;
|
||||
pub use parsers::{parse_csv, parse_json};
|
||||
pub use types::{AnnotatedRow, DomainField, FieldMapping, ImportRow, ParsedFile, RowResult, Transform};
|
||||
use domain::{
|
||||
models::{AnnotatedRow, FieldMapping, FileFormat, ImportError, ParsedFile},
|
||||
ports::DocumentParser,
|
||||
};
|
||||
|
||||
#[cfg(feature = "xlsx")]
|
||||
pub use parsers::parse_xlsx;
|
||||
pub struct ImporterDocumentParser;
|
||||
|
||||
impl DocumentParser for ImporterDocumentParser {
|
||||
fn parse(&self, bytes: &[u8], format: FileFormat) -> Result<ParsedFile, ImportError> {
|
||||
match format {
|
||||
FileFormat::Csv => parsers::parse_csv(bytes),
|
||||
FileFormat::Json => parsers::parse_json(bytes),
|
||||
FileFormat::Xlsx => {
|
||||
#[cfg(feature = "xlsx")]
|
||||
{ parsers::parse_xlsx(bytes) }
|
||||
#[cfg(not(feature = "xlsx"))]
|
||||
{ Err(ImportError::Xlsx("XLSX support not compiled in".into())) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_mapping(&self, file: &ParsedFile, mappings: &[FieldMapping]) -> Vec<AnnotatedRow> {
|
||||
mapper::apply_mapping(file, mappings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::types::{AnnotatedRow, DomainField, FieldMapping, ImportRow, ParsedFile, RowResult, Transform};
|
||||
use domain::models::{
|
||||
AnnotatedRow, DomainField, FieldMapping, ImportRow, ParsedFile, RowResult, Transform,
|
||||
};
|
||||
|
||||
pub fn apply_mapping(file: &ParsedFile, mappings: &[FieldMapping]) -> Vec<AnnotatedRow> {
|
||||
file.rows.iter().map(|row| {
|
||||
@@ -76,7 +78,7 @@ fn set_field(row: &mut ImportRow, field: &DomainField, value: String) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::{DomainField, FieldMapping, ParsedFile, RowResult, Transform};
|
||||
use domain::models::{DomainField, FieldMapping, ParsedFile, RowResult, Transform};
|
||||
|
||||
fn sample_file() -> ParsedFile {
|
||||
ParsedFile {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{ImportError, types::ParsedFile};
|
||||
use domain::models::{ImportError, ParsedFile};
|
||||
|
||||
pub fn parse_csv(bytes: &[u8]) -> Result<ParsedFile, ImportError> {
|
||||
if bytes.is_empty() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use domain::models::{ImportError, ParsedFile};
|
||||
use serde_json::Value;
|
||||
use crate::{ImportError, types::ParsedFile};
|
||||
|
||||
pub fn parse_json(bytes: &[u8]) -> Result<ParsedFile, ImportError> {
|
||||
let value: Value = serde_json::from_slice(bytes)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use calamine::{Reader, open_workbook_from_rs, Xlsx, Data};
|
||||
use std::io::Cursor;
|
||||
use crate::{ImportError, types::ParsedFile};
|
||||
use domain::models::{ImportError, ParsedFile};
|
||||
|
||||
pub fn parse_xlsx(bytes: &[u8]) -> Result<ParsedFile, ImportError> {
|
||||
let cursor = Cursor::new(bytes);
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ParsedFile {
|
||||
pub columns: Vec<String>,
|
||||
pub rows: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum DomainField {
|
||||
Title,
|
||||
ReleaseYear,
|
||||
Director,
|
||||
Rating,
|
||||
WatchedAt,
|
||||
Comment,
|
||||
ExternalMetadataId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Transform {
|
||||
RatingScale(f64),
|
||||
DateFormat(String),
|
||||
Identity,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FieldMapping {
|
||||
pub source_column: String,
|
||||
pub domain_field: DomainField,
|
||||
pub transform: Transform,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ImportRow {
|
||||
pub title: Option<String>,
|
||||
pub release_year: Option<String>,
|
||||
pub director: Option<String>,
|
||||
pub rating: Option<String>,
|
||||
pub watched_at: Option<String>,
|
||||
pub comment: Option<String>,
|
||||
pub external_metadata_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum RowResult {
|
||||
Valid(ImportRow),
|
||||
Invalid { errors: Vec<String>, raw: Vec<(String, String)> },
|
||||
}
|
||||
|
||||
/// Wraps a RowResult with a duplicate flag so this information persists when
|
||||
/// serialised as JSON into the import_sessions.row_results DB column.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnnotatedRow {
|
||||
pub result: RowResult,
|
||||
pub is_duplicate: bool,
|
||||
}
|
||||
Reference in New Issue
Block a user