Repository: moov-io/ach
Pull request #113 (2017-07-26 22:49:44)

Errors refactor for more detail and dynamic messages
Complete rewrite/refactor of how errors are handled in the library. Added support for FieldError, BatchError, and FileError. This should make it much easier to add additional SEC types and debug issues when they arise. 

coveralls: 
[![Coverage Status](https://coveralls.io/builds/12568082/badge)](https://coveralls.io/builds/12568082)

Coverage increased (+2.7%) to 95.251% when pulling **72446c913ede58a93b8c148a2ad024b9db98e3b1 on wadearnold:Errors-refactor-for-more-detail-and-wrapping** into **40f64ee1146076200d474ee7d42944af9b6c4448 on moov-io:master**.


Patch:
diff --git a/README.md b/README.md
index db279a00..348c37cc 100644
--- a/README.md
+++ b/README.md
@@ -18,20 +18,15 @@ ACH is at an early stage and under active development. Please star the project i
 
 
 ## Project Roadmap
-* Support Return Entries
-* Support WEB, CCD, IAT Entries
+* Support SEC WEB
+* Additional SEC codes will be added based on library users needs. Please open an issue with a valid test file.
 
 ## Usage and examples
-
-With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
-
-```sh
-go get -u github.com/moov-io/ach
-```
-
 See [godoc.org/github.com/moov-io/ach](https://godoc.org/github.com/moov-io/ach) for usage.
 
-Examples exist in projects [example](https://github.com/moov-io/ach/tree/master/example) folder 
+ACH.Read() returns an error of type ParseError. A ParseError is an error with Line and Record name values. A ParseError.err holds the underlying error of type FieldError, FileError, or BatchError with specific information about record validation
+
+Examples exist in projects [example](https://github.com/moov-io/ach/tree/master/example) folder
 
 # Contributing
 
diff --git a/addenda.go b/addenda.go
index 11b5fb5c..e0942061 100644
--- a/addenda.go
+++ b/addenda.go
@@ -32,8 +32,9 @@ type Addenda struct {
 // NewAddenda returns a new Addenda with default values for none exported fields
 func NewAddenda() Addenda {
 	return Addenda{
-		recordType: "7",
-		TypeCode:   "05",
+		recordType:     "7",
+		TypeCode:       "05",
+		SequenceNumber: 1,
 	}
 }
 
@@ -70,13 +71,14 @@ func (addenda *Addenda) Validate() error {
 		return err
 	}
 	if addenda.recordType != "7" {
-		return &ValidateError{FieldName: "recordType", Value: addenda.recordType, Err: ErrRecordType}
+		msg := fmt.Sprintf(msgRecordType, 7)
+		return &FieldError{FieldName: "recordType", Value: addenda.recordType, Msg: msg}
 	}
 	if err := addenda.isTypeCode(addenda.TypeCode); err != nil {
-		return &ValidateError{FieldName: "TypeCode", Value: addenda.TypeCode, Err: err}
+		return &FieldError{FieldName: "TypeCode", Value: addenda.TypeCode, Msg: err.Error()}
 	}
 	if err := addenda.isAlphanumeric(addenda.PaymentRelatedInformation); err != nil {
-		return &ValidateError{FieldName: "PaymentRelatedInformation", Value: addenda.PaymentRelatedInformation, Err: err}
+		return &FieldError{FieldName: "PaymentRelatedInformation", Value: addenda.PaymentRelatedInformation, Msg: err.Error()}
 	}
 
 	return nil
@@ -86,16 +88,16 @@ func (addenda *Addenda) Validate() error {
 // invalid the ACH transfer will be returned.
 func (addenda *Addenda) fieldInclusion() error {
 	if addenda.recordType == "" {
-		return &ValidateError{FieldName: "recordType", Value: addenda.recordType, Err: ErrRecordType}
+		return &FieldError{FieldName: "recordType", Value: addenda.recordType, Msg: msgFieldInclusion}
 	}
 	if addenda.TypeCode == "" {
-		return &ValidateError{FieldName: "recordType", Value: addenda.recordType, Err: ErrAddendaTypeCode}
+		return &FieldError{FieldName: "TypeCode", Value: addenda.TypeCode, Msg: msgFieldInclusion}
 	}
 	if addenda.SequenceNumber == 0 {
-		return &ValidateError{FieldName: "SequenceNumber", Value: string(addenda.SequenceNumber), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "SequenceNumber", Value: addenda.SequenceNumberField(), Msg: msgFieldInclusion}
 	}
 	if addenda.EntryDetailSequenceNumber == 0 {
-		return &ValidateError{FieldName: "EntryDetailSequenceNumber", Value: string(addenda.EntryDetailSequenceNumber), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "EntryDetailSequenceNumber", Value: addenda.EntryDetailSequenceNumberField(), Msg: msgFieldInclusion}
 	}
 	return nil
 }
diff --git a/batchControl.go b/batchControl.go
index 564ab38e..68d79ecd 100644
--- a/batchControl.go
+++ b/batchControl.go
@@ -6,6 +6,7 @@ package ach
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 )
 
@@ -97,7 +98,9 @@ func (bc *BatchControl) Parse(record string) {
 // NewBatchControl returns a new BatchControl with default values for none exported fields
 func NewBatchControl() *BatchControl {
 	return &BatchControl{
-		recordType: "8",
+		recordType:  "8",
+		EntryHash:   1,
+		BatchNumber: 1,
 	}
 }
 
@@ -125,18 +128,19 @@ func (bc *BatchControl) Validate() error {
 		return err
 	}
 	if bc.recordType != "8" {
-		return &ValidateError{FieldName: "recordType", Value: bc.recordType, Err: ErrRecordType}
+		msg := fmt.Sprintf(msgRecordType, 7)
+		return &FieldError{FieldName: "recordType", Value: bc.recordType, Msg: msg}
 	}
 	if err := bc.isServiceClass(bc.ServiceClassCode); err != nil {
-		return &ValidateError{FieldName: "ServiceClassCode", Value: string(bc.ServiceClassCode), Err: ErrServiceClass}
+		return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bc.ServiceClassCode), Msg: err.Error()}
 	}
 
 	if err := bc.isAlphanumeric(bc.CompanyIdentification); err != nil {
-		return &ValidateError{FieldName: "CompanyIdentification", Value: bc.CompanyIdentification, Err: err}
+		return &FieldError{FieldName: "CompanyIdentification", Value: bc.CompanyIdentification, Msg: err.Error()}
 	}
 
 	if err := bc.isAlphanumeric(bc.MessageAuthenticationCode); err != nil {
-		return &ValidateError{FieldName: "MessageAuthenticationCode", Value: bc.MessageAuthenticationCode, Err: err}
+		return &FieldError{FieldName: "MessageAuthenticationCode", Value: bc.MessageAuthenticationCode, Msg: err.Error()}
 	}
 
 	return nil
@@ -146,16 +150,16 @@ func (bc *BatchControl) Validate() error {
 // invalid the ACH transfer will be returned.
 func (bc *BatchControl) fieldInclusion() error {
 	if bc.recordType == "" {
-		return &ValidateError{FieldName: "recordType", Value: bc.recordType, Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "recordType", Value: bc.recordType, Msg: msgFieldInclusion}
 	}
 	if bc.ServiceClassCode == 0 {
-		return &ValidateError{FieldName: "ServiceClassCode", Value: string(bc.ServiceClassCode), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bc.ServiceClassCode), Msg: msgFieldInclusion}
 	}
 	if bc.ODFIIdentification == 0 {
-		return &ValidateError{FieldName: "ODFIIdentification", Value: string(bc.ODFIIdentification), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "ODFIIdentification", Value: bc.ODFIIdentificationField(), Msg: msgFieldInclusion}
 	}
 	if bc.BatchNumber == 0 {
-		return &ValidateError{FieldName: "BatchNumber", Value: string(bc.BatchNumber), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "BatchNumber", Value: bc.BatchNumberField(), Msg: msgFieldInclusion}
 	}
 	return nil
 }
diff --git a/batchHeader.go b/batchHeader.go
index 94240a46..5aa2c1c7 100644
--- a/batchHeader.go
+++ b/batchHeader.go
@@ -6,11 +6,12 @@ package ach
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 	"time"
 )
 
-// ErrServiceClass
+// msgServiceClass
 
 // BatchHeader identifies the originating entity and the type of transactions
 // contained in the batch (i.e., the standard entry class, PPD for consumer, CCD
@@ -162,28 +163,29 @@ func (bh *BatchHeader) Validate() error {
 		return err
 	}
 	if bh.recordType != "5" {
-		return &ValidateError{FieldName: "recordType", Value: bh.recordType, Err: ErrRecordType}
+		msg := fmt.Sprintf(msgRecordType, 5)
+		return &FieldError{FieldName: "recordType", Value: bh.recordType, Msg: msg}
 	}
 	if err := bh.isServiceClass(bh.ServiceClassCode); err != nil {
-		return &ValidateError{FieldName: "ServiceClassCode", Value: string(bh.ServiceClassCode), Err: err}
+		return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bh.ServiceClassCode), Msg: err.Error()}
 	}
 	if err := bh.isSECCode(bh.StandardEntryClassCode); err != nil {
-		return &ValidateError{FieldName: "ServiceClassCode", Value: string(bh.ServiceClassCode), Err: err}
+		return &FieldError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Msg: err.Error()}
 	}
 	if err := bh.isOriginatorStatusCode(bh.OriginatorStatusCode); err != nil {
-		return &ValidateError{FieldName: "OriginatorStatusCode", Value: string(bh.OriginatorStatusCode), Err: err}
+		return &FieldError{FieldName: "OriginatorStatusCode", Value: strconv.Itoa(bh.OriginatorStatusCode), Msg: err.Error()}
 	}
 	if err := bh.isAlphanumeric(bh.CompanyName); err != nil {
-		return &ValidateError{FieldName: "CompanyName", Value: string(bh.CompanyName), Err: err}
+		return &FieldError{FieldName: "CompanyName", Value: bh.CompanyName, Msg: err.Error()}
 	}
 	if err := bh.isAlphanumeric(bh.CompanyDiscretionaryData); err != nil {
-		return &ValidateError{FieldName: "CompanyDiscretionaryData", Value: string(bh.CompanyDiscretionaryData), Err: err}
+		return &FieldError{FieldName: "CompanyDiscretionaryData", Value: bh.CompanyDiscretionaryData, Msg: err.Error()}
 	}
 	if err := bh.isAlphanumeric(bh.CompanyIdentification); err != nil {
-		return &ValidateError{FieldName: "CompanyIdentification", Value: string(bh.CompanyIdentification), Err: err}
+		return &FieldError{FieldName: "CompanyIdentification", Value: bh.CompanyIdentification, Msg: err.Error()}
 	}
 	if err := bh.isAlphanumeric(bh.CompanyEntryDescription); err != nil {
-		return &ValidateError{FieldName: "CompanyEntryDescription", Value: string(bh.CompanyEntryDescription), Err: err}
+		return &FieldError{FieldName: "CompanyEntryDescription", Value: bh.CompanyEntryDescription, Msg: err.Error()}
 	}
 
 	return nil
@@ -193,31 +195,31 @@ func (bh *BatchHeader) Validate() error {
 // invalid the ACH transfer will be returned.
 func (bh *BatchHeader) fieldInclusion() error {
 	if bh.recordType == "" {
-		return &ValidateError{FieldName: "recordType", Value: bh.recordType, Err: ErrRecordType}
+		return &FieldError{FieldName: "recordType", Value: bh.recordType, Msg: msgFieldInclusion}
 	}
 	if bh.ServiceClassCode == 0 {
-		return &ValidateError{FieldName: "ServiceClassCode", Value: string(bh.ServiceClassCode), Err: ErrServiceClass}
+		return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bh.ServiceClassCode), Msg: msgFieldInclusion}
 	}
 	if bh.CompanyName == "" {
-		return &ValidateError{FieldName: "CompanyName", Value: bh.CompanyName, Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "CompanyName", Value: bh.CompanyName, Msg: msgFieldInclusion}
 	}
 	if bh.CompanyIdentification == "" {
-		return &ValidateError{FieldName: "CompanyIdentification", Value: bh.CompanyIdentification, Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "CompanyIdentification", Value: bh.CompanyIdentification, Msg: msgFieldInclusion}
 	}
 	if bh.StandardEntryClassCode == "" {
-		return &ValidateError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Err: ErrSECCode}
+		return &FieldError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Msg: msgFieldInclusion}
 	}
 	if bh.CompanyEntryDescription == "" {
-		return &ValidateError{FieldName: "CompanyEntryDescription", Value: bh.CompanyEntryDescription, Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "CompanyEntryDescription", Value: bh.CompanyEntryDescription, Msg: msgFieldInclusion}
 	}
 	if bh.OriginatorStatusCode == 0 {
-		return &ValidateError{FieldName: "OriginatorStatusCode", Value: string(bh.OriginatorStatusCode), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "OriginatorStatusCode", Value: strconv.Itoa(bh.OriginatorStatusCode), Msg: msgFieldInclusion}
 	}
 	if bh.ODFIIdentification == 0 {
-		return &ValidateError{FieldName: "ODFIIdentification", Value: string(bh.ODFIIdentification), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "ODFIIdentification", Value: bh.ODFIIdentificationField(), Msg: msgFieldInclusion}
 	}
 	if bh.BatchNumber == 0 {
-		return &ValidateError{FieldName: "BatchNumber", Value: string(bh.BatchNumber), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "BatchNumber", Value: strconv.Itoa(bh.BatchNumber), Msg: msgFieldInclusion}
 	}
 
 	return nil
diff --git a/batchPPD.go b/batchPPD.go
index 2baa440c..fcb30628 100644
--- a/batchPPD.go
+++ b/batchPPD.go
@@ -5,25 +5,7 @@
 package ach
 
 import (
-	"errors"
-)
-
-// Errors specific to parsing a Batch container
-var (
-	ErrBatchServiceClassMismatch = errors.New("Service Class Code is not the same in Header and Control")
-	ErrBatchEntryCountMismatch   = errors.New("Batch Entry Count is out-of-balance with number of Entries")
-	ErrBatchAmountMismatch       = errors.New("Batch Control debit and credit amounts are not the same as sum of Entries")
-	ErrBatchNumberMismatch       = errors.New("Batch Number is not the same in Header as Control")
-	ErrBatchAscendingTraceNumber = errors.New("Trace Numbers on the File are not in ascending order within a batch")
-	ErrBatchAddendaSequence      = errors.New("Addenda Sequence numbers are not in ascending order")
-	ErrValidEntryHash            = errors.New("Entry Hash is not equal to the sum of Entry Detail RDFI Identification")
-	ErrBatchOriginatorDNE        = errors.New("Originator Status Code is not equal to “2” for DNE if the Transaction Code is 23 or 33")
-	ErrBatchCompanyID            = errors.New("Company Identification must match the Company ID from the batch header record")
-	ErrBatchODFIIDMismatch       = errors.New("Batch Control ODFI Identification must be the same as batch header")
-	ErrBatchTraceNumberNotODFI   = errors.New("Trace Number in an Entry Detail Record are not the same as the ODFI Routing Number")
-	ErrBatchAddendaIndicator     = errors.New("Addenda found with no Addenda Indicator in proceeding Entry Detail")
-	ErrBatchAddendaTraceNumber   = errors.New("Addenda Entry Detail Sequence number does not match proceeding Entry Detail Trace Number")
-	ErrBatchEntries              = errors.New("Batch must have Entrie Record(s) to be built")
+	"fmt"
 )
 
 // BatchPPD holds the Batch Header and Batch Control and all Entry Records for PPD Entries
