@@ -11,7 +11,7 @@ package uk.gov.nationalarchives.csv.validator
1111
1212import uk .gov .nationalarchives .utf8 .validator .{Utf8Validator , ValidationHandler }
1313
14- import scala .language .postfixOps
14+ import scala .language .{ postfixOps , reflectiveCalls }
1515import scala .util .Try
1616import scalaz ._ , Scalaz ._
1717import java .io .{IOException , Reader => JReader , InputStreamReader => JInputStreamReader , FileInputStream => JFileInputStream , LineNumberReader => JLineNumberReader }
@@ -25,10 +25,26 @@ import uk.gov.nationalarchives.csv.validator.metadata.Row
2525import scala .annotation .tailrec
2626import uk .gov .nationalarchives .csv .validator .api .TextFile
2727
28- sealed abstract class FailMessage (val msg : String , val lineNr : Option [Int ], val colIdx : Option [Int ])
29- case class WarningMessage (message: String , lineNumber : Option [Int ] = None , columnIndex : Option [Int ] = None ) extends FailMessage (message, lineNumber, columnIndex)
30- case class ErrorMessage (message: String , lineNumber : Option [Int ] = None , columnIndex : Option [Int ] = None ) extends FailMessage (message, lineNumber, columnIndex)
31- case class SchemaMessage (message: String , lineNumber : Option [Int ] = None , columnIndex : Option [Int ] = None ) extends FailMessage (message, lineNumber, columnIndex)
28+
29+ // error reporting classes
30+ sealed trait ErrorType
31+ case object ValidationWarning extends ErrorType
32+ case object ValidationError extends ErrorType
33+ case object SchemaDefinitionError extends ErrorType
34+ case class FailMessage (`type` : ErrorType , message : String , lineNumber : Option [Int ] = None , columnIndex : Option [Int ] = None ) // TODO(AR) consider a better name, e.g. CsvValidationFailure
35+ object FailMessage {
36+ def isWarning : PartialFunction [FailMessage , FailMessage ] = {
37+ case fm @ FailMessage (ValidationWarning , _, _, _) => fm
38+ }
39+
40+ def isError : PartialFunction [FailMessage , FailMessage ] = {
41+ case fm @ FailMessage (ValidationError , _, _, _) => fm
42+ }
43+
44+ def isSchemaDefinitionError : PartialFunction [FailMessage , FailMessage ] = {
45+ case fm @ FailMessage (SchemaDefinitionError , _, _, _) => fm
46+ }
47+ }
3248
3349case class ProgressFor (rowsToValidate : Int , progress : ProgressCallback )
3450
@@ -88,16 +104,16 @@ trait MetaDataValidator {
88104 val maybeNoData =
89105 if (schema.globalDirectives.contains(NoHeader ())) {
90106 if (! rowIt.hasNext && ! schema.globalDirectives.contains(PermitEmpty ())) {
91- Some (ErrorMessage ( " metadata file is empty but this has not been permitted" ).failNel [Any ])
107+ Some (FailMessage ( ValidationError , " metadata file is empty but this has not been permitted" ).failureNel [Any ])
92108 } else {
93109 None
94110 }
95111 } else {
96112 if (! rowIt.hasNext) {
97- Some (ErrorMessage ( " metadata file is empty but should contain at least a header" ).failNel [Any ])
113+ Some (FailMessage ( ValidationError , " metadata file is empty but should contain at least a header" ).failureNel [Any ])
98114 } else {
99115 if (! rowIt.hasNext && ! schema.globalDirectives.contains(PermitEmpty ())) {
100- Some (ErrorMessage ( " metadata file has a header but no data and this has not been permitted" ).failNel [Any ])
116+ Some (FailMessage ( ValidationError , " metadata file has a header but no data and this has not been permitted" ).failureNel [Any ])
101117 } else {
102118 None
103119 }
@@ -143,19 +159,19 @@ trait MetaDataValidator {
143159 val maybeNoData =
144160 if (schema.globalDirectives.contains(NoHeader ())) {
145161 if (! rowIt.hasNext && ! schema.globalDirectives.contains(PermitEmpty ())) {
146- Some (ErrorMessage ( " metadata file is empty but this has not been permitted" ).failureNel[Any ])
162+ Some (FailMessage ( ValidationError , " metadata file is empty but this has not been permitted" ).failureNel[Any ])
147163 } else {
148164 None
149165 }
150166 } else {
151167 if (! rowIt.hasNext) {
152- Some (ErrorMessage ( " metadata file is empty but should contain at least a header" ).failureNel[Any ])
168+ Some (FailMessage ( ValidationError , " metadata file is empty but should contain at least a header" ).failureNel[Any ])
153169 } else {
154170 val header = rowIt.skipHeader()
155171 val headerValidation = validateHeader(header, schema)
156- headerValidation.orElse{
172+ headerValidation.orElse {
157173 if (! rowIt.hasNext && ! schema.globalDirectives.contains(PermitEmpty ())) {
158- Some (ErrorMessage ( " metadata file has a header but no data and this has not been permitted" ).failureNel[Any ])
174+ Some (FailMessage ( ValidationError , " metadata file has a header but no data and this has not been permitted" ).failureNel[Any ])
159175 } else {
160176 None
161177 }
@@ -175,9 +191,9 @@ trait MetaDataValidator {
175191 metadataValidation
176192
177193 case Left (ts) =>
178- // TODO emit all errors not just first!
179- ErrorMessage ( ts(0 ).toString).failureNel[Any ]
180- // ts.toList.map(t => ErrorMessage( t.toString).failureNel[Any]).sequence[MetaDataValidation, Any]
194+ // TODO(AR) emit all errors not just first!
195+ FailMessage ( ValidationError , ts(0 ).toString).failureNel[Any ]
196+ // ts.toList.map(t => FailMessage(ValidationError, t.toString).failureNel[Any]).sequence[MetaDataValidation, Any]
181197 }
182198 }
183199
@@ -211,7 +227,7 @@ trait MetaDataValidator {
211227 if (headerList.sameElements(schemaHeader))
212228 None
213229 else
214- Some (ErrorMessage ( s " Metadata header, cannot find the column headers - ${Util .diff(schemaHeader.toSet, headerList.toSet).mkString(" , " )} - . ${if (icnc.isEmpty) " (Case sensitive)" else " " }" ).failNel [Any ])
230+ Some (FailMessage ( ValidationError , s " Metadata header, cannot find the column headers - ${Util .diff(schemaHeader.toSet, headerList.toSet).mkString(" , " )} - . ${if (icnc.isEmpty) " (Case sensitive)" else " " }" ).failureNel [Any ])
215231 }
216232
217233 def validateRow (row : Row , schema : Schema , mayBeLast : Option [Boolean ] = None ): MetaDataValidation [Any ] = {
@@ -236,7 +252,7 @@ trait MetaDataValidator {
236252 case None => true .successNel
237253 case Some (nel) => {
238254 val ret = nel.reverse.map {
239- case (offset, message) => ErrorMessage ( s " [UTF-8 Error][@ $offset] ${message}" )
255+ case (offset, message) => FailMessage ( ValidationError , s " [UTF-8 Error][@ $offset] ${message}" )
240256 }
241257 ret.failure
242258 }
@@ -249,20 +265,20 @@ trait MetaDataValidator {
249265 }
250266
251267 if (tc.isEmpty || tc.get.numberOfColumns == row.cells.length) true .successNel[FailMessage ]
252- else ErrorMessage ( s " Expected @totalColumns of ${tc.get.numberOfColumns} and found ${row.cells.length} on line ${row.lineNumber}" , Some (row.lineNumber), Some (row.cells.length)).failureNel[Any ]
268+ else FailMessage ( ValidationError , s " Expected @totalColumns of ${tc.get.numberOfColumns} and found ${row.cells.length} on line ${row.lineNumber}" , Some (row.lineNumber), Some (row.cells.length)).failureNel[Any ]
253269 }
254270
255271 protected def rules (row : Row , schema : Schema , mayBeLast : Option [Boolean ] = None ): MetaDataValidation [List [Any ]]
256272
257273 protected def validateCell (columnIndex : Int , cells : (Int ) => Option [Cell ], row : Row , schema : Schema , mayBeLast : Option [Boolean ] = None ): MetaDataValidation [Any ] = {
258274 cells(columnIndex) match {
259275 case Some (c) => rulesForCell(columnIndex, row, schema, mayBeLast)
260- case _ => ErrorMessage ( s " Missing value at line: ${row.lineNumber}, column: ${schema.columnDefinitions(columnIndex).id}" , Some (row.lineNumber), Some (columnIndex)).failureNel[Any ]
276+ case _ => FailMessage ( ValidationError , s " Missing value at line: ${row.lineNumber}, column: ${schema.columnDefinitions(columnIndex).id}" , Some (row.lineNumber), Some (columnIndex)).failureNel[Any ]
261277 }
262278 }
263279
264- protected def toWarnings (results : Rule # RuleValidation [Any ], lineNumber : Int , columnIndex : Int ): MetaDataValidation [Any ] = results.leftMap(_.map(WarningMessage ( _, Some (lineNumber), Some (columnIndex))))
265- protected def toErrors (results : Rule # RuleValidation [Any ], lineNumber : Int , columnIndex : Int ): MetaDataValidation [Any ] = results.leftMap(_.map(ErrorMessage ( _, Some (lineNumber), Some (columnIndex))))
280+ protected def toWarnings (results : Rule # RuleValidation [Any ], lineNumber : Int , columnIndex : Int ): MetaDataValidation [Any ] = results.leftMap(_.map(FailMessage ( ValidationWarning , _, Some (lineNumber), Some (columnIndex))))
281+ protected def toErrors (results : Rule # RuleValidation [Any ], lineNumber : Int , columnIndex : Int ): MetaDataValidation [Any ] = results.leftMap(_.map(FailMessage ( ValidationError , _, Some (lineNumber), Some (columnIndex))))
266282
267283 protected def rulesForCell (columnIndex : Int , row : Row , schema : Schema , mayBeLast : Option [Boolean ] = None ): MetaDataValidation [Any ]
268284
0 commit comments