Commit 3349625

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-12-08 16:52:57
add details to csv parsing errors
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 38fb258
Changed files (2)
lib/model/export_import/csv_converter.dart
@@ -28,6 +28,7 @@ class CsvConverter {
         (column) => column.encode(record)
       ).toList()
     ).toList();
+    table.insert(0, columns.map((c) => c.csvTitle).toList());
 
     final csvCreator = ListToCsvConverter(
         fieldDelimiter: settings.fieldDelimiter,
@@ -55,7 +56,7 @@ class CsvConverter {
         if (csvLines.length < 2) return converter.convert(csvString, eol: '\n');
         return csvLines;
     }();
-    if (lines.length < 2) return RecordParsingResult.err(RecordParsingErrorType.emptyFile);
+    if (lines.length < 2) return RecordParsingResult.err(RecordParsingErrorEmptyFile());
 
     // Get and validate columns from csv title.
     final List<ExportColumn> columns = [];
@@ -64,26 +65,27 @@ class CsvConverter {
       final column = availableColumns.firstWhere(
               (c) => c.csvTitle == titleText
                   && c.restoreAbleType != null);
-      if (column == null) return RecordParsingResult.err(RecordParsingErrorType.unknownColumn);
+      if (column == null) return RecordParsingResult.err(RecordParsingErrorUnknownColumn(titleText));
       columns.add(column);
     }
     if (columns.where((e) => e.restoreAbleType == RowDataFieldType.timestamp).isEmpty) {
-      return RecordParsingResult.err(RecordParsingErrorType.timeNotRestoreable);
+      return RecordParsingResult.err(RecordParsingErrorTimeNotRestoreable());
     }
 
     // Convert data to records.
     final List<BloodPressureRecord> records = [];
+    int currentLineNumber = 1;
     for (final currentLine in lines) {
       if (currentLine.length < columns.length) {
-        return RecordParsingResult.err(RecordParsingErrorType.expectedMoreFields);
+        return RecordParsingResult.err(RecordParsingErrorExpectedMoreFields(currentLineNumber));
       }
       
       final List<(RowDataFieldType, dynamic)> recordPieces = [];
-      for (int idx = 0; idx < columns.length; idx++) {
-        assert(currentLine[idx] is String);
-        final piece = columns[idx].decode(currentLine[idx]);
-        if (piece?.$1 != columns[idx].restoreAbleType) { // validation
-          return RecordParsingResult.err(RecordParsingErrorType.unparsableField);
+      for (int fieldIndex = 0; fieldIndex < columns.length; fieldIndex++) {
+        assert(currentLine[fieldIndex] is String);
+        final piece = columns[fieldIndex].decode(currentLine[fieldIndex]);
+        if (piece?.$1 != columns[fieldIndex].restoreAbleType) { // validation
+          return RecordParsingResult.err(RecordParsingErrorUnparsableField(currentLineNumber, currentLine[fieldIndex]));
         }
         if (piece != null) recordPieces.add(piece);
       }
@@ -107,6 +109,7 @@ class CsvConverter {
       }
 
       records.add(BloodPressureRecord(timestamp, sys, dia, pul, note, needlePin: needlePin));
+      currentLineNumber++;
     }
     
     assert(records.length == lines.length, 'every line should have been parse'); // first line got removed
lib/model/export_import/record_parsing_result.dart
@@ -6,15 +6,15 @@ class RecordParsingResult {
   RecordParsingResult._create(this._result, this._error);
 
   final List<BloodPressureRecord>? _result;
-  final RecordParsingErrorType? _error;
+  final RecordParsingError? _error;
 
   /// Pass a valid record list and indicate success.
   factory RecordParsingResult.ok(List<BloodPressureRecord> result)=>
       RecordParsingResult._create(result, null);
 
   /// Indicate a parsing failure.
-  factory RecordParsingResult.err(RecordParsingErrorType type)=>
-      RecordParsingResult._create(null, type);
+  factory RecordParsingResult.err(RecordParsingError error)=>
+      RecordParsingResult._create(null, error);
 
   /// Returns if there is an error present.
   ///
@@ -24,7 +24,7 @@ class RecordParsingResult {
   /// Returns the passed list on success or the result of [errorHandler] in case a error is present.
   ///
   /// When [errorHandler] returns null a empty list is passed.
-  List<BloodPressureRecord> getOr(List<BloodPressureRecord>? Function(RecordParsingErrorType error) errorHandler) {
+  List<BloodPressureRecord> getOr(List<BloodPressureRecord>? Function(RecordParsingError error) errorHandler) {
     if (_result != null) {
       assert(_error == null);
       return _result!;
@@ -34,22 +34,38 @@ class RecordParsingResult {
   }
 }
 
-// TODO: consider converting to sealed class to allow passing error details.
 /// Indicates what type error occurred while trying to decode a csv data.
-enum RecordParsingErrorType {
-  /// There are not enough lines in the csv file to parse the record.
-  emptyFile,
+sealed class RecordParsingError {}
 
-  /// There is no column with this csv title that can be reversed.
-  unknownColumn,
+/// There are not enough lines in the csv file to parse the record.
+class RecordParsingErrorEmptyFile implements RecordParsingError {}
 
-  /// The current line has less fields than the first line.
-  expectedMoreFields,
+/// There is no column that allows restoring a timestamp.
+class RecordParsingErrorTimeNotRestoreable implements RecordParsingError {}
 
-  /// There is no column that allows restoring a timestamp.
-  timeNotRestoreable,
+/// There is no column with this csv title that can be reversed.
+class RecordParsingErrorUnknownColumn implements RecordParsingError {
+  RecordParsingErrorUnknownColumn(this.title);
+  
+  /// CSV title of the column no equivalent was found for. 
+  final String title;
+}
+
+/// The current line has less fields than the first line.
+class RecordParsingErrorExpectedMoreFields implements RecordParsingError {
+  RecordParsingErrorExpectedMoreFields(this.lineNumber);
+
+  /// Line in which this error occurred. 
+  final int lineNumber;
+}
+
+/// The corresponding column couldn't decode a specific field in the csv file.
+class RecordParsingErrorUnparsableField implements RecordParsingError {
+  RecordParsingErrorUnparsableField(this.lineNumber, this.fieldContents);
 
-  /// The corresponding column couldn't decode a specific field in the csv file.
-  unparsableField,
-  // TODO ...
+  /// Line in which this error occurred.
+  final int lineNumber;
+  
+  /// Text in the csv string that failed to parse.
+  final String fieldContents;
 }
\ No newline at end of file