@@ -46,27 +28,31 @@ func NewBatchPPD() *BatchPPD {
 	return batch
 }
 
-// Validate NACHA rules on the entire batch before being added to a File
+// Validate checks valid NACHA batch rules. Assumes properly parsed records.
 func (batch *BatchPPD) Validate() error {
+	batchNumber := batch.header.BatchNumber
 	// validate batch header and control codes are the same
 	if batch.header.ServiceClassCode != batch.control.ServiceClassCode {
-		return ErrBatchServiceClassMismatch
+		msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.ServiceClassCode, batch.control.ServiceClassCode)
+		return &BatchError{BatchNumber: batchNumber, FieldName: "ServiceClassCode", Msg: msg}
 	}
 	// Company Identification must match the Company ID from the batch header record
 	if batch.header.CompanyIdentification != batch.control.CompanyIdentification {
-		return ErrBatchCompanyID
+		msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.CompanyIdentification, batch.control.CompanyIdentification)
+		return &BatchError{BatchNumber: batchNumber, FieldName: "CompanyIdentification", Msg: msg}
 	}
 	// Control ODFI Identification must be the same as batch header
 	if batch.header.ODFIIdentification != batch.control.ODFIIdentification {
-		return ErrBatchODFIIDMismatch
+		msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.ODFIIdentification, batch.control.ODFIIdentification)
+		return &BatchError{BatchNumber: batchNumber, FieldName: "ODFIIdentification", Msg: msg}
 	}
-
 	// batch number header and control must match
 	if batch.header.BatchNumber != batch.control.BatchNumber {
-		return ErrBatchNumberMismatch
+		msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.header.ODFIIdentification, batch.control.ODFIIdentification)
+		return &BatchError{BatchNumber: batchNumber, FieldName: "BatchNumber", Msg: msg}
 	}
 
