Make the OFX spec version an 'enum'

This commit is contained in:
Aaron Lindsay 2017-04-17 10:54:20 -04:00
parent 94f49640b4
commit 0eba6741f2
14 changed files with 181 additions and 43 deletions

View File

@ -45,7 +45,7 @@ func TestMarshalBankStatementRequest(t *testing.T) {
var client = ofxgo.Client{
AppID: "OFXGO",
AppVer: "0001",
SpecVersion: "203",
SpecVersion: ofxgo.OfxVersion203,
}
var request ofxgo.Request
@ -119,7 +119,7 @@ NEWFILEUID:NONE
var client = ofxgo.Client{
AppID: "OFXGO",
AppVer: "0001",
SpecVersion: "103",
SpecVersion: ofxgo.OfxVersion103,
}
var request ofxgo.Request
@ -213,7 +213,7 @@ func TestUnmarshalBankStatementResponse(t *testing.T) {
</OFX>`)
var expected ofxgo.Response
expected.Version = "203"
expected.Version = ofxgo.OfxVersion203
expected.Signon.Status.Code = 0
expected.Signon.Status.Severity = "INFO"
expected.Signon.DtServer = *ofxgo.NewDateGMT(2006, 1, 15, 11, 23, 03, 0)

View File

@ -15,9 +15,9 @@ import (
type Client struct {
// Request fields to overwrite with the client's values. If nonempty,
// defaults are used
SpecVersion string // VERSION in header
AppID string // SONRQ>APPID
AppVer string // SONRQ>APPVER
SpecVersion ofxVersion // VERSION in header
AppID string // SONRQ>APPID
AppVer string // SONRQ>APPVER
// Don't insert newlines or indentation when marshalling to SGML/XML
NoIndent bool
@ -25,14 +25,13 @@ type Client struct {
var defaultClient Client
// OfxVersion returns a string representation of the OFX specification version
// this Client will marshal Requests as. Defaults to "203" if the client's
// SpecVersion field is empty.
func (c *Client) OfxVersion() string {
if len(c.SpecVersion) > 0 {
// OfxVersion returns the OFX specification version this Client will marshal
// Requests as. Defaults to "203" if the client's SpecVersion field is empty.
func (c *Client) OfxVersion() ofxVersion {
if c.SpecVersion.Valid() {
return c.SpecVersion
}
return "203"
return OfxVersion203
}
// ID returns this Client's OFX AppID field, defaulting to "OFXGO" if

View File

@ -121,10 +121,15 @@ func detectSettings() {
const anonymous = "anonymous00000000000000000000000"
func tryProfile(appID, appVer, version string, noindent bool) bool {
ver, err := ofxgo.NewOfxVersion(version)
if err != nil {
fmt.Println("Error creating new OfxVersion enum:", err)
os.Exit(1)
}
var client = ofxgo.Client{
AppID: appID,
AppVer: appVer,
SpecVersion: version,
SpecVersion: ver,
NoIndent: noindent,
}

View File

@ -1,14 +1,21 @@
package main
import (
"fmt"
"github.com/aclindsa/ofxgo"
"os"
)
func newRequest() (*ofxgo.Client, *ofxgo.Request) {
ver, err := ofxgo.NewOfxVersion(ofxVersion)
if err != nil {
fmt.Println("Error creating new OfxVersion enum:", err)
os.Exit(1)
}
var client = ofxgo.Client{
AppID: appID,
AppVer: appVer,
SpecVersion: ofxVersion,
SpecVersion: ver,
NoIndent: noIndentRequests,
}

View File

@ -13,6 +13,80 @@ import (
"strings"
)
type ofxVersion uint
// OfxVersion* constants represent the OFX specification version in use
const (
OfxVersion102 ofxVersion = 1 + iota
OfxVersion103
OfxVersion151
OfxVersion160
OfxVersion200
OfxVersion201
OfxVersion202
OfxVersion203
OfxVersion210
OfxVersion211
OfxVersion220
)
var ofxVersions = [...]string{"102", "103", "151", "160", "200", "201", "202", "203", "210", "211", "220"}
func (e ofxVersion) Valid() bool {
// This check is mostly out of paranoia, ensuring e != 0 should be
// sufficient
return e >= OfxVersion102 && e <= OfxVersion220
}
func (e ofxVersion) String() string {
if e.Valid() {
return ofxVersions[e-1]
}
return fmt.Sprintf("invalid ofxVersion (%d)", e)
}
func (e *ofxVersion) FromString(in string) error {
value := strings.TrimSpace(in)
for i, s := range ofxVersions {
if s == value {
*e = ofxVersion(i + 1)
return nil
}
}
*e = 0
return errors.New("Invalid OfxVersion: \"" + in + "\"")
}
func (e *ofxVersion) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value string
err := d.DecodeElement(&value, &start)
if err != nil {
return err
}
return e.FromString(value)
}
func (e *ofxVersion) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(ofxVersions[*e-1], start)
return nil
}
// NewOfxVersion returns returns an 'enum' value of type ofxVersion given its
// string representation
func NewOfxVersion(s string) (ofxVersion, error) {
var e ofxVersion
err := e.FromString(s)
if err != nil {
return 0, err
}
return e, nil
}
type acctType uint
// AcctType* constants represent types of bank accounts

View File

@ -13,6 +13,51 @@ import (
"testing"
)
func TestOfxVersion(t *testing.T) {
e, err := ofxgo.NewOfxVersion("102")
if err != nil {
t.Fatalf("Unexpected error creating new OfxVersion from string \"102\"\n")
}
if !e.Valid() {
t.Fatalf("OfxVersion unexpectedly invalid\n")
}
err = e.FromString("220")
if err != nil {
t.Fatalf("Unexpected error on OfxVersion.FromString(\"220\")\n")
}
if e.String() != "220" {
t.Fatalf("OfxVersion.String() expected to be \"220\"\n")
}
marshalHelper(t, "220", &e)
overwritten, err := ofxgo.NewOfxVersion("THISWILLNEVERBEAVALIDENUMSTRING")
if err == nil {
t.Fatalf("Expected error creating new OfxVersion from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
}
if overwritten.Valid() {
t.Fatalf("OfxVersion created with string \"THISWILLNEVERBEAVALIDENUMSTRING\" should not be valid\n")
}
if !strings.Contains(strings.ToLower(overwritten.String()), "invalid") {
t.Fatalf("OfxVersion created with string \"THISWILLNEVERBEAVALIDENUMSTRING\" should not return valid string from String()\n")
}
b, err := xml.Marshal(&overwritten)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(OfxVersion): %s\n", err)
}
if string(b) != "" {
t.Fatalf("Expected empty string, got '%s'\n", string(b))
}
unmarshalHelper(t, "220", &e, &overwritten)
err = xml.Unmarshal([]byte("<GARBAGE><!LALDK>"), &overwritten)
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
}
func TestAcctType(t *testing.T) {
e, err := ofxgo.NewAcctType("CHECKING")
if err != nil {

View File

@ -44,7 +44,7 @@ func TestMarshalCCStatementRequest(t *testing.T) {
var client = ofxgo.Client{
AppID: "OFXGO",
AppVer: "0001",
SpecVersion: "203",
SpecVersion: ofxgo.OfxVersion203,
}
var request ofxgo.Request
@ -86,7 +86,7 @@ NEWFILEUID:NONE
EDT := time.FixedZone("EDT", -4*60*60)
EST := time.FixedZone("EST", -5*60*60)
expected.Version = "102"
expected.Version = ofxgo.OfxVersion102
expected.Signon.Status.Code = 0
expected.Signon.Status.Severity = "INFO"
expected.Signon.Status.Message = "SUCCESS"

View File

@ -1,6 +1,9 @@
#!/usr/bin/env python
enums = {
# OFX spec version
"OfxVersion": (["102", "103", "151", "160", "200", "201", "202", "203", "210", "211", "220"], "the OFX specification version in use"),
# Bank/general
"AcctType": (["Checking", "Savings", "MoneyMrkt", "CreditLine", "CD"], "types of bank accounts"),
"TrnType": (["Credit", "Debit", "Int", "Div", "Fee", "SrvChg", "Dep", "ATM", "POS", "Xfer", "Check", "Payment", "Cash", "DirectDep", "DirectDebit", "RepeatPmt", "Hold", "Other"], "types of transactions. INT, ATM, and POS depend on the signage of the account."),

View File

@ -52,7 +52,7 @@ func TestMarshalInvStatementRequest(t *testing.T) {
var client = ofxgo.Client{
AppID: "MYAPP",
AppVer: "1234",
SpecVersion: "203",
SpecVersion: ofxgo.OfxVersion203,
}
var request ofxgo.Request
@ -322,7 +322,7 @@ func TestUnmarshalInvStatementResponse(t *testing.T) {
</OFX>`)
var expected ofxgo.Response
expected.Version = "203"
expected.Version = ofxgo.OfxVersion203
expected.Signon.Status.Code = 0
expected.Signon.Status.Severity = "INFO"
expected.Signon.DtServer = *ofxgo.NewDateGMT(2017, 4, 1, 20, 12, 44, 0)
@ -765,7 +765,7 @@ NEWFILEUID: NONE
</OFX>`)
var expected ofxgo.Response
expected.Version = "102"
expected.Version = ofxgo.OfxVersion102
expected.Signon.Status.Code = 0
expected.Signon.Status.Severity = "INFO"
expected.Signon.DtServer = *ofxgo.NewDateGMT(2017, 4, 3, 12, 0, 0, 0)

View File

@ -39,7 +39,7 @@ func TestMarshalProfileRequest(t *testing.T) {
var client = ofxgo.Client{
AppID: "OFXGO",
AppVer: "0001",
SpecVersion: "203",
SpecVersion: ofxgo.OfxVersion203,
}
var request ofxgo.Request
@ -215,7 +215,7 @@ NEWFILEUID:NONE
</OFX>`)
var expected ofxgo.Response
expected.Version = "102"
expected.Version = ofxgo.OfxVersion102
expected.Signon.Status.Code = 0
expected.Signon.Status.Severity = "INFO"
expected.Signon.DtServer = *ofxgo.NewDateGMT(2017, 4, 3, 9, 34, 58, 0)