-	if err := batch.isBatchEntryCountMismatch(); err != nil {
+	if err := batch.isBatchEntryCount(); err != nil {
 		return err
 	}
 
@@ -74,15 +60,15 @@ func (batch *BatchPPD) Validate() error {
 		return err
 	}
 
-	if err := batch.isBatchAmountMismatch(); err != nil {
+	if err := batch.isBatchAmount(); err != nil {
 		return err
 	}
 
-	if err := batch.isEntryHashMismatch(); err != nil {
+	if err := batch.isEntryHash(); err != nil {
 		return err
 	}
 
-	if err := batch.isOriginatorDNEMismatch(); err != nil {
+	if err := batch.isOriginatorDNE(); err != nil {
 		return err
 	}
 
@@ -97,7 +83,7 @@ func (batch *BatchPPD) Validate() error {
 	return nil
 }
 
-// ValidateAll validate all dependency records in the batch.
+// ValidateAll is a deep validation of all recods within the batch
 func (batch *BatchPPD) ValidateAll() error {
 	if err := batch.header.Validate(); err != nil {
 		return err
@@ -128,8 +114,9 @@ func (batch *BatchPPD) Build() error {
 	if err := batch.header.Validate(); err != nil {
 		return err
 	}
+	// Specific to Batch type.
 	if len(batch.entries) <= 0 {
-		return ErrBatchEntries
+		return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "entries", Msg: msgBatchEntries}
 	}
 	// build controls and sequence numbers
 	entryCount := 0
@@ -157,9 +144,9 @@ func (batch *BatchPPD) Build() error {
 	bc.TotalCreditEntryDollarAmount, bc.TotalDebitEntryDollarAmount = batch.calculateBatchAmounts()
 	batch.control = bc
 
-	// Validate the built batch
+	// validate checks that the above build covered all validation checks
 	if err := batch.ValidateAll(); err != nil {
-		return err
+		return err // only errors if source code of build is inconcisstent with validate
 	}
 	return nil
 }
@@ -190,42 +177,40 @@ func (batch *BatchPPD) GetEntries() []*EntryDetail {
 }
 
 // AddEntry appends an EntryDetail to the Batch
-//func (batch *Batch) AddEntryDetail(entry EntryDetail) []EntryDetail {
 func (batch *BatchPPD) AddEntry(entry *EntryDetail) Batcher {
-	//entry.setTraceNumber(batch.header.ODFIIdentification, 1)
 	batch.entries = append(batch.entries, entry)
 	//	return batch.entries
 	return Batcher(batch)
 }
 
-// isBatchEntryCountMismatch validate Entry count is accurate
+// isBatchEntryCount validate Entry count is accurate
 // The Entry/Addenda Count Field is a tally of each Entry Detail and Addenda
 // Record processed within the batch
-func (batch *BatchPPD) isBatchEntryCountMismatch() error {
+func (batch *BatchPPD) isBatchEntryCount() error {
 	entryCount := 0
 	for _, entry := range batch.entries {
 		entryCount = entryCount + 1 + len(entry.Addendums)
 	}
 	if entryCount != batch.control.EntryAddendaCount {
-		return ErrBatchEntryCountMismatch
+		msg := fmt.Sprintf(msgBatchCalculatedControlEquality, entryCount, batch.control.EntryAddendaCount)
+		return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "EntryAddendaCount", Msg: msg}
 	}
 	return nil
 }
 
-// isBatchAmountMismatch validate Amount is the same as what is in the Entries
+// isBatchAmount validate Amount is the same as what is in the Entries
 // The Total Debit and Credit Entry Dollar Amount fields contain accumulated
 // Entry Detail debit and credit totals within a given batch
-func (batch *BatchPPD) isBatchAmountMismatch() error {
+func (batch *BatchPPD) isBatchAmount() error {
 	credit, debit := batch.calculateBatchAmounts()
-	//fmt.Printf("debit: %v batch debit: %v \n", debit, batch.Control.TotalDebitEntryDollarAmount)
-
 	if debit != batch.control.TotalDebitEntryDollarAmount {
-		return ErrBatchAmountMismatch
+		msg := fmt.Sprintf(msgBatchCalculatedControlEquality, debit, batch.control.TotalDebitEntryDollarAmount)
+		return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TotalDebitEntryDollarAmount", Msg: msg}
 	}
-	//fmt.Printf("credit: %v batch credit: %v \n", credit, batch.Control.TotalCreditEntryDollarAmount)
 
 	if credit != batch.control.TotalCreditEntryDollarAmount {
-		return ErrBatchAmountMismatch
+		msg := fmt.Sprintf(msgBatchCalculatedControlEquality, credit, batch.control.TotalCreditEntryDollarAmount)
+		return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TotalCreditEntryDollarAmount", Msg: msg}
 	}
 	return nil
 }
@@ -256,18 +241,20 @@ func (batch *BatchPPD) isSequenceAscending() error {
 	lastSeq := -1
 	for _, entry := range batch.entries {
 		if entry.TraceNumber <= lastSeq {
-			return ErrBatchAscendingTraceNumber
+			msg := fmt.Sprintf(msgBatchAscending, entry.TraceNumber, lastSeq)
+			return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TraceNumber", Msg: msg}
 		}
 		lastSeq = entry.TraceNumber
 	}
 	return nil
 }
 
-// isEntryHashMismatch validates the hash by recalulating the result
-func (batch *BatchPPD) isEntryHashMismatch() error {
+// isEntryHash validates the hash by recalulating the result
+func (batch *BatchPPD) isEntryHash() error {
 	hashField := batch.calculateEntryHash()
 	if hashField != batch.control.EntryHashField() {
-		return ErrValidEntryHash
+		msg := fmt.Sprintf(msgBatchCalculatedControlEquality, hashField, batch.control.EntryHashField())
+		return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "EntryHash", Msg: msg}
 	}
 	return nil
 }
@@ -283,11 +270,12 @@ func (batch *BatchPPD) calculateEntryHash() string {
 }
 
 // The Originator Status Code is not equal to “2” for DNE if the Transaction Code is 23 or 33
-func (batch *BatchPPD) isOriginatorDNEMismatch() error {
+func (batch *BatchPPD) isOriginatorDNE() error {
 	if batch.header.OriginatorStatusCode != 2 {
 		for _, entry := range batch.entries {
 			if entry.TransactionCode == 23 || entry.TransactionCode == 33 {
-				return ErrBatchOriginatorDNE
+				msg := fmt.Sprintf(msgBatchOriginatorDNE, batch.header.OriginatorStatusCode)
+				return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "OriginatorStatusCode", Msg: msg}
 			}
 		}
 	}
@@ -299,7 +287,8 @@ func (batch *BatchPPD) isOriginatorDNEMismatch() error {
 func (batch *BatchPPD) isTraceNumberODFI() error {
 	for _, entry := range batch.entries {
 		if batch.header.ODFIIdentificationField() != entry.TraceNumberField()[:8] {
-			return ErrBatchTraceNumberNotODFI
+			msg := fmt.Sprintf(msgBatchTraceNumberNotODFI, batch.header.ODFIIdentificationField(), entry.TraceNumberField()[:8])
+			return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "ODFIIdentificationField", Msg: msg}
 		}
 	}
 
@@ -312,18 +301,20 @@ func (batch *BatchPPD) isAddendaSequence() error {
 		if len(entry.Addendums) > 0 {
 			// addenda without indicator flag of 1
 			if entry.AddendaRecordIndicator != 1 {
-				return ErrBatchAddendaIndicator
+				return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "AddendaRecordIndicator", Msg: msgBatchAddendaIndicator}
 			}
-			seq := -1
+			lastSeq := -1
 			// check if sequence is assending
 			for _, addenda := range entry.Addendums {
-				if !(addenda.SequenceNumber > seq) {
-					return ErrBatchAddendaSequence
+				if addenda.SequenceNumber < lastSeq {
+					msg := fmt.Sprintf(msgBatchAscending, addenda.SequenceNumber, lastSeq)
+					return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "SequenceNumber", Msg: msg}
 				}
-				seq = addenda.SequenceNumber
+				lastSeq = addenda.SequenceNumber
 				// check that we are in the correct Entry Detail
 				if !(addenda.EntryDetailSequenceNumberField() == entry.TraceNumberField()[8:]) {
-					return ErrBatchAddendaTraceNumber
+					msg := fmt.Sprintf(msgBatchAddendaTraceNumber, addenda.EntryDetailSequenceNumberField(), entry.TraceNumberField()[8:])
+					return &BatchError{BatchNumber: batch.header.BatchNumber, FieldName: "TraceNumber", Msg: msg}
 				}
 			}
 		}
diff --git a/batcher.go b/batcher.go
index d9c189d1..5b596d3f 100644
--- a/batcher.go
+++ b/batcher.go
@@ -1,5 +1,9 @@
 package ach
 
+import (
+	"fmt"
+)
+
 // Batcher abstract the different ACH batch types that can exist in a file.
 // Each batch type is defined by SEC (Standard Entry Class) code in the Batch Header
 // * SEC identifies the payment type (product) found within an ACH batch-using a 3-character code
@@ -19,3 +23,28 @@ type Batcher interface {
 	Validate() error
 	ValidateAll() error
 }
+
+// BatchError is an Error that describes batch validation issues
+type BatchError struct {
+	BatchNumber int
+	FieldName   string
+	Msg         string
+}
+
+func (e *BatchError) Error() string {
+	return fmt.Sprintf("BatchNumber %d %s %s", e.BatchNumber, e.FieldName, e.Msg)
+}
+
+// Errors specific to parsing a Batch container
+var (
+	// generic messages
+	msgBatchHeaderControlEquality     = "header %v is not equal to control %v"
+	msgBatchCalculatedControlEquality = "calculated %v is out-of-balance with control %v"
+	msgBatchAscending                 = "%v is less than last %v. Must be in ascending order"
+	// specific messages for error
+	msgBatchOriginatorDNE      = "%v is not “2” for DNE with entry transaction code of 23 or 33"
+	msgBatchTraceNumberNotODFI = "%v in header does not match entry trace number %v"
+	msgBatchAddendaIndicator   = "is 0 but found addenda record(s)"
+	msgBatchAddendaTraceNumber = "%v does not match proceeding entry detail trace number %v"
+	msgBatchEntries            = "must have Entrie Record(s) to be built"
+)
diff --git a/entryDetail.go b/entryDetail.go
index f481ff46..ca7ff359 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -4,7 +4,10 @@
 
 package ach
 
-import "fmt"
+import (
+	"fmt"
+	"strconv"
+)
 
 // EntryDetail contains the actual transaction data for an individual entry.
 // Fields include those designating the entry as a deposit (credit) or
@@ -136,26 +139,31 @@ func (ed *EntryDetail) Validate() error {
 		return err
 	}
 	if ed.recordType != "6" {
-		return &ValidateError{FieldName: "recordType", Value: ed.recordType, Err: ErrRecordType}
+		msg := fmt.Sprintf(msgRecordType, 6)
+		return &FieldError{FieldName: "recordType", Value: ed.recordType, Msg: msg}
 	}
 	if err := ed.isTransactionCode(ed.TransactionCode); err != nil {
-		return &ValidateError{FieldName: "TransactionCode", Value: string(ed.TransactionCode), Err: err}
+		return &FieldError{FieldName: "TransactionCode", Value: strconv.Itoa(ed.TransactionCode), Msg: err.Error()}
 	}
 	if err := ed.isAlphanumeric(ed.DFIAccountNumber); err != nil {
-		return &ValidateError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Err: err}
+		return &FieldError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Msg: err.Error()}
 	}
 	if err := ed.isAlphanumeric(ed.IndividualIdentificationNumber); err != nil {
-		return &ValidateError{FieldName: "IndividualIdentificationNumber", Value: ed.IndividualIdentificationNumber, Err: err}
+		return &FieldError{FieldName: "IndividualIdentificationNumber", Value: ed.IndividualIdentificationNumber, Msg: err.Error()}
 	}
 	if err := ed.isAlphanumeric(ed.IndividualName); err != nil {
-		return &ValidateError{FieldName: "IndividualName", Value: ed.IndividualName, Err: err}
+		return &FieldError{FieldName: "IndividualName", Value: ed.IndividualName, Msg: err.Error()}
 	}
 	if err := ed.isAlphanumeric(ed.DiscretionaryData); err != nil {
-		return &ValidateError{FieldName: "DiscretionaryData", Value: ed.DiscretionaryData, Err: err}
+		return &FieldError{FieldName: "DiscretionaryData", Value: ed.DiscretionaryData, Msg: err.Error()}
 	}
-	if err := ed.isCheckDigit(ed.RDFIIdentificationField(), ed.CheckDigit); err != nil {
-		return &ValidateError{FieldName: "CheckDigit", Value: string(ed.CheckDigit), Err: err}
+	calculated := ed.CalculateCheckDigit(ed.RDFIIdentificationField())
+	if calculated != ed.CheckDigit {
+		msg := fmt.Sprintf(msgValidCheckDigit, calculated)
+		return &FieldError{FieldName: "RDFIIdentification", Value: ed.RDFIIdentificationField(), Msg: msg}
+		//return msgValidCheckDigit
 	}
+
 	return nil
 }
 
@@ -163,28 +171,28 @@ func (ed *EntryDetail) Validate() error {
 // invalid the ACH transfer will be returned.
 func (ed *EntryDetail) fieldInclusion() error {
 	if ed.recordType == "" {
-		return &ValidateError{FieldName: "recordType", Value: ed.recordType, Err: ErrRecordType}
+		return &FieldError{FieldName: "recordType", Value: ed.recordType, Msg: msgFieldInclusion}
 	}
 	if ed.TransactionCode == 0 {
-		return &ValidateError{FieldName: "TransactionCode", Value: string(ed.TransactionCode), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "TransactionCode", Value: strconv.Itoa(ed.TransactionCode), Msg: msgFieldInclusion}
 	}
 	if ed.RDFIIdentification == 0 {
-		return &ValidateError{FieldName: "RDFIIdentification", Value: string(ed.RDFIIdentification), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "RDFIIdentification", Value: ed.RDFIIdentificationField(), Msg: msgFieldInclusion}
 	}
 	if ed.CheckDigit == 0 {
-		return &ValidateError{FieldName: "CheckDigit", Value: string(ed.CheckDigit), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "CheckDigit", Value: strconv.Itoa(ed.CheckDigit), Msg: msgFieldInclusion}
 	}
 	if ed.DFIAccountNumber == "" {
-		return &ValidateError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Msg: msgFieldInclusion}
 	}
 	if ed.Amount == 0 {
-		return &ValidateError{FieldName: "Amount", Value: string(ed.Amount), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "Amount", Value: ed.AmountField(), Msg: msgFieldInclusion}
 	}
 	if ed.IndividualName == "" {
-		return &ValidateError{FieldName: "IndividualName", Value: ed.IndividualName, Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "IndividualName", Value: ed.IndividualName, Msg: msgFieldInclusion}
 	}
 	if ed.TraceNumber == 0 {
-		return &ValidateError{FieldName: "TraceNumber", Value: string(ed.TraceNumber), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "TraceNumber", Value: ed.TraceNumberField(), Msg: msgFieldInclusion}
 	}
 	return nil
 }
diff --git a/file.go b/file.go
index 29d737ba..0291d6a1 100644
--- a/file.go
+++ b/file.go
@@ -9,7 +9,8 @@
 package ach
 
 import (
-	"errors"
+	"fmt"
+	"strconv"
 )
 
 // First position of all Record Types. These codes are uniquily assigned to
@@ -21,32 +22,54 @@ const (
 	entryAddendaPos = "7"
 	batchControlPos = "8"
 	fileControlPos  = "9"
+
+	// RecordLength character count of each line representing a letter in a file
+	RecordLength = 94
 )
 
-// Errors specific to parsing a Batch container
+// currently supported SEC codes
+const (
+	ppd = "PPD"
+)
+
+// Errors strings specific to parsing a Batch container
 var (
-	ErrFileBatchCount   = errors.New("Total Number of Batches in file is out-of-balance with File Control")
-	ErrFileEntryCount   = errors.New("Total Entries and Addenda count is out-of-balance with File Control")
-	ErrFileDebitAmount  = errors.New("Total Debit amount is out-of-balance with File Control")
-	ErrFileCreditAmount = errors.New("Total Credit amount is out-of-balance with File Control")
-	ErrFileEntryHash    = errors.New("Calculated Batch Control Entry hash does not match File Control Entry Hash")
-	ErrFileBatches      = errors.New("File must have []*Batches to be built")
+	msgFileControlEquality           = "header %v is not equal to control %v"
+	msgFileCalculatedControlEquality = "calculated %v is out-of-balance with control %v"
+	// specific messages
+	msgRecordLength      = "must be 94 characters and found %d"
+	msgFileBatchOutside  = "outside of current batch"
+	msgFileBatchInside   = "inside of current batch"
+	msgFileControl       = "none or more than one file control exists"
+	msgFileHeader        = "none or more than one file headers exists"
+	msgUnknownRecordType = "%s is an unknown record type"
+	msgFileNoneSEC       = "%v SEC(standard entry class) is not implemented"
 )
 
+// FileError is an error describing issues validating a file
+type FileError struct {
+	FieldName string
+	Value     string
+	Msg       string
+}
+
+func (e *FileError) Error() string {
+	return fmt.Sprintf("%s %s", e.FieldName, e.Msg)
+}
+
 // File contains the structures of a parsed ACH File.
 type File struct {
 	Header  FileHeader
 	Batches []Batcher
 	Control FileControl
-	// converters is composed for ACH to golang Converters
+
 	converters
 }
 
 // NewFile constucuts a file template.
 func NewFile() *File {
 	return &File{
-		Header: NewFileHeader(),
-		// Batches: []Batch, TODO need a NewBatchPPD
+		Header:  NewFileHeader(),
 		Control: NewFileControl(),
 	}
 }
@@ -59,7 +82,7 @@ func (f *File) Build() error {
 	}
 	// Requires at least one Batch in the new file.
 	if len(f.Batches) <= 0 {
-		return ErrFileBatches
+		return &FileError{FieldName: "Batchs", Value: strconv.Itoa(len(f.Batches)), Msg: "must have []*Batches to be built"}
 	}
 	// add 2 for FileHeader/control and reset if build was called twice do to error
 	totalRecordsInFile := 2
@@ -120,7 +143,8 @@ func (f *File) SetHeader(h FileHeader) *File {
 func (f *File) Validate() error {
 	// The value of the Batch Count Field is equal to the number of Company/Batch/Header Records in the file.
 	if f.Control.BatchCount != len(f.Batches) {
-		return ErrFileBatchCount
+		msg := fmt.Sprintf(msgFileCalculatedControlEquality, len(f.Batches), f.Control.BatchCount)
+		return &FileError{FieldName: "BatchCount", Value: strconv.Itoa(len(f.Batches)), Msg: msg}
 	}
 
 	if err := f.isEntryAddendaCount(); err != nil {
@@ -131,7 +155,7 @@ func (f *File) Validate() error {
 		return err
 	}
 
-	if err := f.isEntryHashMismatch(); err != nil {
+	if err := f.isEntryHash(); err != nil {
 		return err
 	}
 
@@ -159,16 +183,17 @@ func (f *File) ValidateAll() error {
 	return nil
 }
 
-// This field is prepared by hashing the RDFI’s 8-digit Routing Number in each entry.
+// isEntryAddenda is prepared by hashing the RDFI’s 8-digit Routing Number in each entry.
 //The Entry Hash provides a check against inadvertent alteration of data
 func (f *File) isEntryAddendaCount() error {
 	count := 0
-	// we assume that each batch block has already validated the addenda count in batch control.
+	// we assume that each batch block has already validated the addenda count is accurate in batch control.
 	for _, batch := range f.Batches {
 		count += batch.GetControl().EntryAddendaCount
 	}
 	if f.Control.EntryAddendaCount != count {
-		return ErrFileEntryCount
+		msg := fmt.Sprintf(msgFileCalculatedControlEquality, count, f.Control.EntryAddendaCount)
+		return &FileError{FieldName: "EntryAddendaCount", Value: f.Control.EntryAddendaCountField(), Msg: msg}
 	}
 	return nil
 }
@@ -183,19 +208,22 @@ func (f *File) isFileAmount() error {
 		credit += batch.GetControl().TotalCreditEntryDollarAmount
 	}
 	if f.Control.TotalDebitEntryDollarAmountInFile != debit {
-		return ErrFileDebitAmount
+		msg := fmt.Sprintf(msgFileCalculatedControlEquality, debit, f.Control.TotalDebitEntryDollarAmountInFile)
+		return &FileError{FieldName: "TotalDebitEntryDollarAmountInFile", Value: f.Control.TotalDebitEntryDollarAmountInFileField(), Msg: msg}
 	}
 	if f.Control.TotalCreditEntryDollarAmountInFile != credit {
-		return ErrFileCreditAmount
+		msg := fmt.Sprintf(msgFileCalculatedControlEquality, credit, f.Control.TotalCreditEntryDollarAmountInFile)
+		return &FileError{FieldName: "TotalCreditEntryDollarAmountInFile", Value: f.Control.TotalCreditEntryDollarAmountInFileField(), Msg: msg}
 	}
 	return nil
 }
 
-// isEntryHashMismatch validates the hash by recalulating the result
-func (f *File) isEntryHashMismatch() error {
+// isEntryHash validates the hash by recalulating the result
+func (f *File) isEntryHash() error {
 	hashField := f.calculateEntryHash()
 	if hashField != f.Control.EntryHashField() {
-		return ErrFileEntryHash
+		msg := fmt.Sprintf(msgFileCalculatedControlEquality, hashField, f.Control.EntryHashField())
+		return &FileError{FieldName: "EntryHash", Value: f.Control.EntryHashField(), Msg: msg}
 	}
 	return nil
 }
diff --git a/fileControl.go b/fileControl.go
index 5e2e1dd6..4c96fc13 100644
--- a/fileControl.go
+++ b/fileControl.go
@@ -88,7 +88,8 @@ func (fc *FileControl) Validate() error {
 		return err
 	}
 	if fc.recordType != "9" {
-		return &ValidateError{FieldName: "recordType", Value: fc.recordType, Err: ErrRecordType}
+		msg := fmt.Sprintf(msgRecordType, 9)
+		return &FieldError{FieldName: "recordType", Value: fc.recordType, Msg: msg}
 	}
 	return nil
 }
@@ -97,19 +98,19 @@ func (fc *FileControl) Validate() error {
 // invalid the ACH transfer will be returned.
 func (fc *FileControl) fieldInclusion() error {
 	if fc.recordType == "" {
-		return &ValidateError{FieldName: "recordType", Value: fc.recordType, Err: ErrRecordType}
+		return &FieldError{FieldName: "recordType", Value: fc.recordType, Msg: msgFieldInclusion}
 	}
 	if fc.BatchCount == 0 {
-		return &ValidateError{FieldName: "BatchCount", Value: string(fc.BatchCount), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "BatchCount", Value: fc.BatchCountField(), Msg: msgFieldInclusion}
 	}
 	if fc.BlockCount == 0 {
-		return &ValidateError{FieldName: "BlockCount", Value: string(fc.BlockCount), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "BlockCount", Value: fc.BlockCountField(), Msg: msgFieldInclusion}
 	}
 	if fc.EntryAddendaCount == 0 {
-		return &ValidateError{FieldName: "EntryAddendaCount", Value: string(fc.EntryAddendaCount), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "EntryAddendaCount", Value: fc.EntryAddendaCountField(), Msg: msgFieldInclusion}
 	}
 	if fc.EntryHash == 0 {
-		return &ValidateError{FieldName: "EntryAddendaCount", Value: string(fc.EntryAddendaCount), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "EntryAddendaCount", Value: fc.EntryAddendaCountField(), Msg: msgFieldInclusion}
 	}
 	return nil
 }
diff --git a/fileHeader.go b/fileHeader.go
index 5add7e44..8690c5b6 100644
--- a/fileHeader.go
+++ b/fileHeader.go
@@ -5,7 +5,6 @@
 package ach
 
 import (
-	"errors"
 	"fmt"
 	"strings"
 	"time"
@@ -13,11 +12,11 @@ import (
 
 // Errors specific to a File Header Record
 var (
-	ErrRecordType       = errors.New("Wrong Record type")
-	ErrRecordSize       = errors.New("Record size is not 094")
-	ErrBlockingFactor   = errors.New("Blocking Factor is not 10")
-	ErrFormatCode       = errors.New("Format Code is not 1.")
-	ErrFileCreationDate = errors.New("File was created before today")
+	msgRecordType       = "recieved expecting %d"
+	msgRecordSize       = "is not 094"
+	msgBlockingFactor   = "is not 10"
+	msgFormatCode       = "is not 1"
+	msgFileCreationDate = "was created before " + time.Now().String()
 )
 
 // FileHeader is a Record designating physical file characteristics and identify
@@ -163,31 +162,33 @@ func (fh *FileHeader) Validate() error {
 		return err
 	}
 	if fh.recordType != "1" {
-		return &ValidateError{FieldName: "recordType", Value: fh.recordType, Err: ErrRecordType}
+		msg := fmt.Sprintf(msgRecordType, 1)
+		return &FieldError{FieldName: "recordType", Value: fh.recordType, Msg: msg}
 	}
 	if err := fh.isUpperAlphanumeric(fh.FileIDModifier); err != nil {
-		return &ValidateError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Err: err}
+		return &FieldError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Msg: err.Error()}
 	}
 	if len(fh.FileIDModifier) != 1 {
-		return &ValidateError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Err: ErrValidFieldLength}
+		msg := fmt.Sprintf(msgValidFieldLength, 1)
+		return &FieldError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Msg: msg}
 	}
 	if fh.recordSize != "094" {
-		return &ValidateError{FieldName: "recordSize", Value: fh.recordSize, Err: ErrRecordSize}
+		return &FieldError{FieldName: "recordSize", Value: fh.recordSize, Msg: msgRecordSize}
 	}
 	if fh.blockingFactor != "10" {
-		return &ValidateError{FieldName: "blockingFactor", Value: fh.blockingFactor, Err: ErrBlockingFactor}
+		return &FieldError{FieldName: "blockingFactor", Value: fh.blockingFactor, Msg: msgBlockingFactor}
 	}
 	if fh.formatCode != "1" {
-		return &ValidateError{FieldName: "formatCode", Value: fh.formatCode, Err: ErrFormatCode}
+		return &FieldError{FieldName: "formatCode", Value: fh.formatCode, Msg: msgFormatCode}
 	}
 	if err := fh.isAlphanumeric(fh.ImmediateDestinationName); err != nil {
-		return &ValidateError{FieldName: "ImmediateDestinationName", Value: fh.ImmediateDestinationName, Err: err}
+		return &FieldError{FieldName: "ImmediateDestinationName", Value: fh.ImmediateDestinationName, Msg: err.Error()}
 	}
 	if err := fh.isAlphanumeric(fh.ImmediateOriginName); err != nil {
-		return &ValidateError{FieldName: "ImmediateOriginName", Value: fh.ImmediateOriginName, Err: err}
+		return &FieldError{FieldName: "ImmediateOriginName", Value: fh.ImmediateOriginName, Msg: err.Error()}
 	}
 	if err := fh.isAlphanumeric(fh.ReferenceCode); err != nil {
-		return &ValidateError{FieldName: "ReferenceCode", Value: fh.ReferenceCode, Err: err}
+		return &FieldError{FieldName: "ReferenceCode", Value: fh.ReferenceCode, Msg: err.Error()}
 	}
 
 	// todo: handle test cases for before date
@@ -203,28 +204,28 @@ func (fh *FileHeader) Validate() error {
 // invalid the ACH transfer will be returned.
 func (fh *FileHeader) fieldInclusion() error {
 	if fh.recordType == "" {
-		return &ValidateError{FieldName: "recordType", Value: fh.recordType, Err: ErrRecordType}
+		return &FieldError{FieldName: "recordType", Value: fh.recordType, Msg: msgFieldInclusion}
 	}
 	if fh.ImmediateDestination == 0 {
-		return &ValidateError{FieldName: "ImmediateDestination", Value: string(fh.ImmediateDestination), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "ImmediateDestination", Value: fh.ImmediateDestinationField(), Msg: msgFieldInclusion}
 	}
 	if fh.ImmediateOrigin == 0 {
-		return &ValidateError{FieldName: "ImmediateOrigin", Value: string(fh.ImmediateOrigin), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "ImmediateOrigin", Value: fh.ImmediateOriginField(), Msg: msgFieldInclusion}
 	}
 	if fh.FileCreationDate.IsZero() {
-		return &ValidateError{FieldName: "FileCreationDate", Value: fh.FileCreationDate.String(), Err: ErrValidFieldInclusion}
+		return &FieldError{FieldName: "FileCreationDate", Value: fh.FileCreationDate.String(), Msg: msgFieldInclusion}
 	}
 	if fh.FileIDModifier == "" {
-		return &ValidateError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Err: ErrRecordType}
+		return &FieldError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Msg: msgFieldInclusion}
 	}
 	if fh.recordSize == "" {
-		return &ValidateError{FieldName: "recordSize", Value: fh.recordSize, Err: ErrRecordType}
+		return &FieldError{FieldName: "recordSize", Value: fh.recordSize, Msg: msgFieldInclusion}
 	}
 	if fh.blockingFactor == "" {
-		return &ValidateError{FieldName: "blockingFactor", Value: fh.blockingFactor, Err: ErrRecordType}
+		return &FieldError{FieldName: "blockingFactor", Value: fh.blockingFactor, Msg: msgFieldInclusion}
 	}
 	if fh.formatCode == "" {
-		return &ValidateError{FieldName: "formatCode", Value: fh.formatCode, Err: ErrRecordType}
+		return &FieldError{FieldName: "formatCode", Value: fh.formatCode, Msg: msgFieldInclusion}
 	}
 	return nil
 }
diff --git a/reader.go b/reader.go
index 61043bba..8a1365be 100644
--- a/reader.go
+++ b/reader.go
@@ -6,14 +6,9 @@ package ach
 
 import (
 	"bufio"
-	"errors"
 	"fmt"
 	"io"
-)
-
-const (
-	// RecordLength character count of each line representing a letter in a file
-	RecordLength = 94
+	"strconv"
 )
 
 // ParseError is returned for parsing reader errors.
@@ -25,29 +20,12 @@ type ParseError struct {
 }
 
 func (e *ParseError) Error() string {
-	return fmt.Sprintf("LineNum:%d, RecordName:%s : %s \n", e.Line, e.Record, e.Err)
+	if e.Record == "" {
+		return fmt.Sprintf("line:%d %T %s", e.Line, e.Err, e.Err)
+	}
+	return fmt.Sprintf("line:%d record:%s %T %s", e.Line, e.Record, e.Err, e.Err)
 }
 
-// These are the errors that can be returned in Parse.Error
-// additional errors can occur in the Record
-var (
-	ErrFileRead           = errors.New("File could not be read")
-	ErrRecordLen          = errors.New("Wrong number of characters in record expected 94")
-	ErrBatchHeader        = errors.New("None or More than one Batch Headers exist in Batch.")
-	ErrBatchControl       = errors.New("No terminating batch control record found in file for previous batch")
-	ErrUnknownRecordType  = errors.New("Unhandled Record Type")
-	ErrFileHeader         = errors.New("None or more than one File Headers exists")
-	ErrFileControl        = errors.New("None or more than one File Control exists")
-	ErrEntryOutside       = errors.New("Entry Detail record outside of a batch")
-	ErrAddendaOutside     = errors.New("Entry Addenda without a preceding Entry Detail")
-	ErrAddendaNoIndicator = errors.New("Addenda without Entry Detail Addenda Inicator")
-)
-
-// currently support SEC codes
-const (
-	ppd = "PPD"
-)
-
 // Reader reads records from a ACH-encoded file.
 type Reader struct {
 	// r handles the IO.Reader sent to be parser.
@@ -79,7 +57,7 @@ func (r *Reader) addCurrentBatch(batch Batcher) {
 	r.currentBatch = batch
 }
 
-// NewReader returns a new Reader that reads from r.
+// NewReader returns a new ACH Reader that reads from r.
 func NewReader(r io.Reader) *Reader {
 	return &Reader{
 		scanner: bufio.NewScanner(r),
@@ -100,35 +78,39 @@ func (r *Reader) Read() (File, error) {
 		// handle the situation where there are no line breaks
 		if r.lineNum == 1 && lineLength > RecordLength && lineLength%RecordLength == 0 {
 			if err := r.processFixedWidthFile(&line); err != nil {
-				return r.File, r.error(err)
+				return r.File, err
 			}
 			break
 		}
 
 		// Only 94 ASCII characters to a line
 		if lineLength != RecordLength {
-			return r.File, r.error(ErrRecordLen)
+			msg := fmt.Sprintf(msgRecordLength, lineLength)
+			err := &FileError{FieldName: "RecordLength", Value: strconv.Itoa(lineLength), Msg: msg}
+			return r.File, r.error(err)
 		}
 
 		r.line = line
 
 		if err := r.parseLine(); err != nil {
-			return r.File, r.error(err)
+			return r.File, err
 		}
 	}
 
 	if err := r.scanner.Err(); err != nil {
-		return r.File, r.error(ErrFileRead)
+		return r.File, err
 	}
 
 	if (FileHeader{}) == r.File.Header {
 		// Their must be at least one File Header
-		return r.File, r.error(ErrFileHeader)
+		r.recordName = "FileHeader"
+		return r.File, r.error(&FileError{Msg: msgFileHeader})
 	}
 
 	if (FileControl{}) == r.File.Control {
 		// Their must be at least one File Control
-		return r.File, r.error(ErrFileControl)
+		r.recordName = "FileControl"
+		return r.File, r.error(&FileError{Msg: msgFileControl})
 	}
 
 	return r.File, nil
@@ -190,7 +172,8 @@ func (r *Reader) parseLine() error {
 			return err
 		}
 	default:
-		return r.error(ErrUnknownRecordType)
+		msg := fmt.Sprintf(msgUnknownRecordType, r.line[:1])
+		return r.error(&FileError{FieldName: "recordType", Value: r.line[:1], Msg: msg})
 	}
 
 	return nil
@@ -201,12 +184,12 @@ func (r *Reader) parseFileHeader() error {
 	r.recordName = "FileHeader"
 	if (FileHeader{}) != r.File.Header {
 		// Their can only be one File Header per File exit
-		return ErrFileHeader
+		r.error(&FileError{Msg: msgFileHeader})
 	}
 	r.File.Header.Parse(r.line)
 
 	if err := r.File.Header.Validate(); err != nil {
-		return err
+		return r.error(err)
 	}
 	return nil
 }
@@ -215,7 +198,8 @@ func (r *Reader) parseFileHeader() error {
 func (r *Reader) parseBatchHeader() error {
 	r.recordName = "BatchHeader"
 	if r.currentBatch != nil {
-		return ErrBatchHeader
+		// batch header inside of current batch
+		return r.error(&FileError{Msg: msgFileBatchInside})
 	}
 	bh := NewBatchHeader()
 	bh.Parse(r.line)
@@ -224,13 +208,13 @@ func (r *Reader) parseBatchHeader() error {
 		r.addCurrentBatch(NewBatchPPD())
 	// @TODO add additional batch types to creation
 	default:
-		return errors.New("Support for Batch's of SEC(standard entry class): " +
-			sec + ", has not been implemented")
+		msg := fmt.Sprintf(msgFileNoneSEC, sec)
+		return r.error(&FileError{FieldName: "StandardEntryClassCode", Msg: msg})
 	}
 	r.currentBatch.SetHeader(bh)
 
 	if err := r.currentBatch.GetHeader().Validate(); err != nil {
-		return err
+		return r.error(err)
 	}
 	return nil
 }
@@ -239,7 +223,7 @@ func (r *Reader) parseBatchHeader() error {
 func (r *Reader) parseEntryDetail() error {
 	r.recordName = "EntryDetail"
 	if r.currentBatch == nil {
-		return ErrEntryOutside
+		return r.error(&FileError{Msg: msgFileBatchOutside})
 	}
 	// @TODO change to EntryDetailer once interface is implmented if EntryDetails change
 	ed := new(EntryDetail)
@@ -248,12 +232,12 @@ func (r *Reader) parseEntryDetail() error {
 	case ppd:
 		ed.Parse(r.line)
 		if err := ed.Validate(); err != nil {
-			return err
+			return r.error(err)
 		}
-	//case "CCD":
+	//case "WEB":
 	default:
-		return errors.New("Support for EntryDetail of SEC(standard entry class): " +
-			sec + ", has not been implemented")
+		msg := fmt.Sprintf(msgFileNoneSEC, sec)
+		return r.error(&FileError{FieldName: "StandardEntryClassCode", Msg: msg})
 	}
 
 	r.currentBatch.AddEntry(ed)
@@ -265,10 +249,11 @@ func (r *Reader) parseAddenda() error {
 	r.recordName = "Addenda"
 
 	if r.currentBatch == nil {
-		return ErrAddendaOutside
+		msg := fmt.Sprintf(msgFileBatchOutside)
+		return r.error(&FileError{FieldName: "Addenda", Msg: msg})
 	}
 	if len(r.currentBatch.GetEntries()) == 0 {
-		return ErrAddendaOutside
+		return r.error(&FileError{FieldName: "Addenda", Msg: msgFileBatchOutside})
 	}
 	entryIndex := len(r.currentBatch.GetEntries()) - 1
 	entry := r.currentBatch.GetEntries()[entryIndex]
@@ -279,18 +264,18 @@ func (r *Reader) parseAddenda() error {
 			addenda := Addenda{}
 			addenda.Parse(r.line)
 			if err := addenda.Validate(); err != nil {
-				return err
+				return r.error(err)
 			}
 			r.currentBatch.GetEntries()[entryIndex].AddAddenda(addenda)
 		} else {
-			return ErrAddendaNoIndicator
+			msg := fmt.Sprintf(msgBatchAddendaIndicator)
+			return r.error(&FileError{FieldName: "AddendaRecordIndicator", Msg: msg})
 		}
 	// case "CCD":
 	default:
-		return errors.New("Support for addenda of SEC(standard entry class): " +
-			sec + ", has not been implemented")
+		msg := fmt.Sprintf(msgFileNoneSEC, sec)
+		return r.error(&FileError{FieldName: "StandardEntryClassCode", Msg: msg})
 	}
-
 	return nil
 }
 
@@ -298,12 +283,12 @@ func (r *Reader) parseAddenda() error {
 func (r *Reader) parseBatchControl() error {
 	r.recordName = "BatchControl"
 	if r.currentBatch == nil {
-		return ErrBatchHeader
+		// batch Control without a current batch
+		return r.error(&FileError{Msg: msgFileBatchOutside})
 	}
-
 	r.currentBatch.GetControl().Parse(r.line)
 	if err := r.currentBatch.GetControl().Validate(); err != nil {
-		return err
+		return r.error(err)
 	}
 	return nil
 }
@@ -312,12 +297,12 @@ func (r *Reader) parseBatchControl() error {
 func (r *Reader) parseFileControl() error {
 	r.recordName = "FileControl"
 	if (FileControl{}) != r.File.Control {
-		// Their can only be one File Control per File exit
-		return ErrFileControl
+		// Can be only one file control per file
+		return r.error(&FileError{Msg: msgFileControl})
 	}
 	r.File.Control.Parse(r.line)
 	if err := r.File.Control.Validate(); err != nil {
-		return err
+		return r.error(err)
 	}
 	return nil
 }
diff --git a/validators.go b/validators.go
index f6c31e3c..328d773f 100644
--- a/validators.go
+++ b/validators.go
@@ -15,30 +15,33 @@ import (
 // validator is common validation and formating of golang types to ach type strings
 type validator struct{}
 
-// ValidateError is returned for errors at a field level in a record
-type ValidateError struct {
+// FieldError is returned for errors at a field level in a record
+type FieldError struct {
 	FieldName string // field name where error happend
 	Value     string // value that cause error
-	Err       error  // the actual error
+	Msg       string // context of the error.
 }
 
-func (e *ValidateError) Error() string {
-	return fmt.Sprintf("FieldName:%s Value:%s msg: %s ", e.FieldName, e.Value, e.Err)
+// Error message is constructed
+// FieldName Msg Value
+// Example1: BatchCount $% has none alphanumeric characters
+// Example2: BatchCount 5 is out-of-balance with file count 6
+func (e *FieldError) Error() string {
+	return fmt.Sprintf("%s %s %s", e.FieldName, e.Value, e.Msg)
 }
 
 // Errors specific to validation
 var (
-	ErrValidAlphanumeric   = errors.New("Field has non alphanumeric characters")
-	ErrValidAlpha          = errors.New("Field has non alpha characters")
-	ErrUpperAlpha          = errors.New("Field is not uppercase A-Z or 0-9")
-	ErrValidFieldInclusion = errors.New("A mandatory field has a default value")
-	ErrValidFieldLength    = errors.New("Field length is invalid")
-	ErrServiceClass        = errors.New("Invalid Service Class Code")
-	ErrSECCode             = errors.New("Invalid Standard Entry Class Code")
-	ErrOrigStatusCode      = errors.New("Invalid Originator Status Code")
-	ErrAddendaTypeCode     = errors.New("Invalid Addenda Type Code")
-	ErrTransactionCode     = errors.New("Invalid Transaction Code")
-	ErrValidCheckDigit     = errors.New("Check Digit does not match calculated check digit")
+	msgAlphanumeric     = "has non alphanumeric characters"
+	msgUpperAlpha       = "is not uppercase A-Z or 0-9"
+	msgFieldInclusion   = "is a mandatory field and has a default value"
+	msgValidFieldLength = "is not length %d"
+	msgServiceClass     = "is an invalid Service Class Code"
+	msgSECCode          = "is an invalid Standard Entry Class Code"
+	msgOrigStatusCode   = "is an invalid Originator Status Code"
+	msgAddendaTypeCode  = "is an invalid Addenda Type Code"
+	msgTransactionCode  = "is an invalid Transaction Code"
+	msgValidCheckDigit  = "does not match calculated check digit %d"
 )
 
 // iServiceClass returns true if a valid service class code
@@ -55,7 +58,7 @@ func (v *validator) isServiceClass(code int) error {
 		280:
 		return nil
 	}
-	return ErrServiceClass
+	return errors.New(msgServiceClass)
 }
 
 // isSECCode returns true if a SEC Code is found
@@ -66,7 +69,7 @@ func (v *validator) isSECCode(code string) error {
 		"IAT", "MTE", "POS", "PPD", "POP", "RCK", "SHR", "TEL", "TRC", "TRX", "WEB", "XCK":
 		return nil
 	}
-	return ErrSECCode
+	return errors.New(msgSECCode)
 }
 
 // isTypeCode returns true if a valid type code is found
@@ -87,7 +90,7 @@ func (v *validator) isTypeCode(code string) error {
 		"05":
 		return nil
 	}
-	return ErrAddendaTypeCode
+	return errors.New(msgAddendaTypeCode)
 }
 
 // isTransactionCode ensures TransactionCode code is valid
@@ -113,7 +116,7 @@ func (v *validator) isTransactionCode(code int) error {
 		38:
 		return nil
 	}
-	return ErrTransactionCode
+	return errors.New(msgTransactionCode)
 }
 
 // isOriginatorStatusCode ensures status code is valid
@@ -128,13 +131,13 @@ func (v *validator) isOriginatorStatusCode(code int) error {
 		2:
 		return nil
 	}
-	return ErrOrigStatusCode
+	return errors.New(msgOrigStatusCode)
 }
 
 // isUpperAlphanumeric checks if string only contains ASCII alphanumeric upper case characters
 func (v *validator) isUpperAlphanumeric(s string) error {
 	if regexp.MustCompile(`[^A-Z0-9]+`).MatchString(s) {
-		return ErrUpperAlpha
+		return errors.New(msgUpperAlpha)
 	}
 	return nil
 }
@@ -143,18 +146,23 @@ func (v *validator) isUpperAlphanumeric(s string) error {
 func (v *validator) isAlphanumeric(s string) error {
 	if regexp.MustCompile(`[^ a-zA-Z0-9_*-\/]+`).MatchString(s) {
 		// ^[ A-Za-z0-9_@./#&+-]*$/
-		return ErrValidAlphanumeric
+		return errors.New(msgAlphanumeric)
 	}
 	return nil
 }
 
 // isOriginatorStatusCode ensures status code is valid
+/*
 func (v *validator) isCheckDigit(routingNumber string, checkDigit int) error {
-	if v.CalculateCheckDigit(routingNumber) != checkDigit {
-		return ErrValidCheckDigit
+	calculated := v.CalculateCheckDigit(routingNumber)
+	if calculated != checkDigit {
+		msg := fmt.Sprintf(msgValidCheckDigit, calculated)
+		return &FieldError{FieldName: "RoutingNumber", Value: routingNumber, Msg: msg}
+		//return msgValidCheckDigit
 	}
 	return nil
 }
+*/
 
 // CalculateCheckDigit returns a check digit for a rounting number
 // Multiply each digit in the Routing number by a weighting factor. The weighting factors for each digit are:


Test patch:
diff --git a/addenda_internal_test.go b/addenda_internal_test.go
index ad500643..1a467100 100644
--- a/addenda_internal_test.go
+++ b/addenda_internal_test.go
@@ -11,13 +11,20 @@ import (
 
 func mockAddenda() Addenda {
 	addenda := NewAddenda()
-	addenda.TypeCode = "08"
-	addenda.SequenceNumber = 1
 	addenda.EntryDetailSequenceNumber = 1234567
 	return addenda
 }
 
-// TestParseAddendaRecord parses a known Addenda Record string.
+func TestMockAddenda(t *testing.T) {
+	addenda := mockAddenda()
+	if err := addenda.Validate(); err != nil {
+		t.Error("mockAddenda does not validate and will break other tests")
+	}
+	if addenda.EntryDetailSequenceNumber != 1234567 {
+		t.Error("EntryDetailSequenceNumber depedendent default value has changed")
+	}
+}
+
 func TestParseAddenda(t *testing.T) {
 	var line = "710WEB                                        DIEGO MAY                            00010000001"
 
@@ -28,7 +35,7 @@ func TestParseAddenda(t *testing.T) {
 	r.line = line
 	err := r.parseAddenda()
 	if err != nil {
-		t.Errorf("unknown error: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetEntries()[0].Addendums[0]
 
@@ -59,7 +66,7 @@ func TestAddendaString(t *testing.T) {
 	r.line = line
 	err := r.parseAddenda()
 	if err != nil {
-		t.Errorf("unknown error: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetEntries()[0].Addendums[0]
 	if record.String() != line {
@@ -67,58 +74,62 @@ func TestAddendaString(t *testing.T) {
 	}
 }
 
-// TestValidateAddendaRecordType ensure error if recordType is not 7
 func TestValidateAddendaRecordType(t *testing.T) {
 	addenda := mockAddenda()
 	addenda.recordType = "2"
 	if err := addenda.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Expected RecordType Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
-// TestValidateAddendaRecordType ensure error if recordType is not 7
 func TestValidateAddendaTypeCode(t *testing.T) {
 	addenda := mockAddenda()
 	addenda.TypeCode = "23"
 	if err := addenda.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Expected Type Code Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "TypeCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestAddendaFieldInclusion(t *testing.T) {
 	addenda := mockAddenda()
-	// works properly
-	if err := addenda.Validate(); err != nil {
-		t.Errorf("Unexpected Addenda error: %v", err.Error())
-	}
-	// create error is mismatch
 	addenda.EntryDetailSequenceNumber = 0
 	if err := addenda.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Addenda error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "EntryDetailSequenceNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
-func TestAddendaPaymentRelatedInformationAlphaNumeric(t *testing.T) {
+func TestAddendaFieldInclusionRecordType(t *testing.T) {
 	addenda := mockAddenda()
-	// works properly
+	addenda.recordType = ""
 	if err := addenda.Validate(); err != nil {
-		t.Errorf("Unexpected Addenda error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		}
 	}
-	// create error is mismatch
+}
+
+func TestAddendaPaymentRelatedInformationAlphaNumeric(t *testing.T) {
+	addenda := mockAddenda()
 	addenda.PaymentRelatedInformation = "@!"
 	if err := addenda.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Addenda error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "PaymentRelatedInformation" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
diff --git a/batchControl_internal_test.go b/batchControl_internal_test.go
index 5d32178b..b87eb970 100644
--- a/batchControl_internal_test.go
+++ b/batchControl_internal_test.go
@@ -13,12 +13,26 @@ func mockBatchControl() *BatchControl {
 	bc := NewBatchControl()
 	bc.ServiceClassCode = 220
 	bc.CompanyIdentification = "123456789"
-	bc.EntryHash = 1
 	bc.ODFIIdentification = 6200001
-	bc.BatchNumber = 1
 	return bc
 }
 
+func TestMockBatchControl(t *testing.T) {
+	bc := mockBatchControl()
+	if err := bc.Validate(); err != nil {
+		t.Error("mockBatchControl does not validate and will break other tests")
+	}
+	if bc.ServiceClassCode != 220 {
+		t.Error("ServiceClassCode depedendent default value has changed")
+	}
+	if bc.CompanyIdentification != "123456789" {
+		t.Error("CompanyIdentification depedendent default value has changed")
+	}
+	if bc.ODFIIdentification != 6200001 {
+		t.Error("ODFIIdentification depedendent default value has changed")
+	}
+}
+
 // TestParseBatchControl parses a known Batch ControlRecord string.
 func TestParseBatchControl(t *testing.T) {
 	var line = "82250000010005320001000000010500000000000000origid                             076401250000001"
@@ -32,10 +46,8 @@ func TestParseBatchControl(t *testing.T) {
 	r.currentBatch.SetHeader(&bh)
 
 	r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: 5320001, TraceNumber: 76401255655291})
-	//fmt.Printf("%+v \n", r.line)
-	err := r.parseBatchControl()
-	if err != nil {
-		t.Errorf("unknown error: %v", err)
+	if err := r.parseBatchControl(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetControl()
 
@@ -87,9 +99,8 @@ func TestBCString(t *testing.T) {
 	r.currentBatch.SetHeader(&bh)
 
 	r.currentBatch.AddEntry(&EntryDetail{TransactionCode: 27, Amount: 10500, RDFIIdentification: 5320001, TraceNumber: 76401255655291})
-	err := r.parseBatchControl()
-	if err != nil {
-		t.Errorf("unknown error: %v", err)
+	if err := r.parseBatchControl(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetControl()
 
@@ -103,81 +114,58 @@ func TestValidateBCRecordType(t *testing.T) {
 	bc := mockBatchControl()
 	bc.recordType = "2"
 	if err := bc.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrRecordType.Error()) {
-			t.Errorf("Expected RecordType Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBCisServiceClassErr(t *testing.T) {
 	bc := mockBatchControl()
-	// works properly
-	if err := bc.Validate(); err != nil {
-		t.Errorf("Unexpected BatchControl error: %v", err.Error())
-	}
-	// create error is mismatch
 	bc.ServiceClassCode = 123
 	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected BatchControl error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "ServiceClassCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
-func TestBCFieldInclusion(t *testing.T) {
+func TestBCBatchNumber(t *testing.T) {
 	bc := mockBatchControl()
-	// works properly
-	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected BatchControl error: %v", err.Error())
-		}
-	}
-	// create error is mismatch
 	bc.BatchNumber = 0
 	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected BatchControl error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "BatchNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBCCompanyIdentificationAlphaNumeric(t *testing.T) {
 	bc := mockBatchControl()
-	// works properly
-	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected BatchControl error: %v", err.Error())
-		}
-	}
-	// create error is mismatch
 	bc.CompanyIdentification = "@!"
 	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected BatchControl error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyIdentification" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBCMessageAuthenticationCodeAlphaNumeric(t *testing.T) {
 	bc := mockBatchControl()
-	// works properly
-	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected BatchControl error: %v", err.Error())
-		}
-	}
-	// create error is mismatch
 	bc.MessageAuthenticationCode = "@!"
 	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected BatchControl error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "MessageAuthenticationCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -186,9 +174,10 @@ func TestBCFieldInclusionRecordType(t *testing.T) {
 	bc := mockBatchControl()
 	bc.recordType = ""
 	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -197,9 +186,10 @@ func TestBCFieldInclusionServiceClassCode(t *testing.T) {
 	bc := mockBatchControl()
 	bc.ServiceClassCode = 0
 	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -208,9 +198,10 @@ func TestBCFieldInclusionODFIIdentification(t *testing.T) {
 	bc := mockBatchControl()
 	bc.ODFIIdentification = 0
 	if err := bc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
diff --git a/batchHeader_internal_test.go b/batchHeader_internal_test.go
index fbe2f369..440b3dba 100644
--- a/batchHeader_internal_test.go
+++ b/batchHeader_internal_test.go
@@ -17,20 +17,41 @@ func mockBatchHeader() *BatchHeader {
 	bh.CompanyIdentification = "123456789"
 	bh.CompanyEntryDescription = "PAYROLL"
 	bh.ODFIIdentification = 6200001
-	bh.BatchNumber = 1
 	return bh
 }
 
+func TestMockBatchHeader(t *testing.T) {
+	bh := mockBatchHeader()
+	if err := bh.Validate(); err != nil {
+		t.Error("mockBatchHeader does not validate and will break other tests")
+	}
+	if bh.ServiceClassCode != 220 {
+		t.Error("ServiceClassCode depedendent default value has changed")
+	}
+	if bh.StandardEntryClassCode != "PPD" {
+		t.Error("StandardEntryClassCode depedendent default value has changed")
+	}
+	if bh.CompanyName != "ACME Corporation" {
+		t.Error("CompanyName depedendent default value has changed")
+	}
+	if bh.CompanyIdentification != "123456789" {
+		t.Error("CompanyIdentification depedendent default value has changed")
+	}
+	if bh.CompanyEntryDescription != "PAYROLL" {
+		t.Error("CompanyEntryDescription depedendent default value has changed")
+	}
+	if bh.ODFIIdentification != 6200001 {
+		t.Error("ODFIIdentification depedendent default value has changed")
+	}
+}
+
 // TestParseBatchHeader parses a known Batch Header Record string.
 func TestParseBatchHeader(t *testing.T) {
 	var line = "5225companyname                         origid    PPDCHECKPAYMT000002080730   1076401250000001"
 	r := NewReader(strings.NewReader(line))
 	r.line = line
-	err := r.parseBatchHeader()
-	//fmt.Printf("holler %+v \n", r.currentBatch.Header)
-
-	if err != nil {
-		t.Errorf("unknown error: %v", err)
+	if err := r.parseBatchHeader(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetHeader()
 
@@ -80,16 +101,13 @@ func TestBHString(t *testing.T) {
 	var line = "5225companyname                         origid    PPDCHECKPAYMT000002080730   1076401250000001"
 	r := NewReader(strings.NewReader(line))
 	r.line = line
-	err := r.parseBatchHeader()
-	if err != nil {
-		t.Errorf("unknown error: %v", err)
+	if err := r.parseBatchHeader(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetHeader()
 
 	if record.String() != line {
 		t.Errorf("Strings do not match")
-
-		//fmt.Printf("%s \n %s", line, r.currentBatch.Header.String())
 	}
 }
 
@@ -98,11 +116,12 @@ func TestValidateBHRecordType(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.recordType = "2"
 	if err := bh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrRecordType.Error()) {
-			t.Errorf("Expected RecordType Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
-
 }
 
 // TestInvalidServiceCode ensure error if service class is not valid
@@ -110,8 +129,10 @@ func TestInvalidServiceCode(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.ServiceClassCode = 123
 	if err := bh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrServiceClass.Error()) {
-			t.Errorf("Expected Service Class Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "ServiceClassCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -119,10 +140,12 @@ func TestInvalidServiceCode(t *testing.T) {
 // TestValidateInvalidServiceCode ensure error if service class is not valid
 func TestInvalidSECCode(t *testing.T) {
 	bh := mockBatchHeader()
-	bh.StandardEntryClassCode = "ABC"
+	bh.StandardEntryClassCode = "123"
 	if err := bh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrSECCode.Error()) {
-			t.Errorf("Expected SEC CodeError got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "StandardEntryClassCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -132,88 +155,70 @@ func TestInvalidOrigStatusCode(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.OriginatorStatusCode = 3
 	if err := bh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrOrigStatusCode.Error()) {
-			t.Errorf("Expected Originator Status CodeError got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "OriginatorStatusCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBatchHeaderFieldInclusion(t *testing.T) {
 	bh := mockBatchHeader()
-	// works properly
-	if err := bh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	bh.BatchNumber = 0
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "BatchNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBatchHeaderCompanyNameAlphaNumeric(t *testing.T) {
 	bh := mockBatchHeader()
-	// works properly
-	if err := bh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	bh.CompanyName = "AT&T"
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyName" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBatchCompanyDiscretionaryDataAlphaNumeric(t *testing.T) {
 	bh := mockBatchHeader()
-	// works properly
-	if err := bh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	bh.CompanyDiscretionaryData = "Invoice: #12345"
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyDiscretionaryData" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBatchCompanyIdentificationAlphaNumeric(t *testing.T) {
 	bh := mockBatchHeader()
-	// works properly
-	if err := bh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	bh.CompanyIdentification = "EIN:12345"
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyIdentification" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestBatchCompanyEntryDescriptionAlphaNumeric(t *testing.T) {
 	bh := mockBatchHeader()
-	// works properly
-	if err := bh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	bh.CompanyEntryDescription = "P@YROLL"
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyEntryDescription" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -222,9 +227,10 @@ func TestBHFieldInclusionRecordType(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.recordType = ""
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -233,9 +239,10 @@ func TestBHFieldInclusionCompanyName(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.CompanyName = ""
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyName" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -244,9 +251,10 @@ func TestBHFieldInclusionCompanyIdentification(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.CompanyIdentification = ""
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyIdentification" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -255,9 +263,10 @@ func TestBHFieldInclusionStandardEntryClassCode(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.StandardEntryClassCode = ""
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "StandardEntryClassCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -266,9 +275,10 @@ func TestBHFieldInclusionCompanyEntryDescription(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.CompanyEntryDescription = ""
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "CompanyEntryDescription" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -277,9 +287,10 @@ func TestBHFieldInclusionOriginatorStatusCode(t *testing.T) {
 	bh := mockBatchHeader()
 	bh.OriginatorStatusCode = 0
 	if err := bh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "OriginatorStatusCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
diff --git a/batchPPD_test.go b/batchPPD_test.go
index e6c6c04e..6508e502 100644
--- a/batchPPD_test.go
+++ b/batchPPD_test.go
@@ -5,125 +5,84 @@
 package ach
 
 import (
-	"strings"
 	"testing"
 	"time"
 )
 
-// TestBatchEntryCountMismatc check for control out-of-balance error.
-func TestBatchEntryCountMismatch(t *testing.T) {
+func mockBatchPPD() *BatchPPD {
 	mockBatch := NewBatchPPD()
 	mockBatch.SetHeader(mockBatchHeader())
-	e := mockEntryDetail()
-	a := mockAddenda()
-	e.AddAddenda(a)
-	mockBatch.AddEntry(e)
-
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Build error: %v", err.Error())
-	}
-	// create error is mismatch
-	mockBatch.GetControl().EntryAddendaCount = 1
+	mockBatch.AddEntry(mockEntryDetail())
 	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchEntryCountMismatch {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-		}
+		panic(err)
 	}
+	return mockBatch
 }
 
-func TestBatchServiceClassMismatch(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+func TestBatchError(t *testing.T) {
+	err := &BatchError{BatchNumber: 1, FieldName: "mock", Msg: "test message"}
+	if err.Error() != "BatchNumber 1 mock test message" {
+		t.Error("BatchError Error has changed formatting")
 	}
-	// create error is mismatch
+}
+
+func TestBatchServiceClassCodeEsquality(t *testing.T) {
+	mockBatch := mockBatchPPD()
 	mockBatch.GetControl().ServiceClassCode = 225
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchServiceClassMismatch {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "ServiceClassCode" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestBatchCompanyIdentification(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
+	mockBatch := mockBatchPPD()
 	mockBatch.GetControl().CompanyIdentification = "XYZ Inc"
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchCompanyID {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "CompanyIdentification" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestBatchODFIIDMismatch(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
+	mockBatch := mockBatchPPD()
 	mockBatch.GetControl().ODFIIdentification = 987654321
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchODFIIDMismatch {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "ODFIIdentification" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestBatchNumberMismatch(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
+	mockBatch := mockBatchPPD()
 	mockBatch.GetControl().BatchNumber = 2
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchNumberMismatch {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "BatchNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestBatchIsSequenceAscending(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-	mockBatch.AddEntry(mockEntryDetail())
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error
-	e3 := mockEntryDetail()
-	e3.TraceNumber = 0
-	mockBatch.AddEntry(e3)
-	mockBatch.GetControl().EntryAddendaCount = 3
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchAscendingTraceNumber {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-		}
-	}
-}
-
-// isBatchAmountMismatch
-func TestCreditBatchIsBatchAmountMismatch(t *testing.T) {
+func TestCreditBatchisBatchAmount(t *testing.T) {
 	mockBatch := NewBatchPPD()
 	mockBatch.SetHeader(mockBatchHeader())
 	e1 := mockEntryDetail()
@@ -134,20 +93,23 @@ func TestCreditBatchIsBatchAmountMismatch(t *testing.T) {
 	e2.Amount = 100
 	mockBatch.AddEntry(e1)
 	mockBatch.AddEntry(e2)
-	// works properly
 	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		t.Errorf("%T: %s", err, err)
 	}
-	// create error
+
 	mockBatch.GetControl().TotalCreditEntryDollarAmount = 1
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchAmountMismatch {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "TotalCreditEntryDollarAmount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestSavingsBatchIsBatchAmountMismatch(t *testing.T) {
+func TestSavingsBatchisBatchAmount(t *testing.T) {
 	mockBatch := NewBatchPPD()
 	mockBatch.SetHeader(mockBatchHeader())
 	e1 := mockEntryDetail()
@@ -158,131 +120,166 @@ func TestSavingsBatchIsBatchAmountMismatch(t *testing.T) {
 	e2.Amount = 100
 	mockBatch.AddEntry(e1)
 	mockBatch.AddEntry(e2)
-	// works properly
 	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		t.Errorf("%T: %s", err, err)
 	}
-	// create error
+
 	mockBatch.GetControl().TotalDebitEntryDollarAmount = 1
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchAmountMismatch {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "TotalDebitEntryDollarAmount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestBatchIsEntryHashMismatch(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-	mockBatch.AddEntry(mockEntryDetail())
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error
-	mockBatch.GetControl().EntryHash = 0
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrValidEntryHash {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+func TestBatchisEntryHash(t *testing.T) {
+	mockBatch := mockBatchPPD()
+	mockBatch.GetControl().EntryHash = 1
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "EntryHash" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-// isOriginatorDNEMismatch
-func TestBatchIsOriginatorDNEMismatch(t *testing.T) {
+func TestBatchDNEMismatch(t *testing.T) {
 	mockBatch := NewBatchPPD()
 	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// Make it fail
-	mockBatch.GetHeader().OriginatorStatusCode = 1
+	ed := mockEntryDetail()
+	ed.AddAddenda(mockAddenda())
+	ed.AddAddenda(mockAddenda())
+	mockBatch.AddEntry(ed)
+	mockBatch.Build()
 
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchOriginatorDNE {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	mockBatch.GetHeader().OriginatorStatusCode = 1
+	mockBatch.GetEntries()[0].TransactionCode = 23
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "OriginatorStatusCode" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-// ErrBatchTraceNumberNotODFI
 func TestBatchTraceNumberNotODFI(t *testing.T) {
+	mockBatch := mockBatchPPD()
+	mockBatch.GetEntries()[0].setTraceNumber(12345678, 1)
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "ODFIIdentificationField" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+func TestBatchEntryCountEquality(t *testing.T) {
 	mockBatch := NewBatchPPD()
 	mockBatch.SetHeader(mockBatchHeader())
-	mockBatch.AddEntry(mockEntryDetail())
-	// works properly
+	e := mockEntryDetail()
+	a := mockAddenda()
+	e.AddAddenda(a)
+	mockBatch.AddEntry(e)
 	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		t.Errorf("%T: %s", err, err)
 	}
-	// Make it fail
-	mockBatch.GetEntries()[0].TraceNumber = 1
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchTraceNumberNotODFI {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+
+	mockBatch.GetControl().EntryAddendaCount = 1
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "EntryAddendaCount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-// ErrBatchAddendaIndicator
 func TestBatchAddendaIndicator(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// Make it fail
+	mockBatch := mockBatchPPD()
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda())
 	mockBatch.GetEntries()[0].AddendaRecordIndicator = 0
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchAddendaIndicator {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	mockBatch.GetControl().EntryAddendaCount = 2
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "AddendaRecordIndicator" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-// isAddendaSequence
-func TestBatchAddendaSequence(t *testing.T) {
+func TestBatchIsAddendaSeqAscending(t *testing.T) {
 	mockBatch := NewBatchPPD()
 	mockBatch.SetHeader(mockBatchHeader())
 	ed := mockEntryDetail()
 	ed.AddAddenda(mockAddenda())
+	ed.AddAddenda(mockAddenda())
 	mockBatch.AddEntry(ed)
-	// works properly
-	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	mockBatch.Build()
+
+	mockBatch.GetEntries()[0].Addendums[0].SequenceNumber = 2
+	mockBatch.GetEntries()[0].Addendums[1].SequenceNumber = 1
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "SequenceNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
 	}
-	// Make it fail
-	mockBatch.GetEntries()[0].Addendums[0].SequenceNumber = 10
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchAddendaSequence {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+}
+
+func TestBatchIsSequenceAscending(t *testing.T) {
+	mockBatch := mockBatchPPD()
+	e3 := mockEntryDetail()
+	e3.TraceNumber = 0
+	mockBatch.AddEntry(e3)
+	mockBatch.GetControl().EntryAddendaCount = 2
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "TraceNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-// ErrBatchAddendaTraceNumber
 func TestBatchAddendaTraceNumber(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	// works properly
+	mockBatch := mockBatchPPD()
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda())
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda())
 	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		t.Errorf("%T: %s", err, err)
 	}
-	// Make it fail
+
 	mockBatch.GetEntries()[0].Addendums[0].EntryDetailSequenceNumber = 99
-	if err := mockBatch.Build(); err != nil {
-		if err != ErrBatchAddendaTraceNumber {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "TraceNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
@@ -314,145 +311,109 @@ func TestBatchBuild(t *testing.T) {
 	entry.AddAddenda(a2)
 	mockBatch.AddEntry(entry)
 	if err := mockBatch.Build(); err != nil {
-		t.Errorf("Unexpected Batch.Build error: %v", err.Error())
-	}
-}
-
-func TestBatchValidateAllBH(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	mockBatch.Build()
-
-	// Make it fail
-	mockBatch.GetHeader().ODFIIdentification = 0
-	if err := mockBatch.ValidateAll(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-		}
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
-func TestBatchValidateAllED(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	mockBatch.Build()
-
-	// Make it fail
+func TestBatchValidateAllEntries(t *testing.T) {
+	mockBatch := mockBatchPPD()
 	mockBatch.GetEntries()[0].DFIAccountNumber = ""
 	if err := mockBatch.ValidateAll(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestBatchValidateAllAddenda(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	mockBatch.Build()
+	mockBatch := mockBatchPPD()
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda())
+	if err := mockBatch.Build(); err != nil {
+		t.Errorf("%T: %s", err, err)
+	}
 
-	// Make it fail
 	mockBatch.GetEntries()[0].Addendums[0].TypeCode = ""
 	if err := mockBatch.ValidateAll(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestBatchValidateAllBatchControl(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	mockBatch.Build()
-
-	// Make it fail
+	mockBatch := mockBatchPPD()
 	mockBatch.GetControl().ODFIIdentification = 0
 	if err := mockBatch.ValidateAll(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.ValidationAll error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestBatchBuildHeader(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-
+func TestBatchValidateAllHeader(t *testing.T) {
+	mockBatch := mockBatchPPD()
 	mockBatch.GetHeader().ODFIIdentification = 0
-	if err := mockBatch.Build(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Build error: %v", err.Error())
+	if err := mockBatch.ValidateAll(); err != nil {
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestBatchBuildNoEntries(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	if err := mockBatch.Build(); err != nil {
-		if !strings.Contains(err.Error(), ErrBatchEntries.Error()) {
-			t.Errorf("Unexpected Batch.Build error: %v", err.Error())
+func TestBatchValidateAllODFI(t *testing.T) {
+	mockBatch := mockBatchPPD()
+	mockBatch.GetHeader().ODFIIdentification = 123456
+	if err := mockBatch.ValidateAll(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "ODFIIdentification" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestBatchDNEMismatch(t *testing.T) {
+func TestBatchBuildNoEntries(t *testing.T) {
 	mockBatch := NewBatchPPD()
 	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	mockBatch.Build()
-
-	mockBatch.GetHeader().OriginatorStatusCode = 1
-	mockBatch.GetEntries()[0].TransactionCode = 23
 	if err := mockBatch.Build(); err != nil {
-		if !strings.Contains(err.Error(), ErrBatchOriginatorDNE.Error()) {
-			t.Errorf("Unexpected Batch.validation error: %v", err.Error())
+		if e, ok := err.(*BatchError); ok {
+			if e.Msg != msgBatchEntries {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestBatchAddendaSeq(t *testing.T) {
-	mockBatch := NewBatchPPD()
-	mockBatch.SetHeader(mockBatchHeader())
-	ed := mockEntryDetail()
-	ed.AddAddenda(mockAddenda())
-	ed.AddAddenda(mockAddenda())
-	mockBatch.AddEntry(ed)
-	mockBatch.Build()
-
-	mockBatch.GetEntries()[0].Addendums[0].SequenceNumber = 2
-	mockBatch.GetEntries()[0].Addendums[1].SequenceNumber = 1
-	if err := mockBatch.Build(); err != nil {
-		if !strings.Contains(err.Error(), ErrBatchAddendaSequence.Error()) {
-			t.Errorf("Unexpected Batch.validation error: %v", err.Error())
+func TestBatchBuildHeader(t *testing.T) {
+	mockBatch := mockBatchPPD()
+	mockBatch.GetHeader().CompanyIdentification = ""
+	if err := mockBatch.Build(); err != nil {
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
diff --git a/entryDetail_internal_test.go b/entryDetail_internal_test.go
index 18b54a62..36dfd5b4 100644
--- a/entryDetail_internal_test.go
+++ b/entryDetail_internal_test.go
@@ -20,6 +20,28 @@ func mockEntryDetail() *EntryDetail {
 	return entry
 }
 
+func TestMockEntryDetail(t *testing.T) {
+	entry := mockEntryDetail()
+	if err := entry.Validate(); err != nil {
+		t.Error("mockEntryDetail does not validate and will break other tests")
+	}
+	if entry.TransactionCode != 22 {
+		t.Error("TransactionCode depedendent default value has changed")
+	}
+	if entry.DFIAccountNumber != "123456789" {
+		t.Error("DFIAccountNumber depedendent default value has changed")
+	}
+	if entry.Amount != 100000000 {
+		t.Error("Amount depedendent default value has changed")
+	}
+	if entry.IndividualName != "Wade Arnold" {
+		t.Error("IndividualName depedendent default value has changed")
+	}
+	if entry.TraceNumber != 123456789 {
+		t.Error("TraceNumber depedendent default value has changed")
+	}
+}
+
 // TestParseEntryDetail Header parses a known Entry Detail Record string.
 func TestParseEntryDetail(t *testing.T) {
 	var line = "62705320001912345            0000010500c-1            Arnold Wade           DD0076401255655291"
@@ -27,9 +49,8 @@ func TestParseEntryDetail(t *testing.T) {
 	r.addCurrentBatch(NewBatchPPD())
 	r.currentBatch.SetHeader(mockBatchHeader())
 	r.line = line
-	err := r.parseEntryDetail()
-	if err != nil {
-		t.Errorf("unknown error: %v", err)
+	if err := r.parseEntryDetail(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetEntries()[0]
 
@@ -76,9 +97,8 @@ func TestEDString(t *testing.T) {
 	r.addCurrentBatch(NewBatchPPD())
 	r.currentBatch.SetHeader(mockBatchHeader())
 	r.line = line
-	err := r.parseEntryDetail()
-	if err != nil {
-		t.Errorf("unknown error: %v", err)
+	if err := r.parseEntryDetail(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.currentBatch.GetEntries()[0]
 
@@ -92,8 +112,10 @@ func TestValidateEDRecordType(t *testing.T) {
 	ed := mockEntryDetail()
 	ed.recordType = "2"
 	if err := ed.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrRecordType.Error()) {
-			t.Errorf("Expected RecordType Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -103,104 +125,82 @@ func TestValidateEDTransactionCode(t *testing.T) {
 	ed := mockEntryDetail()
 	ed.TransactionCode = 63
 	if err := ed.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrTransactionCode.Error()) {
-			t.Errorf("Expected Transaction Code Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "TransactionCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestEDFieldInclusion(t *testing.T) {
 	ed := mockEntryDetail()
-	// works properly
-	if err := ed.Validate(); err != nil {
-		t.Errorf("Unexpected Entry Detail error: %v", err.Error())
-	}
-	// create error is mismatch
 	ed.Amount = 0
 	if err := ed.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Entry Detail error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestEDdfiAccountNumberAlphaNumeric(t *testing.T) {
 	ed := mockEntryDetail()
-	// works properly
-	if err := ed.Validate(); err != nil {
-		t.Errorf("Unexpected Entry Detail error: %v", err.Error())
-	}
-	// create error is mismatch
 	ed.DFIAccountNumber = "74647#999!"
 	if err := ed.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Entry Detail error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "DFIAccountNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestEDIndividualIdentificationNumberAlphaNumeric(t *testing.T) {
 	ed := mockEntryDetail()
-	// works properly
-	if err := ed.Validate(); err != nil {
-		t.Errorf("Unexpected Entry Detail error: %v", err.Error())
-	}
-	// create error is mismatch
 	ed.IndividualIdentificationNumber = "#12345!"
 	if err := ed.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Entry Detail error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "IndividualIdentificationNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestEDIndividualNameAlphaNumeric(t *testing.T) {
 	ed := mockEntryDetail()
-	// works properly
-	if err := ed.Validate(); err != nil {
-		t.Errorf("Unexpected Entry Detail error: %v", err.Error())
-	}
-	// create error is mismatch
 	ed.IndividualName = "W@DE"
 	if err := ed.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Entry Detail error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "IndividualName" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestEDDiscretionaryDataAlphaNumeric(t *testing.T) {
 	ed := mockEntryDetail()
-	// works properly
-	if err := ed.Validate(); err != nil {
-		t.Errorf("Unexpected Entry Detail error: %v", err.Error())
-	}
-	// create error is mismatch
 	ed.DiscretionaryData = "@!"
 	if err := ed.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Entry Detail error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "DiscretionaryData" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestEDisCheckDigit(t *testing.T) {
 	ed := mockEntryDetail()
-	// works properly
-	if err := ed.Validate(); err != nil {
-		t.Errorf("Unexpected Entry Detail error: %v", err.Error())
-	}
-	// create error is mismatch
 	ed.CheckDigit = 1
 	if err := ed.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Entry Detail error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "RDFIIdentification" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -209,10 +209,10 @@ func TestEDSetRDFI(t *testing.T) {
 	ed := NewEntryDetail()
 	ed.SetRDFI(81086674)
 	if ed.RDFIIdentification != 8108667 {
-		t.Errorf("RDFI identification")
+		t.Error("RDFI identification")
 	}
 	if ed.CheckDigit != 4 {
-		t.Errorf("Unexpected check digit")
+		t.Error("Unexpected check digit")
 	}
 }
 
@@ -220,9 +220,10 @@ func TestEDFieldInclusionRecordType(t *testing.T) {
 	entry := mockEntryDetail()
 	entry.recordType = ""
 	if err := entry.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -231,9 +232,10 @@ func TestEDFieldInclusionTransactionCode(t *testing.T) {
 	entry := mockEntryDetail()
 	entry.TransactionCode = 0
 	if err := entry.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -242,9 +244,10 @@ func TestEDFieldInclusionRDFIIdentification(t *testing.T) {
 	entry := mockEntryDetail()
 	entry.RDFIIdentification = 0
 	if err := entry.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -253,9 +256,10 @@ func TestEDFieldInclusionCheckDigit(t *testing.T) {
 	entry := mockEntryDetail()
 	entry.CheckDigit = 0
 	if err := entry.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -264,9 +268,10 @@ func TestEDFieldInclusionDFIAccountNumber(t *testing.T) {
 	entry := mockEntryDetail()
 	entry.DFIAccountNumber = ""
 	if err := entry.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -275,9 +280,10 @@ func TestEDFieldInclusionIndividualName(t *testing.T) {
 	entry := mockEntryDetail()
 	entry.IndividualName = ""
 	if err := entry.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -286,9 +292,10 @@ func TestEDFieldInclusionTraceNumber(t *testing.T) {
 	entry := mockEntryDetail()
 	entry.TraceNumber = 0
 	if err := entry.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
diff --git a/fileControl_internal_test.go b/fileControl_internal_test.go
index f6309ff4..7f095b92 100644
--- a/fileControl_internal_test.go
+++ b/fileControl_internal_test.go
@@ -18,6 +18,25 @@ func mockFileControl() FileControl {
 	return fc
 }
 
+func TestMockFileControl(t *testing.T) {
+	fc := mockFileControl()
+	if err := fc.Validate(); err != nil {
+		t.Error("mockFileControl does not validate and will break other tests")
+	}
+	if fc.BatchCount != 1 {
+		t.Error("BatchCount depedendent default value has changed")
+	}
+	if fc.BlockCount != 1 {
+		t.Error("BlockCount depedendent default value has changed")
+	}
+	if fc.EntryAddendaCount != 1 {
+		t.Error("EntryAddendaCount depedendent default value has changed")
+	}
+	if fc.EntryHash != 5320001 {
+		t.Error("EntryHash depedendent default value has changed")
+	}
+}
+
 // TestParseFileControl parses a known File Control Record string.
 func TestParseFileControl(t *testing.T) {
 	var line = "9000001000001000000010005320001000000010500000000000000                                       "
@@ -25,7 +44,7 @@ func TestParseFileControl(t *testing.T) {
 	r.line = line
 	err := r.parseFileControl()
 	if err != nil {
-		t.Errorf("unknown error: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.File.Control
 
@@ -62,7 +81,7 @@ func TestFCString(t *testing.T) {
 	r.line = line
 	err := r.parseFileControl()
 	if err != nil {
-		t.Errorf("unknown error: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.File.Control
 	if record.String() != line {
@@ -76,24 +95,22 @@ func TestValidateFCRecordType(t *testing.T) {
 	fc.recordType = "2"
 
 	if err := fc.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrRecordType.Error()) {
-			t.Errorf("Expected RecordType Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestFCFieldInclusion(t *testing.T) {
 	fc := mockFileControl()
-	// works properly
-	if err := fc.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	fc.BatchCount = 0
 	if err := fc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -102,9 +119,10 @@ func TestFCFieldInclusionRecordType(t *testing.T) {
 	fc := mockFileControl()
 	fc.recordType = ""
 	if err := fc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -113,9 +131,10 @@ func TestFCFieldInclusionBlockCount(t *testing.T) {
 	fc := mockFileControl()
 	fc.BlockCount = 0
 	if err := fc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -124,9 +143,10 @@ func TestFCFieldInclusionEntryAddendaCount(t *testing.T) {
 	fc := mockFileControl()
 	fc.EntryAddendaCount = 0
 	if err := fc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -135,9 +155,10 @@ func TestFCFieldInclusionEntryHash(t *testing.T) {
 	fc := mockFileControl()
 	fc.EntryHash = 0
 	if err := fc.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
diff --git a/fileHeader_internal_test.go b/fileHeader_internal_test.go
index 096d27ff..4346812b 100644
--- a/fileHeader_internal_test.go
+++ b/fileHeader_internal_test.go
@@ -21,13 +21,32 @@ func mockFileHeader() FileHeader {
 	return fh
 }
 
+func TestMockFileHeader(t *testing.T) {
+	fh := mockFileHeader()
+	if err := fh.Validate(); err != nil {
+		t.Error("mockFileHeader does not validate and will break other tests")
+	}
+	if fh.ImmediateDestination != 9876543210 {
+		t.Error("ImmediateDestination depedendent default value has changed")
+	}
+	if fh.ImmediateOrigin != 1234567890 {
+		t.Error("ImmediateOrigin depedendent default value has changed")
+	}
+	if fh.ImmediateDestinationName != "Federal Reserve Bank" {
+		t.Error("ImmediateDestinationName depedendent default value has changed")
+	}
+	if fh.ImmediateOriginName != "My Bank Name" {
+		t.Error("ImmediateOriginName depedendent default value has changed")
+	}
+}
+
 // TestParseFileHeader parses a known File Header Record string.
 func TestParseFileHeader(t *testing.T) {
 	var line = "101 076401251 0764012510807291511A094101achdestname            companyname                    "
 	r := NewReader(strings.NewReader(line))
 	r.line = line
 	if err := r.parseFileHeader(); err != nil {
-		t.Errorf("unknown error: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.File.Header
 
@@ -79,9 +98,8 @@ func TestFHString(t *testing.T) {
 	var line = "101 076401251 0764012510807291511A094101achdestname            companyname                    "
 	r := NewReader(strings.NewReader(line))
 	r.line = line
-	err := r.parseFileHeader()
-	if err != nil {
-		t.Errorf("unknown error: %v", err)
+	if err := r.parseFileHeader(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	record := r.File.Header
 
@@ -95,8 +113,10 @@ func TestValidateFHRecordType(t *testing.T) {
 	fh := mockFileHeader()
 	fh.recordType = "2"
 	if err := fh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrRecordType.Error()) {
-			t.Errorf("Expected RecordType Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -104,10 +124,12 @@ func TestValidateFHRecordType(t *testing.T) {
 // TestValidateIDModifier ensure ID Modiier is upper alphanumeric
 func TestValidateIDModifier(t *testing.T) {
 	fh := mockFileHeader()
-	fh.FileIDModifier = "A"
+	fh.FileIDModifier = "a"
 	if err := fh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrValidFieldLength.Error()) {
-			t.Errorf("Expected ID Modifier Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "FileIDModifier" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -117,8 +139,10 @@ func TestValidateRecordSize(t *testing.T) {
 	fh := mockFileHeader()
 	fh.recordSize = "666"
 	if err := fh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrRecordSize.Error()) {
-			t.Errorf("Expected Record Size Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordSize" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -128,8 +152,10 @@ func TestBlockingFactor(t *testing.T) {
 	fh := mockFileHeader()
 	fh.blockingFactor = "99"
 	if err := fh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrBlockingFactor.Error()) {
-			t.Errorf("Expected Blocking Factor Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "blockingFactor" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -139,100 +165,82 @@ func TestFormatCode(t *testing.T) {
 	fh := mockFileHeader()
 	fh.formatCode = "2"
 	if err := fh.Validate(); err != nil {
-		if !strings.Contains(err.Error(), ErrFormatCode.Error()) {
-			t.Errorf("Expected Format Code Error got: %v", err)
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "formatCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestFHFieldInculsion(t *testing.T) {
 	fh := mockFileHeader()
-	// works properly
-	if err := fh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	fh.ImmediateOrigin = 0
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestUpperLengthFileID(t *testing.T) {
 	fh := mockFileHeader()
-	// works properly
-	if err := fh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	fh.FileIDModifier = "a"
-
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "FileIDModifier" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 
 	fh.FileIDModifier = "AA"
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "FileIDModifier" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestImmediateDestinationNameAlphaNumeric(t *testing.T) {
 	fh := mockFileHeader()
-	// works properly
 	fh.ImmediateDestinationName = "Super Big Bank"
-	if err := fh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	fh.ImmediateDestinationName = "Big @$$ Bank"
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "ImmediateDestinationName" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestImmediateOriginNameAlphaNumeric(t *testing.T) {
 	fh := mockFileHeader()
-	// works properly
 	fh.ImmediateOriginName = "Super Big Bank"
+	fh.ImmediateOriginName = "Bigger @$$ Bank"
 	if err := fh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
-	fh.ImmediateOriginName = "Big @$$ Bank"
-	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "ImmediateOriginName" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
 
 func TestImmediateReferenceCodeAlphaNumeric(t *testing.T) {
 	fh := mockFileHeader()
-	// works properly
 	fh.ReferenceCode = " "
-	if err := fh.Validate(); err != nil {
-		t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
-	}
-	// create error is mismatch
 	fh.ReferenceCode = "!@#$%^"
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "ReferenceCode" {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -241,9 +249,10 @@ func TestFHFieldInclusionRecordType(t *testing.T) {
 	fh := mockFileHeader()
 	fh.recordType = ""
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -252,9 +261,10 @@ func TestFHFieldInclusionImmediatDestination(t *testing.T) {
 	fh := mockFileHeader()
 	fh.ImmediateDestination = 0
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -263,9 +273,10 @@ func TestFHFieldInclusionFileIDModifier(t *testing.T) {
 	fh := mockFileHeader()
 	fh.FileIDModifier = ""
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -274,9 +285,10 @@ func TestFHFieldInclusionRecordSize(t *testing.T) {
 	fh := mockFileHeader()
 	fh.recordSize = ""
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -285,9 +297,10 @@ func TestFHFieldInclusionBlockingFactor(t *testing.T) {
 	fh := mockFileHeader()
 	fh.blockingFactor = ""
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
@@ -296,9 +309,10 @@ func TestFHFieldInclusionFormatCode(t *testing.T) {
 	fh := mockFileHeader()
 	fh.formatCode = ""
 	if err := fh.Validate(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected Batch.Validation error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
 		}
 	}
 }
diff --git a/file_test.go b/file_test.go
index 5cacc919..fe75161b 100644
--- a/file_test.go
+++ b/file_test.go
@@ -5,133 +5,104 @@
 package ach
 
 import (
-	"strings"
 	"testing"
 )
 
-// TestBatchCountError tests for to many batch counts
-func TestBatchCountError(t *testing.T) {
-	r := NewReader(strings.NewReader(" "))
-	r.File.AddBatch(NewBatchPPD())
-	r.File.Control.BatchCount = 1
-	if err := r.File.Validate(); err != nil {
-		t.Errorf("Unexpected File.Validation error: %v", err.Error())
+func mockFilePPD() *File {
+	mockFile := NewFile()
+	mockFile.SetHeader(mockFileHeader())
+	mockBatch := mockBatchPPD()
+	mockFile.AddBatch(mockBatch)
+	if err := mockFile.Build(); err != nil {
+		panic(err)
 	}
+	return mockFile
+}
+
+func TestFileError(t *testing.T) {
+	err := &FileError{FieldName: "mock", Msg: "test message"}
+	if err.Error() != "mock test message" {
+		t.Error("FileError Error has changed formatting")
+	}
+}
+
+// TestFileBatchCount if calculated count is different from control
+func TestFileBatchCount(t *testing.T) {
+	file := mockFilePPD()
+
 	// More batches than the file control count.
-	r.File.AddBatch(NewBatchPPD())
-	if err := r.File.Validate(); err != nil {
-		if err != ErrFileBatchCount {
-			t.Errorf("Unexpected File.Validation error: %v", err.Error())
+	file.AddBatch(mockBatchPPD())
+	if err := file.Validate(); err != nil {
+		if e, ok := err.(*FileError); ok {
+			if e.FieldName != "BatchCount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
-func TestFileEntryAddendaError(t *testing.T) {
-	r := NewReader(strings.NewReader(" "))
-	mockBatch := NewBatchPPD()
-	mockBatch.GetControl().EntryAddendaCount = 1
-	r.File.AddBatch(mockBatch)
-	r.File.Control.BatchCount = 1
-	r.File.Control.EntryAddendaCount = 1
-	if err := r.File.Validate(); err != nil {
-		t.Errorf("Unexpected File.Validation error: %v", err.Error())
-	}
+func TestFileEntryAddenda(t *testing.T) {
+	file := mockFilePPD()
 
 	// more entries than the file control
-	r.File.Control.EntryAddendaCount = 5
-	if err := r.File.Validate(); err != nil {
-		if err != ErrFileEntryCount {
-			t.Errorf("Unexpected File.Validation error: %v", err.Error())
+	file.Control.EntryAddendaCount = 5
+	if err := file.Validate(); err != nil {
+		if e, ok := err.(*FileError); ok {
+			if e.FieldName != "EntryAddendaCount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestFileDebitAmount(t *testing.T) {
-
-	r := NewReader(strings.NewReader(" "))
-	mockBatch := NewBatchPPD()
-	bc := BatchControl{
-		EntryAddendaCount:           1,
-		TotalDebitEntryDollarAmount: 105000,
-	}
-	mockBatch.SetControl(&bc)
-
-	r.File.AddBatch(mockBatch)
-	r.File.Control.BatchCount = 1
-	r.File.Control.EntryAddendaCount = 1
-	r.File.Control.TotalDebitEntryDollarAmountInFile = 105000
-
-	if err := r.File.Validate(); err != nil {
-		t.Errorf("Unexpected File.Validation error: %v", err.Error())
-	}
-
-	r.File.Control.TotalDebitEntryDollarAmountInFile = 0
-	if err := r.File.Validate(); err != nil {
-		if err != ErrFileDebitAmount {
-			t.Errorf("Unexpected File.Validation error: %v", err.Error())
+	file := mockFilePPD()
+
+	// inequality in total debit amount
+	file.Control.TotalDebitEntryDollarAmountInFile = 63
+	if err := file.Validate(); err != nil {
+		if e, ok := err.(*FileError); ok {
+			if e.FieldName != "TotalDebitEntryDollarAmountInFile" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestFileCreditAmount(t *testing.T) {
-	r := NewReader(strings.NewReader(" "))
-	mockBatch := NewBatchPPD()
-	bc := BatchControl{
-		EntryAddendaCount:            1,
-		TotalCreditEntryDollarAmount: 10500,
-	}
-	mockBatch.SetControl(&bc)
-
-	r.File.AddBatch(mockBatch)
-	r.File.Control.BatchCount = 1
-	r.File.Control.EntryAddendaCount = 1
-	r.File.Control.TotalCreditEntryDollarAmountInFile = 10500
-
-	if err := r.File.Validate(); err != nil {
-		t.Errorf("Unexpected File.Validation error: %v", err.Error())
-	}
-
-	r.File.Control.TotalCreditEntryDollarAmountInFile = 0
-	if err := r.File.Validate(); err != nil {
-		if err != ErrFileCreditAmount {
-			t.Errorf("Unexpected File.Validation error: %v", err.Error())
+	file := mockFilePPD()
+
+	// inequality in total debit amount
+	file.Control.TotalCreditEntryDollarAmountInFile = 63
+	if err := file.Validate(); err != nil {
+		if e, ok := err.(*FileError); ok {
+			if e.FieldName != "TotalCreditEntryDollarAmountInFile" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestFileEntryHash(t *testing.T) {
-	r := NewReader(strings.NewReader(" "))
-	mockBatch1 := NewBatchPPD()
-	bc := BatchControl{
-		EntryAddendaCount:            1,
-		TotalCreditEntryDollarAmount: 10500,
-		EntryHash:                    1212121212,
-	}
-	mockBatch1.SetControl(&bc)
-
-	mockBatch2 := NewBatchPPD()
-	bc2 := BatchControl{
-		EntryAddendaCount:            1,
-		TotalCreditEntryDollarAmount: 10500,
-		EntryHash:                    2121212121,
-	}
-	mockBatch2.SetControl(&bc2)
-
-	r.File.AddBatch(mockBatch1)
-	r.File.AddBatch(mockBatch2)
-
-	r.File.Control.BatchCount = 2
-	r.File.Control.EntryAddendaCount = 2
-	r.File.Control.TotalCreditEntryDollarAmountInFile = 21000
-	r.File.Control.EntryHash = 3333333333
-	if err := r.File.Validate(); err != nil {
-		t.Errorf("Unexpected File.Validation error: %v", err.Error())
-	}
-
-	r.File.Control.EntryHash = 0
-	if err := r.File.Validate(); err != nil {
-		if err != ErrFileEntryHash {
-			t.Errorf("Unexpected File.Validation error: %v", err.Error())
+	file := mockFilePPD()
+	file.AddBatch(mockBatchPPD())
+	file.Build()
+	file.Control.EntryHash = 63
+	if err := file.Validate(); err != nil {
+		if e, ok := err.(*FileError); ok {
+			if e.FieldName != "EntryHash" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
@@ -148,21 +119,34 @@ func TestFileBlockCount10(t *testing.T) {
 	batch.AddEntry(mockEntryDetail())
 	batch.Build()
 	file.AddBatch(batch)
+	if err := file.Build(); err != nil {
+		t.Errorf("%T: %s", err, err)
+	}
 
 	// ensure with 10 records in file we don't get 2 for a block count
+	if file.Control.BlockCount != 1 {
+		t.Error("BlockCount on 10 records is not equal to 1")
+	}
+	// make 11th record which should produce BlockCount of 2
+	file.Batches[0].AddEntry(mockEntryDetail())
+	file.Batches[0].Build() // File.Build does not re-build Batches
 	if err := file.Build(); err != nil {
-		t.Errorf("Unexpected File.Validation error: %v", err.Error())
+		t.Errorf("%T: %s", err, err)
 	}
-	if file.Control.BlockCount != 1 {
-		t.Errorf("Unexpected block count in file expect 1 got: %v", file.Control.BlockCount)
+	if file.Control.BlockCount != 2 {
+		t.Error("BlockCount on 11 records is not equal to 2")
 	}
 }
 
 func TestFileBuildBadFileHeader(t *testing.T) {
 	file := NewFile().SetHeader(FileHeader{})
 	if err := file.Build(); err != nil {
-		if !strings.Contains(err.Error(), ErrRecordType.Error()) {
-			t.Errorf("Unexpected File.Build error: %v", err.Error())
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
@@ -170,59 +154,57 @@ func TestFileBuildBadFileHeader(t *testing.T) {
 func TestFileBuildNoBatch(t *testing.T) {
 	file := NewFile().SetHeader(mockFileHeader())
 	if err := file.Build(); err != nil {
-		if !strings.Contains(err.Error(), ErrFileBatches.Error()) {
-			t.Errorf("Unexpected File.Build error: %v", err.Error())
+		if e, ok := err.(*FileError); ok {
+			if e.FieldName != "Batchs" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestFileValidateAllBatch(t *testing.T) {
-	file := NewFile().SetHeader(mockFileHeader())
-	batch := NewBatchPPD()
-	batch.SetHeader(mockBatchHeader())
-	batch.AddEntry(mockEntryDetail())
-	batch.Build()
-	file.AddBatch(batch)
+	file := mockFilePPD()
 	// break the file header
 	file.Batches[0].GetHeader().ODFIIdentification = 0
-	if err := file.Build(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected File.ValidationAll error: %v", err.Error())
+	if err := file.ValidateAll(); err != nil {
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestFileValidateAllFileHeader(t *testing.T) {
-	file := NewFile().SetHeader(mockFileHeader())
-	batch := NewBatchPPD()
-	batch.SetHeader(mockBatchHeader())
-	batch.AddEntry(mockEntryDetail())
-	batch.Build()
-	file.AddBatch(batch)
+	file := mockFilePPD()
 	// break the file header
 	file.Header.ImmediateOrigin = 0
-	if err := file.Build(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected File.ValidationAll error: %v", err.Error())
+	if err := file.ValidateAll(); err != nil {
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
 
 func TestFileValidateAllFileControl(t *testing.T) {
-	file := NewFile().SetHeader(mockFileHeader())
-	batch := NewBatchPPD()
-	batch.SetHeader(mockBatchHeader())
-	batch.AddEntry(mockEntryDetail())
-	batch.Build()
-	file.AddBatch(batch)
+	file := mockFilePPD()
 	// break the file header
 	file.Control.BatchCount = 0
-	if err := file.Build(); err != nil {
-		_, ok := err.(*ValidateError)
-		if !ok {
-			t.Errorf("Unexpected File.ValidationAll error: %v", err.Error())
+	if err := file.ValidateAll(); err != nil {
+		if e, ok := err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
 		}
 	}
 }
diff --git a/reader_test.go b/reader_test.go
index 4fb970fd..f6b8bbb7 100644
--- a/reader_test.go
+++ b/reader_test.go
@@ -10,58 +10,61 @@ import (
 	"testing"
 )
 
-// TestDecode is a complete file decoding test.
+func TestParseError(t *testing.T) {
+	e := &FieldError{FieldName: "testField", Value: "nil", Msg: "could not parse"}
+	err := &ParseError{Line: 63, Err: e}
+	if err.Error() != "line:63 *ach.FieldError testField nil could not parse" {
+		t.Error("ParseError error string formating has changed")
+	}
+	err.Record = "TestRecord"
+	if err.Error() != "line:63 record:TestRecord *ach.FieldError testField nil could not parse" {
+		t.Error("ParseError error string formating has changed")
+	}
+}
+
+// TestDecode is a complete file decoding test. A canary test
 func TestPPDDebitRead(t *testing.T) {
 	f, err := os.Open("./testdata/ppd-debit.ach")
 	if err != nil {
-		t.Errorf("%s: ", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	defer f.Close()
 	r := NewReader(f)
 	_, err = r.Read()
 	if err != nil {
-		t.Errorf("Can not ach.read file: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
-	err = r.File.ValidateAll()
-	if err != nil {
-		t.Errorf("Could not validate entire read file: %v", err)
+	if err = r.File.ValidateAll(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
-// TestDecode is a complete file decoding test.
+// TestDecode is a complete file decoding test. A canary test
 func TestPPDDebitFiexedLengthRead(t *testing.T) {
 	f, err := os.Open("./testdata/ppd-debit-fixedLength.ach")
 	if err != nil {
-		t.Errorf("%s: ", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	defer f.Close()
 	r := NewReader(f)
 	_, err = r.Read()
 	if err != nil {
-		t.Errorf("Can not ach.read file: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
-/*
-func TestMultiBatchFile(t *testing.T) {
-	f, err := os.Open("./testdata/20110805A.ach")
-	if err != nil {
-		t.Errorf("%s: ", err)
-	}
-	defer f.Close()
-	r := NewReader(f)
-	_, err = r.Read()
-	if err != nil {
-		t.Errorf("Can not ach.read file: %v", err)
-	}
-}
-*/
 func TestRecordTypeUnknown(t *testing.T) {
 	var line = "301 076401251 0764012510807291511A094101achdestname            companyname                    "
 	r := NewReader(strings.NewReader(line))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrUnknownRecordType.Error()) {
-		t.Errorf("Expected RecordType Error got: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FileError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", e, e)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
 	}
 }
 
@@ -70,9 +73,14 @@ func TestTwoFileHeaders(t *testing.T) {
 	var twoHeaders = line + "\n" + line
 	r := NewReader(strings.NewReader(twoHeaders))
 	_, err := r.Read()
-
-	if !strings.Contains(err.Error(), ErrFileHeader.Error()) {
-		t.Errorf("Expected File Header Error got: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FileError); ok {
+			if e.Msg != msgFileControl {
+				t.Errorf("%T: %s", e, e)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
 	}
 }
 
@@ -89,27 +97,44 @@ func TestTwoFileControls(t *testing.T) {
 	r.File.AddBatch(r.currentBatch)
 	r.File.Control.EntryHash = 5320001
 	_, err := r.Read()
-
-	if !strings.Contains(err.Error(), ErrFileControl.Error()) {
-		t.Errorf("Expected File Control Error got: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FileError); ok {
+			if e.Msg != msgFileControl {
+				t.Errorf("%T: %s", e, e)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
 	}
 }
 
 func TestFileLineShort(t *testing.T) {
-	var line = "1 line is only 90 characters                                               !"
+	var line = "1 line is only 70 characters ........................................!"
 	r := NewReader(strings.NewReader(line))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrRecordLen.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FileError); ok {
+			if e.FieldName != "RecordLength" {
+				t.Errorf("%T: %s", e, e)
+			}
+		} else {
+			t.Errorf("%T: %s", e, e)
+		}
 	}
 }
 
 func TestFileLineLong(t *testing.T) {
-	var line = "1 line is only 100 characters                                                        !"
+	var line = "1 line is 100 characters ..........................................................................!"
 	r := NewReader(strings.NewReader(line))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrRecordLen.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FileError); ok {
+			if e.FieldName != "RecordLength" {
+				t.Errorf("%T: %s", e, e)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
 	}
 }
 
@@ -121,8 +146,14 @@ func TestFileFileHeaderErr(t *testing.T) {
 	// necessary to have a file control not nil
 	r.File.Control = mockFileControl()
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrValidFieldInclusion.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
@@ -132,8 +163,14 @@ func TestFileBatchHeaderErr(t *testing.T) {
 	bh.ODFIIdentification = 0
 	r := NewReader(strings.NewReader(bh.String()))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrValidFieldInclusion.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
@@ -146,8 +183,14 @@ func TestFileBatchHeaderDuplicate(t *testing.T) {
 	r.addCurrentBatch(NewBatchPPD())
 	// read should fail because it is parsing a second batch header and there can only be one.
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), "BatchHeader") {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
@@ -156,8 +199,14 @@ func TestFileEntryDetailOutsideBatch(t *testing.T) {
 	ed := mockEntryDetail()
 	r := NewReader(strings.NewReader(ed.String()))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrEntryOutside.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FileError); ok {
+			if e.Msg != msgFileBatchOutside {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
@@ -170,13 +219,19 @@ func TestFileEntryDetail(t *testing.T) {
 	r.addCurrentBatch(NewBatchPPD())
 	r.currentBatch.SetHeader(mockBatchHeader())
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrValidFieldInclusion.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
-// TestFileEntryDetailNotPPD validation error populates through the reader
-func TestFileEntryDetailNotPPD(t *testing.T) {
+// TestFileEntryDetailSECNoSupport validation error populates through the reader
+func TestFileEntryDetailSECNoSupport(t *testing.T) {
 	ed := mockEntryDetail()
 	ed.CheckDigit = 0
 	line := ed.String()
@@ -185,8 +240,14 @@ func TestFileEntryDetailNotPPD(t *testing.T) {
 	r.currentBatch.SetHeader(mockBatchHeader())
 	r.currentBatch.GetHeader().StandardEntryClassCode = "ABCXYZ"
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), "ABCXYZ") {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FieldError); ok {
+			if e.FieldName != "StandardEntryClassCode" {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
@@ -200,8 +261,16 @@ func TestFileAddenda(t *testing.T) {
 	line := bh.String() + "\n" + ed.String() + "\n" + ed.Addendums[0].String()
 	r := NewReader(strings.NewReader(line))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), "Addenda") {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if err != nil {
+		if p, ok := err.(*ParseError); ok {
+			if e, ok := p.Err.(*FieldError); ok {
+				if e.Msg != msgFieldInclusion {
+					t.Errorf("%T: %s", e, e)
+				}
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
 	}
 }
 
@@ -210,8 +279,16 @@ func TestFileAddendaOutsideBatch(t *testing.T) {
 	addenda := mockAddenda()
 	r := NewReader(strings.NewReader(addenda.String()))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrAddendaOutside.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if err != nil {
+		if p, ok := err.(*ParseError); ok {
+			if e, ok := p.Err.(*FileError); ok {
+				if e.Msg != msgFileBatchOutside {
+					t.Errorf("%T: %s", e, e)
+				}
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
 	}
 }
 
@@ -223,19 +300,30 @@ func TestFileAddendaNoIndicator(t *testing.T) {
 	line := bh.String() + "\n" + ed.String() + "\n" + addenda.String()
 	r := NewReader(strings.NewReader(line))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrAddendaNoIndicator.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FileError); ok {
+			if e.FieldName != "AddendaRecordIndicator" {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
 func TestFileFileControlErr(t *testing.T) {
-
 	fc := mockFileControl()
 	fc.BatchCount = 0
 	r := NewReader(strings.NewReader(fc.String()))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrValidFieldInclusion.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if p, ok := err.(*ParseError); ok {
+		if e, ok := p.Err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", e, e)
+			}
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
 	}
 }
 
@@ -244,7 +332,11 @@ func TestFileLongErr(t *testing.T) {
 	line := "101 076401251 0764012510807291511A094101achdestname            companyname                    5000companyname                         origid    PPDCHECKPAYMT000002080730   1076401250000001"
 	r := NewReader(strings.NewReader(line))
 	_, err := r.Read()
-	if !strings.Contains(err.Error(), ErrServiceClass.Error()) {
-		t.Errorf("Unexpected read.Read() error: %v", err)
+	if e, ok := err.(*ParseError); ok {
+		if e, ok := e.Err.(*FieldError); ok {
+			if e.Msg != msgFieldInclusion {
+				t.Errorf("%T: %s", err, err)
+			}
+		}
 	}
 }
diff --git a/writer_test.go b/writer_test.go
index f7fedd81..857c3949 100644
--- a/writer_test.go
+++ b/writer_test.go
@@ -15,28 +15,26 @@ func TestPPDWrite(t *testing.T) {
 	batch.AddEntry(entry)
 	batch.Build()
 	file.AddBatch(batch)
-	file.Build()
+
 	if err := file.Build(); err != nil {
-		t.Errorf("Could not build file: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 	if err := file.ValidateAll(); err != nil {
-		t.Errorf("Could not validate built file: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
 
 	b := &bytes.Buffer{}
 	f := NewWriter(b)
 
-	err := f.WriteAll([]*File{file})
-	if err != nil {
-		t.Errorf("Unexpected error: %s\n", err)
+	if err := f.WriteAll([]*File{file}); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 	r := NewReader(strings.NewReader(b.String()))
-	_, err = r.Read()
+	_, err := r.Read()
 	if err != nil {
-		t.Errorf("Can not ach.Read generated file: %v", err)
+		t.Errorf("%T: %s", err, err)
 	}
-	err = r.File.ValidateAll()
-	if err != nil {
-		t.Errorf("Could not validate entire generated file: %v", err)
+	if err = r.File.ValidateAll(); err != nil {
+		t.Errorf("%T: %s", err, err)
 	}
 }