View File

@ -3,6 +3,7 @@ package ofxgo
import (
"bytes"
"errors"
"fmt"
"github.com/aclindsa/go/src/encoding/xml"
"time"
)
@ -14,7 +15,7 @@ import (
// error will be returned when Marshal() is called on this Request.
type Request struct {
URL string
Version string // OFX version string, overwritten in Client.Request()
Version ofxVersion // OFX version, overwritten in Client.Request()
Signon SignonRequest //<SIGNONMSGSETV1>
Signup []Message //<SIGNUPMSGSETV1>
Bank []Message //<BANKMSGSETV1>
@ -80,10 +81,10 @@ func (oq *Request) Marshal() (*bytes.Buffer, error) {
// Write the header appropriate to our version
switch oq.Version {
case "102", "103", "151", "160":
case OfxVersion102, OfxVersion103, OfxVersion151, OfxVersion160:
b.WriteString(`OFXHEADER:100
DATA:OFXSGML
VERSION:` + oq.Version + `
VERSION:` + oq.Version.String() + `
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
@ -92,11 +93,11 @@ OLDFILEUID:NONE
NEWFILEUID:NONE
`)
case "200", "201", "202", "203", "210", "211", "220":
case OfxVersion200, OfxVersion201, OfxVersion202, OfxVersion203, OfxVersion210, OfxVersion211, OfxVersion220:
b.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + "\n")
b.WriteString(`<?OFX OFXHEADER="200" VERSION="` + oq.Version + `" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>` + "\n")
b.WriteString(`<?OFX OFXHEADER="200" VERSION="` + oq.Version.String() + `" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>` + "\n")
default:
return nil, errors.New(oq.Version + " is not a valid OFX version string")
return nil, fmt.Errorf("%d is not a valid OFX version string", oq.Version)
}
encoder := xml.NewEncoder(&b)

View File

@ -14,7 +14,7 @@ import (
// It can be inspected by using type assertions or switches on the message set
// you're interested in.
type Response struct {
Version string // String for OFX header, defaults to 203
Version ofxVersion // OFX header version
Signon SignonResponse //<SIGNONMSGSETV1>
Signup []Message //<SIGNUPMSGSETV1>
Bank []Message //<BANKMSGSETV1>
@ -68,12 +68,14 @@ func (or *Response) readSGMLHeaders(r *bufio.Reader) error {
return errors.New("OFX DATA header does not contain OFXSGML")
}
case "VERSION":
switch headervalue {
case "102", "103", "151", "160":
seenVersion = true
or.Version = headervalue
default:
return errors.New("Invalid OFX VERSION in header")
err := or.Version.FromString(headervalue)
if err != nil {
return err
}
seenVersion = true
if or.Version > OfxVersion160 {
return errors.New("OFX VERSION > 160 in SGML header")
}
case "SECURITY":
if headervalue != "NONE" {
@ -134,12 +136,14 @@ func (or *Response) readXMLHeaders(decoder *xml.Decoder) error {
}
seenHeader = true
case "VERSION":
switch value {
case "200", "201", "202", "203", "210", "211", "220":
seenVersion = true
or.Version = value
default:
return errors.New("Invalid OFX VERSION in header")
err := or.Version.FromString(value)
if err != nil {
return err
}
seenVersion = true
if or.Version < OfxVersion200 {
return errors.New("OFX VERSION < 200 in XML header")
}
case "SECURITY":
if value != "NONE" {

View File

@ -9,7 +9,7 @@ func TestMarshalInvalidSignons(t *testing.T) {
var client = ofxgo.Client{
AppID: "OFXGO",
AppVer: "0001",
SpecVersion: "203",
SpecVersion: ofxgo.OfxVersion203,
}
var request ofxgo.Request

View File

@ -40,7 +40,7 @@ func TestMarshalAcctInfoRequest(t *testing.T) {
var client = ofxgo.Client{
AppID: "OFXGO",
AppVer: "0001",
SpecVersion: "203",
SpecVersion: ofxgo.OfxVersion203,
}
var request ofxgo.Request
@ -112,7 +112,7 @@ func TestUnmarshalAcctInfoResponse(t *testing.T) {
</OFX>`)
var expected ofxgo.Response
expected.Version = "203"
expected.Version = ofxgo.OfxVersion203
expected.Signon.Status.Code = 0
expected.Signon.Status.Severity = "INFO"
expected.Signon.DtServer = *ofxgo.NewDateGMT(2006, 1, 15, 11, 23, 03, 0)