1
0
mirror of https://github.com/aclindsa/ofxgo.git synced 2025-07-03 20:38:39 -04:00

39 Commits

Author SHA1 Message Date
d31ac10d08 Add InvTransaction() method to InvTransaction interface. 2024-04-10 21:16:42 -04:00
4f2c5582d1 Pass by value in MarshalXML. 2024-04-10 09:12:15 -04:00
e302ce2e6b CI: Update go versions tested 2023-03-14 22:18:43 -04:00
e3c4afc0a3 Add SecurityInfo() and InvPosition() tests. 2023-03-14 22:10:19 -04:00
c2490e1c6a Add InvPosition() InvPosition to Position interface. 2023-03-14 22:10:19 -04:00
12aca9ab06 Add SecurityInfo() SecInfo to Security interface. 2023-03-14 22:10:19 -04:00
1f657a5d18 Add missing handling for -dryrun flag in command-line client
This flag was only handled for the `download-profile` command. Add
the same handling for all other commands (except `detect-settings`).
2023-02-05 19:52:02 -05:00
afd882f7d2 README: Add inline link to documentation 2021-10-17 21:57:26 -04:00
5ed0050aad Add tests for String/Boolean String() methods 2021-10-17 21:55:45 -04:00
09f161e13e Add tests for Payee/Balance Valid() methods 2021-10-17 21:55:45 -04:00
e1a72fcd54 Replace github.com/howeyc/gopass with golang.org/x/term
gopass is no longer maintained, and the suggested replacement for
getting passwords from terminals is x/term.
2021-10-17 21:20:04 -04:00
a4a166aa74 Update dependencies 2021-10-17 21:04:05 -04:00
3ee400d1ec Test against latest golang: 1.17 2021-10-17 20:50:10 -04:00
67fa945cc8 cmd/ofx: check for nil Currency fields in transactions 2021-10-17 15:49:08 -04:00
cb48d30deb Added Accept http header as Citi now requires it
Without this header set Citi returns http 403 for every request.
2021-09-01 09:35:45 -04:00
12ea3b7e8b Update max CI Golang version to 1.16.x 2021-03-16 15:52:56 -04:00
e76c697cad cmd/ofx: Allow setting User-Agent header from command-line 2021-03-16 15:36:27 -04:00
2641443ebe BasicClient: Add ability to set User-Agent header
Some financial institutions require specific values in the User-Agent
header.
2021-03-16 15:36:27 -04:00
01b26887af GH-39 relax ofx response parsing to support TYPE1 responses
Section 4.2.2.2 Type 1 Protocol Overview in the
[Open Financial Exchange Specification 2.2, Nov 26, 2017](https://www.ofx.net/downloads/OFX%202.2.pdf)
states that:

    Type 1 applies only to the request part of a message; the server response is unaffected.

Thus it appears that we can safely parse SECURITY:TYPE1
using the same logic that we parse SECURITY:NONE

As I understand it, Security:TYPE1 indicates that the
financial institution uses SSL enryption to protect
customer credentials in transit.  This applies
explicitly to the incoming requests which may
contain authentication as part of the request
but does not appear to cause any material changes
to the response format.

As a result it appears that we can safely parse
SECURITY:TYPE1 responess in the same way that
we parse SECURITY:NONE responsese.
2021-03-12 10:46:24 -05:00
2b8a79e4b7 review changes 2021-01-04 07:24:40 -05:00
9136c9bab2 README: add example for parsing local file 2021-01-04 07:24:40 -05:00
0d93a42626 support SGML OFX responses with no line breaks
Some financial institutions (*cough* Wells Fargo *cough*) export OFX files as a single line, which is technically valid according to the v1 spec. In order to parse them correctly, `readSGMLHeaders` now uses a regular expression that allows for all whitespace/line breaks to be optionally excluded.

A new sample response (wellsfargo.qfx) has been added to document this behaviour.
2021-01-04 07:24:40 -05:00
56ca46714b cmd/ofx: Add -dryrun 2020-12-07 13:49:35 -05:00
4c7c48cab7 cmd/ofx: Use 'response.ofx' as the default download file everywhere
Conflicting defaults led to confusing default behavior between
sub-commands.
2020-12-07 13:49:35 -05:00
8c1e6eafab cmd/ofx: Add download-profile command 2020-11-25 14:42:25 -05:00
52f3e4120b README: Replace Travis badge with GitHub Actions 2020-11-25 07:18:43 -05:00
ef87cc536c Replace Travis CI with Github Actions 2020-11-25 07:09:08 -05:00
830a6064c7 .travis.yml: Set Go 1.12 as the minimum version
I'm getting errors when attempting to build with any older version:

    golang.org/x/term
    # golang.org/x/term
    ../../../golang.org/x/term/term_unix_linux.go:9:7: ioctlReadTermios redeclared in this block
	    previous declaration at ../../../golang.org/x/term/term_unix_aix.go:9:26
    ../../../golang.org/x/term/term_unix_linux.go:10:7: ioctlWriteTermios redeclared in this block
	    previous declaration at ../../../golang.org/x/term/term_unix_aix.go:10:27
2020-11-17 10:33:48 -05:00
6807c93e0e cmd/ofx: Add option to use carriage returns for requests 2020-11-17 10:33:48 -05:00
10edd94920 Vanguard client: Accept 500 errors on initial response
Though their server returns a 500 HTTP status code, it still sets the
required cookies on the response that we can use to make a second
request.
2020-11-17 10:33:48 -05:00
d88d45a664 signon: Add missing GENUSERKEY field 2020-11-17 10:33:48 -05:00
2caa23564a Fix spelling of 'Withholding' 2020-11-07 16:45:55 -05:00
5923a34de0 README: Fix bad documentation badge link 2020-10-06 23:15:00 -04:00
aa4d8074b2 Fix spelling error 2020-10-06 23:14:42 -04:00
65cc26a0db Make golint happy 2020-10-06 23:14:42 -04:00
8f3e7309f2 Add comments to a few exported methods 2020-10-06 23:14:42 -04:00
631508ccc9 README: Move from godoc.org to pkg.go.dev 2020-10-06 22:46:45 -04:00
60a5707de6 Update dependencies 2020-10-06 22:38:40 -04:00
3240ef383b README: Update build status badge 2020-10-06 22:19:44 -04:00
33 changed files with 1340 additions and 223 deletions

34
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: ofxgo CI Test
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
go-version: [1.13.x, 1.18.x, 1.20.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test -v -covermode=count -coverprofile="profile.cov" ./...
- name: Send Coverage
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: "profile.cov"
flag-name: ${{ matrix.os }}-go-${{ matrix.go-version }}
parallel: true
# notifies that all test jobs are finished.
finish:
needs: test
runs-on: ubuntu-latest
steps:
- uses: shogo82148/actions-goveralls@v1
with:
parallel-finished: true

View File

@ -1,20 +0,0 @@
language: go
os:
- linux
go:
- 1.9.x
- 1.12.x
- master
before_install:
# Fetch/build coverage reporting tools
- go get github.com/mattn/goveralls
- go install github.com/mattn/goveralls
script:
- go test -v -covermode=count -coverprofile=coverage.out
after_script:
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN

View File

@ -1,9 +1,9 @@
# OFXGo
[![Go Report Card](https://goreportcard.com/badge/github.com/aclindsa/ofxgo)](https://goreportcard.com/report/github.com/aclindsa/ofxgo)
[![Build Status](https://travis-ci.org/aclindsa/ofxgo.svg?branch=master)](https://travis-ci.org/aclindsa/ofxgo)
[![Build Status](https://github.com/aclindsa/ofxgo/workflows/ofxgo%20CI%20Test/badge.svg?branch=master)](https://github.com/aclindsa/ofxgo/actions?query=workflow%3A%22ofxgo+CI+Test%22+branch%3Amaster)
[![Coverage Status](https://coveralls.io/repos/github/aclindsa/ofxgo/badge.svg?branch=master)](https://coveralls.io/github/aclindsa/ofxgo?branch=master)
[![GoDoc](https://godoc.org/github.com/aclindsa/ofxgo?status.svg)](https://godoc.org/github.com/aclindsa/ofxgo)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/aclindsa?ofxgo)](https://pkg.go.dev/github.com/aclindsa/ofxgo)
**OFXGo** is a library for querying OFX servers and/or parsing the responses. It
also provides an example command-line client to demonstrate the use of the
@ -13,7 +13,7 @@ library.
The main purpose of this project is to provide a library to make it easier to
query financial information with OFX from the comfort of Golang, without having
to marshal/unmarshal to SGML or XML. The library does *not* intend to abstract
to marshal/unmarshal to SGML or XML. The library does _not_ intend to abstract
away all of the details of the OFX specification, which would be difficult to do
well. Instead, it exposes the OFX SGML/XML hierarchy as structs which mostly
resemble it. Its primary goal is to enable the creation of other personal
@ -26,7 +26,8 @@ created a sample command-line client which uses the library to do simple tasks
(currently it does little more than list accounts and query for balances and
transactions). My hope is that by studying its code, new users will be able to
figure out how to use the library much faster than staring at the OFX
specification (or this library's API documentation). The command-line client
specification (or this library's [API
documentation](https://pkg.go.dev/github.com/aclindsa/ofxgo)). The command-line client
also serves as an easy way for me to test/debug the library with actual
financial institutions, which frequently have 'quirks' in their implementations.
The command-line client can be found in the [cmd/ofx
@ -36,7 +37,7 @@ repository.
## Library documentation
Documentation can be found with the `go doc` tool, or at
https://godoc.org/github.com/aclindsa/ofxgo
https://pkg.go.dev/github.com/aclindsa/ofxgo
## Example Usage
@ -94,9 +95,30 @@ if stmt, ok := response.Bank[0].(*ofxgo.StatementResponse); ok {
}
```
Similarly, if you have an OFX file available locally, you can parse it directly:
```go
func main() {
f, err := os.Open("./transactions.qfx")
if err != nil {
fmt.Printf("can't open file: %v\n", err)
return
}
defer f.Close()
resp, err := ofxgo.ParseResponse(f)
if err != nil {
fmt.Printf("can't parse response: %v\n", err)
return
}
// do something with resp (*ofxgo.Response)
}
```
## Requirements
OFXGo requires go >= 1.9
OFXGo requires go >= 1.12
## Using the command-line client

View File

@ -284,3 +284,100 @@ func TestUnmarshalBankStatementResponse(t *testing.T) {
checkResponsesEqual(t, &expected, response)
checkResponseRoundTrip(t, response)
}
func TestPayeeValid(t *testing.T) {
p := Payee{
Name: "Jane",
Addr1: "Sesame Street",
City: "Mytown",
State: "AA",
PostalCode: "12345",
Phone: "12345678901",
}
valid, err := p.Valid()
if !valid {
t.Fatalf("Unexpected error from calling Valid: %s\n", err)
}
// Ensure some empty fields trigger invalid response
badp := p
badp.Name = ""
valid, err = badp.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty name\n")
}
badp = p
badp.Addr1 = ""
valid, err = badp.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty address\n")
}
badp = p
badp.City = ""
valid, err = badp.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty city\n")
}
badp = p
badp.State = ""
valid, err = badp.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty state\n")
}
badp = p
badp.PostalCode = ""
valid, err = badp.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty postal code\n")
}
badp = p
badp.Phone = ""
valid, err = badp.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty phone\n")
}
}
func TestBalanceValid(t *testing.T) {
var a Amount
a.SetFrac64(8, 1)
b := Balance{
Name: "Checking",
Desc: "Jane's Personal Checking",
BalType: BalTypeDollar,
Value: a,
}
valid, err := b.Valid()
if !valid {
t.Fatalf("Unexpected error from calling Valid: %s\n", err)
}
badb := b
badb.Name = ""
valid, err = badb.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty name\n")
}
badb = b
badb.Desc = ""
valid, err = badb.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with empty description\n")
}
badb = Balance{
Name: "Checking",
Desc: "Jane's Personal Checking",
Value: a,
}
valid, err = badb.Valid()
if valid || err == nil {
t.Fatalf("Expected error from calling Valid with unspecified balance type\n")
}
}

View File

@ -21,6 +21,8 @@ type BasicClient struct {
NoIndent bool
// Use carriage returns on new lines
CarriageReturn bool
// Set User-Agent header to this string, if not empty
UserAgent string
}
// OfxVersion returns the OFX specification version this BasicClient will marshal
@ -61,27 +63,42 @@ func (c *BasicClient) CarriageReturnNewLines() bool {
return c.CarriageReturn
}
// RawRequest is a convenience wrapper around http.Post. It is exposed only for
// when you need to read/inspect the raw HTTP response yourself.
func (c *BasicClient) RawRequest(URL string, r io.Reader) (*http.Response, error) {
if !strings.HasPrefix(URL, "https://") {
return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
}
response, err := http.Post(URL, "application/x-ofx", r)
request, err := http.NewRequest("POST", URL, r)
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "application/x-ofx")
request.Header.Add("Accept", "*/*, application/x-ofx")
if c.UserAgent != "" {
request.Header.Set("User-Agent", c.UserAgent)
}
response, err := http.DefaultClient.Do(request)
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New("OFXQuery request status: " + response.Status)
return response, errors.New("OFXQuery request status: " + response.Status)
}
return response, nil
}
// RequestNoParse marshals a Request to XML, makes an HTTP request, and returns
// the raw HTTP response
func (c *BasicClient) RequestNoParse(r *Request) (*http.Response, error) {
return clientRequestNoParse(c, r)
}
// Request marshals a Request to XML, makes an HTTP request, and then
// unmarshals the response into a Response object.
func (c *BasicClient) Request(r *Request) (*Response, error) {
return clientRequest(c, r)
}

View File

@ -20,7 +20,7 @@ var filename, bankID, acctID, acctType string
func init() {
defineServerFlags(downloadCommand.Flags)
downloadCommand.Flags.StringVar(&filename, "filename", "./download.ofx", "The file to save to")
downloadCommand.Flags.StringVar(&filename, "filename", "./response.ofx", "The file to save to")
downloadCommand.Flags.StringVar(&bankID, "bankid", "", "BankID (from `get-accounts` subcommand)")
downloadCommand.Flags.StringVar(&acctID, "acctid", "", "AcctID (from `get-accounts` subcommand)")
downloadCommand.Flags.StringVar(&acctType, "accttype", "CHECKING", "AcctType (from `get-accounts` subcommand)")
@ -64,6 +64,11 @@ func download() {
query.Bank = append(query.Bank, &statementRequest)
if dryrun {
printRequest(client, query)
return
}
response, err := client.RequestNoParse(query)
if err != nil {
fmt.Println("Error requesting account statement:", err)

View File

@ -49,6 +49,11 @@ func bankTransactions() {
query.Bank = append(query.Bank, &statementRequest)
if dryrun {
printRequest(client, query)
return
}
response, err := client.Request(query)
if err != nil {
fmt.Println("Error requesting account statement:", err)
@ -77,7 +82,7 @@ func bankTransactions() {
func printTransaction(defCurrency ofxgo.CurrSymbol, tran *ofxgo.Transaction) {
currency := defCurrency
if ok, _ := tran.Currency.Valid(); ok {
if tran.Currency != nil {
currency = tran.Currency.CurSym
}

View File

@ -18,7 +18,7 @@ var ccDownloadCommand = command{
func init() {
defineServerFlags(ccDownloadCommand.Flags)
ccDownloadCommand.Flags.StringVar(&filename, "filename", "./download.ofx", "The file to save to")
ccDownloadCommand.Flags.StringVar(&filename, "filename", "./response.ofx", "The file to save to")
ccDownloadCommand.Flags.StringVar(&acctID, "acctid", "", "AcctID (from `get-accounts` subcommand)")
}
@ -51,6 +51,11 @@ func ccDownload() {
}
query.CreditCard = append(query.CreditCard, &statementRequest)
if dryrun {
printRequest(client, query)
return
}
response, err := client.RequestNoParse(query)
if err != nil {

View File

@ -38,6 +38,11 @@ func ccTransactions() {
}
query.CreditCard = append(query.CreditCard, &statementRequest)
if dryrun {
printRequest(client, query)
return
}
response, err := client.Request(query)
if err != nil {
fmt.Println("Error requesting account statement:", err)
@ -60,7 +65,7 @@ func ccTransactions() {
fmt.Println("Transactions:")
for _, tran := range stmt.BankTranList.Transactions {
currency := stmt.CurDef
if ok, _ := tran.Currency.Valid(); ok {
if tran.Currency != nil {
currency = tran.Currency.CurSym
}

View File

@ -3,7 +3,8 @@ package main
import (
"flag"
"fmt"
"github.com/howeyc/gopass"
"golang.org/x/term"
"os"
)
type command struct {
@ -22,6 +23,9 @@ func (c *command) usage() {
// flags common to all server transactions
var serverURL, username, password, org, fid, appID, appVer, ofxVersion, clientUID string
var noIndentRequests bool
var carriageReturn bool
var dryrun bool
var userAgent string
func defineServerFlags(f *flag.FlagSet) {
f.StringVar(&serverURL, "url", "", "Financial institution's OFX Server URL (see ofxhome.com if you don't know it)")
@ -34,6 +38,9 @@ func defineServerFlags(f *flag.FlagSet) {
f.StringVar(&ofxVersion, "ofxversion", "203", "OFX version to use")
f.StringVar(&clientUID, "clientuid", "", "Client UID (only required by a few FIs, like Chase)")
f.BoolVar(&noIndentRequests, "noindent", false, "Don't indent OFX requests")
f.BoolVar(&carriageReturn, "carriagereturn", false, "Use carriage return as line separator")
f.StringVar(&userAgent, "useragent", "", "Use string as User-Agent header when sending request")
f.BoolVar(&dryrun, "dryrun", false, "Don't send request - print content of request instead")
}
func checkServerFlags() bool {
@ -49,7 +56,7 @@ func checkServerFlags() bool {
if ret && len(password) == 0 {
fmt.Printf("Password for %s: ", username)
pass, err := gopass.GetPasswd()
pass, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
fmt.Printf("Error reading password: %s\n", err)
ret = false

View File

@ -35,6 +35,11 @@ func getAccounts() {
}
query.Signup = append(query.Signup, &acctInfo)
if dryrun {
printRequest(client, query)
return
}
response, err := client.Request(query)
if err != nil {
fmt.Println("Error requesting account information:", err)

View File

@ -20,7 +20,7 @@ var brokerID string
func init() {
defineServerFlags(invDownloadCommand.Flags)
invDownloadCommand.Flags.StringVar(&filename, "filename", "./download.ofx", "The file to save to")
invDownloadCommand.Flags.StringVar(&filename, "filename", "./response.ofx", "The file to save to")
invDownloadCommand.Flags.StringVar(&acctID, "acctid", "", "AcctID (from `get-accounts` subcommand)")
invDownloadCommand.Flags.StringVar(&brokerID, "brokerid", "", "BrokerID (from `get-accounts` subcommand)")
}
@ -60,6 +60,11 @@ func invDownload() {
}
query.InvStmt = append(query.InvStmt, &statementRequest)
if dryrun {
printRequest(client, query)
return
}
response, err := client.RequestNoParse(query)
if err != nil {

View File

@ -45,6 +45,11 @@ func invTransactions() {
}
query.InvStmt = append(query.InvStmt, &statementRequest)
if dryrun {
printRequest(client, query)
return
}
response, err := client.Request(query)
if err != nil {
os.Exit(1)

View File

@ -7,6 +7,7 @@ import (
)
var commands = []command{
profileDownloadCommand,
getAccountsCommand,
downloadCommand,
ccDownloadCommand,

View File

@ -0,0 +1,81 @@
package main
import (
"flag"
"fmt"
"github.com/aclindsa/ofxgo"
"io"
"os"
)
var profileDownloadCommand = command{
Name: "download-profile",
Description: "Download a FI profile to a file",
Flags: flag.NewFlagSet("download-profile", flag.ExitOnError),
CheckFlags: downloadProfileCheckFlags,
Do: downloadProfile,
}
func init() {
defineServerFlags(profileDownloadCommand.Flags)
profileDownloadCommand.Flags.StringVar(&filename, "filename", "./response.ofx", "The file to save to")
}
func downloadProfileCheckFlags() bool {
// Assume if the user didn't specify username that we should use anonymous
// values for it and password
if len(username) == 0 {
username = "anonymous00000000000000000000000"
password = "anonymous00000000000000000000000"
}
ret := checkServerFlags()
if len(filename) == 0 {
fmt.Println("Error: Filename empty")
return false
}
return ret
}
func downloadProfile() {
client, query := newRequest()
uid, err := ofxgo.RandomUID()
if err != nil {
fmt.Println("Error creating uid for transaction:", err)
os.Exit(1)
}
profileRequest := ofxgo.ProfileRequest{
TrnUID: *uid,
}
query.Prof = append(query.Prof, &profileRequest)
if dryrun {
printRequest(client, query)
return
}
response, err := client.RequestNoParse(query)
if err != nil {
fmt.Println("Error requesting FI profile:", err)
os.Exit(1)
}
defer response.Body.Close()
file, err := os.Create(filename)
if err != nil {
fmt.Println("Error creating file to write to:", err)
os.Exit(1)
}
defer file.Close()
_, err = io.Copy(file, response.Body)
if err != nil {
fmt.Println("Error writing response to file:", err)
os.Exit(1)
}
}

View File

@ -18,6 +18,8 @@ func newRequest() (ofxgo.Client, *ofxgo.Request) {
AppVer: appVer,
SpecVersion: ver,
NoIndent: noIndentRequests,
CarriageReturn: carriageReturn,
UserAgent: userAgent,
})
var query ofxgo.Request
@ -30,3 +32,14 @@ func newRequest() (ofxgo.Client, *ofxgo.Request) {
return client, &query
}
func printRequest(c ofxgo.Client, r *ofxgo.Request) {
r.SetClientFields(c)
b, err := r.Marshal()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(b)
}

View File

@ -69,11 +69,11 @@ func (e *ofxVersion) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *ofxVersion) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e ofxVersion) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(ofxVersions[*e-1], start)
enc.EncodeElement(ofxVersions[e-1], start)
return nil
}
@ -137,11 +137,11 @@ func (e *acctType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *acctType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e acctType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(acctTypes[*e-1], start)
enc.EncodeElement(acctTypes[e-1], start)
return nil
}
@ -218,11 +218,11 @@ func (e *trnType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *trnType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e trnType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(trnTypes[*e-1], start)
enc.EncodeElement(trnTypes[e-1], start)
return nil
}
@ -284,11 +284,11 @@ func (e *imageType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *imageType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e imageType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(imageTypes[*e-1], start)
enc.EncodeElement(imageTypes[e-1], start)
return nil
}
@ -350,11 +350,11 @@ func (e *imageRefType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
return e.FromString(value)
}
func (e *imageRefType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e imageRefType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(imageRefTypes[*e-1], start)
enc.EncodeElement(imageRefTypes[e-1], start)
return nil
}
@ -416,11 +416,11 @@ func (e *checkSup) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *checkSup) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e checkSup) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(checkSups[*e-1], start)
enc.EncodeElement(checkSups[e-1], start)
return nil
}
@ -481,11 +481,11 @@ func (e *correctAction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
return e.FromString(value)
}
func (e *correctAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e correctAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(correctActions[*e-1], start)
enc.EncodeElement(correctActions[e-1], start)
return nil
}
@ -547,11 +547,11 @@ func (e *balType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *balType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e balType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(balTypes[*e-1], start)
enc.EncodeElement(balTypes[e-1], start)
return nil
}
@ -617,11 +617,11 @@ func (e *inv401kSource) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
return e.FromString(value)
}
func (e *inv401kSource) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e inv401kSource) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(inv401kSources[*e-1], start)
enc.EncodeElement(inv401kSources[e-1], start)
return nil
}
@ -684,11 +684,11 @@ func (e *subAcctType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *subAcctType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e subAcctType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(subAcctTypes[*e-1], start)
enc.EncodeElement(subAcctTypes[e-1], start)
return nil
}
@ -749,11 +749,11 @@ func (e *buyType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *buyType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e buyType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(buyTypes[*e-1], start)
enc.EncodeElement(buyTypes[e-1], start)
return nil
}
@ -815,11 +815,11 @@ func (e *optAction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *optAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e optAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(optActions[*e-1], start)
enc.EncodeElement(optActions[e-1], start)
return nil
}
@ -880,11 +880,11 @@ func (e *tferAction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *tferAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e tferAction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(tferActions[*e-1], start)
enc.EncodeElement(tferActions[e-1], start)
return nil
}
@ -945,11 +945,11 @@ func (e *posType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *posType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e posType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(posTypes[*e-1], start)
enc.EncodeElement(posTypes[e-1], start)
return nil
}
@ -1010,11 +1010,11 @@ func (e *secured) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *secured) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e secured) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(secureds[*e-1], start)
enc.EncodeElement(secureds[e-1], start)
return nil
}
@ -1076,11 +1076,11 @@ func (e *duration) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *duration) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e duration) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(durations[*e-1], start)
enc.EncodeElement(durations[e-1], start)
return nil
}
@ -1142,11 +1142,11 @@ func (e *restriction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *restriction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e restriction) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(restrictions[*e-1], start)
enc.EncodeElement(restrictions[e-1], start)
return nil
}
@ -1207,11 +1207,11 @@ func (e *unitType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *unitType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e unitType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(unitTypes[*e-1], start)
enc.EncodeElement(unitTypes[e-1], start)
return nil
}
@ -1272,11 +1272,11 @@ func (e *optBuyType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *optBuyType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e optBuyType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(optBuyTypes[*e-1], start)
enc.EncodeElement(optBuyTypes[e-1], start)
return nil
}
@ -1337,11 +1337,11 @@ func (e *sellType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *sellType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e sellType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(sellTypes[*e-1], start)
enc.EncodeElement(sellTypes[e-1], start)
return nil
}
@ -1410,11 +1410,11 @@ func (e *loanPmtFreq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *loanPmtFreq) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e loanPmtFreq) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(loanPmtFreqs[*e-1], start)
enc.EncodeElement(loanPmtFreqs[e-1], start)
return nil
}
@ -1478,11 +1478,11 @@ func (e *incomeType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *incomeType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e incomeType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(incomeTypes[*e-1], start)
enc.EncodeElement(incomeTypes[e-1], start)
return nil
}
@ -1544,11 +1544,11 @@ func (e *sellReason) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *sellReason) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e sellReason) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(sellReasons[*e-1], start)
enc.EncodeElement(sellReasons[e-1], start)
return nil
}
@ -1609,11 +1609,11 @@ func (e *optSellType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *optSellType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e optSellType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(optSellTypes[*e-1], start)
enc.EncodeElement(optSellTypes[e-1], start)
return nil
}
@ -1676,11 +1676,11 @@ func (e *relType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *relType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e relType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(relTypes[*e-1], start)
enc.EncodeElement(relTypes[e-1], start)
return nil
}
@ -1743,11 +1743,11 @@ func (e *charType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *charType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e charType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(charTypes[*e-1], start)
enc.EncodeElement(charTypes[e-1], start)
return nil
}
@ -1808,11 +1808,11 @@ func (e *syncMode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *syncMode) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e syncMode) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(syncModes[*e-1], start)
enc.EncodeElement(syncModes[e-1], start)
return nil
}
@ -1873,11 +1873,11 @@ func (e *ofxSec) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *ofxSec) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e ofxSec) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(ofxSecs[*e-1], start)
enc.EncodeElement(ofxSecs[e-1], start)
return nil
}
@ -1938,11 +1938,11 @@ func (e *debtType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *debtType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e debtType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(debtTypes[*e-1], start)
enc.EncodeElement(debtTypes[e-1], start)
return nil
}
@ -2005,11 +2005,11 @@ func (e *debtClass) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *debtClass) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e debtClass) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(debtClasss[*e-1], start)
enc.EncodeElement(debtClasss[e-1], start)
return nil
}
@ -2073,11 +2073,11 @@ func (e *couponFreq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *couponFreq) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e couponFreq) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(couponFreqs[*e-1], start)
enc.EncodeElement(couponFreqs[e-1], start)
return nil
}
@ -2140,11 +2140,11 @@ func (e *callType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *callType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e callType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(callTypes[*e-1], start)
enc.EncodeElement(callTypes[e-1], start)
return nil
}
@ -2210,11 +2210,11 @@ func (e *assetClass) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *assetClass) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e assetClass) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(assetClasss[*e-1], start)
enc.EncodeElement(assetClasss[e-1], start)
return nil
}
@ -2276,11 +2276,11 @@ func (e *mfType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *mfType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e mfType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(mfTypes[*e-1], start)
enc.EncodeElement(mfTypes[e-1], start)
return nil
}
@ -2341,11 +2341,11 @@ func (e *optType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *optType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e optType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(optTypes[*e-1], start)
enc.EncodeElement(optTypes[e-1], start)
return nil
}
@ -2408,11 +2408,11 @@ func (e *stockType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *stockType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e stockType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(stockTypes[*e-1], start)
enc.EncodeElement(stockTypes[e-1], start)
return nil
}
@ -2476,11 +2476,11 @@ func (e *holderType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}
func (e *holderType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e holderType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(holderTypes[*e-1], start)
enc.EncodeElement(holderTypes[e-1], start)
return nil
}
@ -2543,11 +2543,11 @@ func (e *acctClassification) UnmarshalXML(d *xml.Decoder, start xml.StartElement
return e.FromString(value)
}
func (e *acctClassification) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e acctClassification) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(acctClassifications[*e-1], start)
enc.EncodeElement(acctClassifications[e-1], start)
return nil
}
@ -2609,11 +2609,11 @@ func (e *svcStatus) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return e.FromString(value)
}
func (e *svcStatus) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e svcStatus) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(svcStatuss[*e-1], start)
enc.EncodeElement(svcStatuss[e-1], start)
return nil
}
@ -2683,11 +2683,11 @@ func (e *usProductType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
return e.FromString(value)
}
func (e *usProductType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
func (e usProductType) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
if !e.Valid() {
return nil
}
enc.EncodeElement(usProductTypes[*e-1], start)
enc.EncodeElement(usProductTypes[e-1], start)
return nil
}

View File

@ -56,6 +56,18 @@ func TestOfxVersion(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E ofxVersion
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct OfxVersion): %s\n", err)
}
if string(b) != "<SC><E>220</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>220</E></SC>", string(b))
}
}
func TestAcctType(t *testing.T) {
@ -101,6 +113,18 @@ func TestAcctType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E acctType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct AcctType): %s\n", err)
}
if string(b) != "<SC><E>CD</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>CD</E></SC>", string(b))
}
}
func TestTrnType(t *testing.T) {
@ -146,6 +170,18 @@ func TestTrnType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E trnType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct TrnType): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestImageType(t *testing.T) {
@ -191,6 +227,18 @@ func TestImageType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E imageType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct ImageType): %s\n", err)
}
if string(b) != "<SC><E>TAX</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>TAX</E></SC>", string(b))
}
}
func TestImageRefType(t *testing.T) {
@ -236,6 +284,18 @@ func TestImageRefType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E imageRefType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct ImageRefType): %s\n", err)
}
if string(b) != "<SC><E>FORMURL</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>FORMURL</E></SC>", string(b))
}
}
func TestCheckSup(t *testing.T) {
@ -281,6 +341,18 @@ func TestCheckSup(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E checkSup
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct CheckSup): %s\n", err)
}
if string(b) != "<SC><E>FRONTANDBACK</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>FRONTANDBACK</E></SC>", string(b))
}
}
func TestCorrectAction(t *testing.T) {
@ -326,6 +398,18 @@ func TestCorrectAction(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E correctAction
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct CorrectAction): %s\n", err)
}
if string(b) != "<SC><E>REPLACE</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>REPLACE</E></SC>", string(b))
}
}
func TestBalType(t *testing.T) {
@ -371,6 +455,18 @@ func TestBalType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E balType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct BalType): %s\n", err)
}
if string(b) != "<SC><E>NUMBER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>NUMBER</E></SC>", string(b))
}
}
func TestInv401kSource(t *testing.T) {
@ -416,6 +512,18 @@ func TestInv401kSource(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E inv401kSource
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct Inv401kSource): %s\n", err)
}
if string(b) != "<SC><E>OTHERNONVEST</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHERNONVEST</E></SC>", string(b))
}
}
func TestSubAcctType(t *testing.T) {
@ -461,6 +569,18 @@ func TestSubAcctType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E subAcctType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct SubAcctType): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestBuyType(t *testing.T) {
@ -506,6 +626,18 @@ func TestBuyType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E buyType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct BuyType): %s\n", err)
}
if string(b) != "<SC><E>BUYTOCOVER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>BUYTOCOVER</E></SC>", string(b))
}
}
func TestOptAction(t *testing.T) {
@ -551,6 +683,18 @@ func TestOptAction(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E optAction
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct OptAction): %s\n", err)
}
if string(b) != "<SC><E>EXPIRE</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>EXPIRE</E></SC>", string(b))
}
}
func TestTferAction(t *testing.T) {
@ -596,6 +740,18 @@ func TestTferAction(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E tferAction
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct TferAction): %s\n", err)
}
if string(b) != "<SC><E>OUT</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OUT</E></SC>", string(b))
}
}
func TestPosType(t *testing.T) {
@ -641,6 +797,18 @@ func TestPosType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E posType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct PosType): %s\n", err)
}
if string(b) != "<SC><E>SHORT</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>SHORT</E></SC>", string(b))
}
}
func TestSecured(t *testing.T) {
@ -686,6 +854,18 @@ func TestSecured(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E secured
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct Secured): %s\n", err)
}
if string(b) != "<SC><E>COVERED</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>COVERED</E></SC>", string(b))
}
}
func TestDuration(t *testing.T) {
@ -731,6 +911,18 @@ func TestDuration(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E duration
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct Duration): %s\n", err)
}
if string(b) != "<SC><E>IMMEDIATE</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>IMMEDIATE</E></SC>", string(b))
}
}
func TestRestriction(t *testing.T) {
@ -776,6 +968,18 @@ func TestRestriction(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E restriction
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct Restriction): %s\n", err)
}
if string(b) != "<SC><E>NONE</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>NONE</E></SC>", string(b))
}
}
func TestUnitType(t *testing.T) {
@ -821,6 +1025,18 @@ func TestUnitType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E unitType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct UnitType): %s\n", err)
}
if string(b) != "<SC><E>CURRENCY</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>CURRENCY</E></SC>", string(b))
}
}
func TestOptBuyType(t *testing.T) {
@ -866,6 +1082,18 @@ func TestOptBuyType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E optBuyType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct OptBuyType): %s\n", err)
}
if string(b) != "<SC><E>BUYTOCLOSE</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>BUYTOCLOSE</E></SC>", string(b))
}
}
func TestSellType(t *testing.T) {
@ -911,6 +1139,18 @@ func TestSellType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E sellType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct SellType): %s\n", err)
}
if string(b) != "<SC><E>SELLSHORT</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>SELLSHORT</E></SC>", string(b))
}
}
func TestLoanPmtFreq(t *testing.T) {
@ -956,6 +1196,18 @@ func TestLoanPmtFreq(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E loanPmtFreq
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct LoanPmtFreq): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestIncomeType(t *testing.T) {
@ -1001,6 +1253,18 @@ func TestIncomeType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E incomeType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct IncomeType): %s\n", err)
}
if string(b) != "<SC><E>MISC</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>MISC</E></SC>", string(b))
}
}
func TestSellReason(t *testing.T) {
@ -1046,6 +1310,18 @@ func TestSellReason(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E sellReason
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct SellReason): %s\n", err)
}
if string(b) != "<SC><E>MATURITY</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>MATURITY</E></SC>", string(b))
}
}
func TestOptSellType(t *testing.T) {
@ -1091,6 +1367,18 @@ func TestOptSellType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E optSellType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct OptSellType): %s\n", err)
}
if string(b) != "<SC><E>SELLTOOPEN</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>SELLTOOPEN</E></SC>", string(b))
}
}
func TestRelType(t *testing.T) {
@ -1136,6 +1424,18 @@ func TestRelType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E relType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct RelType): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestCharType(t *testing.T) {
@ -1181,6 +1481,18 @@ func TestCharType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E charType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct CharType): %s\n", err)
}
if string(b) != "<SC><E>ALPHAANDNUMERIC</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>ALPHAANDNUMERIC</E></SC>", string(b))
}
}
func TestSyncMode(t *testing.T) {
@ -1226,6 +1538,18 @@ func TestSyncMode(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E syncMode
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct SyncMode): %s\n", err)
}
if string(b) != "<SC><E>LITE</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>LITE</E></SC>", string(b))
}
}
func TestOfxSec(t *testing.T) {
@ -1271,6 +1595,18 @@ func TestOfxSec(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E ofxSec
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct OfxSec): %s\n", err)
}
if string(b) != "<SC><E>TYPE 1</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>TYPE 1</E></SC>", string(b))
}
}
func TestDebtType(t *testing.T) {
@ -1316,6 +1652,18 @@ func TestDebtType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E debtType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct DebtType): %s\n", err)
}
if string(b) != "<SC><E>ZERO</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>ZERO</E></SC>", string(b))
}
}
func TestDebtClass(t *testing.T) {
@ -1361,6 +1709,18 @@ func TestDebtClass(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E debtClass
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct DebtClass): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestCouponFreq(t *testing.T) {
@ -1406,6 +1766,18 @@ func TestCouponFreq(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E couponFreq
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct CouponFreq): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestCallType(t *testing.T) {
@ -1451,6 +1823,18 @@ func TestCallType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E callType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct CallType): %s\n", err)
}
if string(b) != "<SC><E>MATURITY</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>MATURITY</E></SC>", string(b))
}
}
func TestAssetClass(t *testing.T) {
@ -1496,6 +1880,18 @@ func TestAssetClass(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E assetClass
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct AssetClass): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestMfType(t *testing.T) {
@ -1541,6 +1937,18 @@ func TestMfType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E mfType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct MfType): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestOptType(t *testing.T) {
@ -1586,6 +1994,18 @@ func TestOptType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E optType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct OptType): %s\n", err)
}
if string(b) != "<SC><E>CALL</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>CALL</E></SC>", string(b))
}
}
func TestStockType(t *testing.T) {
@ -1631,6 +2051,18 @@ func TestStockType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E stockType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct StockType): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestHolderType(t *testing.T) {
@ -1676,6 +2108,18 @@ func TestHolderType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E holderType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct HolderType): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestAcctClassification(t *testing.T) {
@ -1721,6 +2165,18 @@ func TestAcctClassification(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E acctClassification
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct AcctClassification): %s\n", err)
}
if string(b) != "<SC><E>OTHER</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>OTHER</E></SC>", string(b))
}
}
func TestSvcStatus(t *testing.T) {
@ -1766,6 +2222,18 @@ func TestSvcStatus(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E svcStatus
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct SvcStatus): %s\n", err)
}
if string(b) != "<SC><E>ACTIVE</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>ACTIVE</E></SC>", string(b))
}
}
func TestUsProductType(t *testing.T) {
@ -1811,4 +2279,16 @@ func TestUsProductType(t *testing.T) {
if err == nil {
t.Fatalf("Expected error unmarshalling garbage value\n")
}
type SC struct {
E usProductType
}
sc := SC{E: e}
b, err = xml.Marshal(sc)
if err != nil {
t.Fatalf("Unexpected error on xml.Marshal(struct UsProductType): %s\n", err)
}
if string(b) != "<SC><E>UGMA</E></SC>" {
t.Fatalf("Expected '%s', got '%s'\n", "<SC><E>UGMA</E></SC>", string(b))
}
}

View File

@ -20,7 +20,7 @@ type DiscoverCardClient struct {
}
// NewDiscoverCardClient returns a Client interface configured to handle
// Discover Card's brand of idiosyncracy
// Discover Card's brand of idiosyncrasy
func NewDiscoverCardClient(bc *BasicClient) Client {
return &DiscoverCardClient{bc}
}
@ -77,6 +77,8 @@ func discoverCardHTTPPost(URL string, r io.Reader) (*http.Response, error) {
return http.ReadResponse(bufio.NewReader(conn), nil)
}
// RawRequest is a convenience wrapper around http.Post. It is exposed only for
// when you need to read/inspect the raw HTTP response yourself.
func (c *DiscoverCardClient) RawRequest(URL string, r io.Reader) (*http.Response, error) {
if !strings.HasPrefix(URL, "https://") {
return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
@ -94,10 +96,14 @@ func (c *DiscoverCardClient) RawRequest(URL string, r io.Reader) (*http.Response
return response, nil
}
// RequestNoParse marshals a Request to XML, makes an HTTP request, and returns
// the raw HTTP response
func (c *DiscoverCardClient) RequestNoParse(r *Request) (*http.Response, error) {
return clientRequestNoParse(c, r)
}
// Request marshals a Request to XML, makes an HTTP request, and then
// unmarshals the response into a Response object.
func (c *DiscoverCardClient) Request(r *Request) (*Response, error) {
return clientRequest(c, r)
}

View File

@ -116,11 +116,11 @@ func (e *{enumLower}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
return e.FromString(value)
}}
func (e *{enumLower}) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {{
func (e {enumLower}) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {{
if !e.Valid() {{
return nil
}}
enc.EncodeElement({enumLower}s[*e-1], start)
enc.EncodeElement({enumLower}s[e-1], start)
return nil
}}
@ -225,6 +225,18 @@ func Test{enum}(t *testing.T) {{
if err == nil {{
t.Fatalf("Expected error unmarshalling garbage value\\n")
}}
type SC struct {{
E {enumLower}
}}
sc := SC{{E: e}}
b, err = xml.Marshal(sc)
if err != nil {{
t.Fatalf("Unexpected error on xml.Marshal(struct {enum}): %s\\n", err)
}}
if string(b) != "<SC><E>{lastValueUpper}</E></SC>" {{
t.Fatalf("Expected '%s', got '%s'\\n", "<SC><E>{lastValueUpper}</E></SC>", string(b))
}}
}}
"""
@ -232,8 +244,10 @@ with open("constants_test.go", 'w') as f:
f.write(test_header)
for enum in enums:
enumLower = enum[:1].lower() + enum[1:].replace(" ", "")
firstValueUpper = enums[enum][0][0].upper()
lastValueUpper = enums[enum][0][-1].upper()
f.write(test_template.format(enum=enum,
enumLower=enumLower,
firstValueUpper=firstValueUpper,
lastValueUpper=lastValueUpper))

8
go.mod
View File

@ -1,11 +1,9 @@
module github.com/aclindsa/ofxgo
require (
github.com/aclindsa/xml v0.0.0-20190701095008-453d2c6090c2
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 // indirect
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect
golang.org/x/text v0.0.0-20180911161511-905a57155faa
github.com/aclindsa/xml v0.0.0-20201125035057-bbd5c9ec99ac
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
)
go 1.9

23
go.sum
View File

@ -1,14 +1,9 @@
github.com/aclindsa/xml v0.0.0-20171002130543-5d4402bb4a20 h1:wN3KlzWq56AIgOqFzYLYVih4zVyPDViCUeG5uZxJHq4=
github.com/aclindsa/xml v0.0.0-20171002130543-5d4402bb4a20/go.mod h1:DiEHtTD+e6zS3+R95F05Bfbcsfv13wZTi2M4LfAFLBE=
github.com/aclindsa/xml v0.0.0-20190625094425-0aa7a3409cf4 h1:STo5wlCItpgL9LFBui17kZ/N1iKQk+UztLRj2cVkSXQ=
github.com/aclindsa/xml v0.0.0-20190625094425-0aa7a3409cf4/go.mod h1:GjqOUT8xlg5+T19lFv6yAGNrtMKkZ839Gt4e16mBXlY=
github.com/aclindsa/xml v0.0.0-20190701095008-453d2c6090c2 h1:ICeGSGrc6fd81VtQ3nZ2h7GEOKxWYwRxjW0v0d/mgu4=
github.com/aclindsa/xml v0.0.0-20190701095008-453d2c6090c2/go.mod h1:GjqOUT8xlg5+T19lFv6yAGNrtMKkZ839Gt4e16mBXlY=
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0=
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0=
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.0.0-20180911161511-905a57155faa h1:uIJ7KxPgS7ODNO//HqlPfjWmWDGRsoONAVcEVaJNWNs=
golang.org/x/text v0.0.0-20180911161511-905a57155faa/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
github.com/aclindsa/xml v0.0.0-20201125035057-bbd5c9ec99ac h1:xCNSfPWpcx3Sdz/+aB/Re4L8oA6Y4kRRRuTh1CHCDEw=
github.com/aclindsa/xml v0.0.0-20201125035057-bbd5c9ec99ac/go.mod h1:GjqOUT8xlg5+T19lFv6yAGNrtMKkZ839Gt4e16mBXlY=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -103,7 +103,7 @@ type InvSell struct {
Taxes Amount `xml:"TAXES,omitempty"`
Fees Amount `xml:"FEES,omitempty"`
Load Amount `xml:"LOAD,omitempty"`
Witholding Amount `xml:"WITHHOLDING,omitempty"` // Federal tax witholdings
Withholding Amount `xml:"WITHHOLDING,omitempty"` // Federal tax withholdings
TaxExempt Boolean `xml:"TAXEXEMPT,omitempty"` // Tax-exempt transaction
Total Amount `xml:"TOTAL"` // Transaction total. Buys, sells, etc.:((quan. * (price +/- markup/markdown)) +/-(commission + fees + load + taxes + penalty + withholding + statewithholding)). Distributions, interest, margin interest, misc. expense, etc.: amount. Return of cap: cost basis
Gain Amount `xml:"GAIN,omitempty"` // Total gain
@ -113,7 +113,7 @@ type InvSell struct {
SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER
LoanID String `xml:"LOANID,omitempty"` // For 401(k) accounts only. Indicates that the transaction was due to a loan or a loan repayment, and which loan it was
StateWitholding Amount `xml:"STATEWITHHOLDING,omitempty"` // State tax witholdings
StateWithholding Amount `xml:"STATEWITHHOLDING,omitempty"` // State tax withholdings
Penalty Amount `xml:"PENALTY,omitempty"` // Amount withheld due to penalty
Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST
@ -131,6 +131,10 @@ func (t BuyDebt) TransactionType() string {
return "BUYDEBT"
}
func (t BuyDebt) InvTransaction() InvTran {
return t.InvBuy.InvTran
}
// BuyMF represents a transaction purchasing a mutual fund
type BuyMF struct {
XMLName xml.Name `xml:"BUYMF"`
@ -144,6 +148,10 @@ func (t BuyMF) TransactionType() string {
return "BUYMF"
}
func (t BuyMF) InvTransaction() InvTran {
return t.InvBuy.InvTran
}
// BuyOpt represents a transaction purchasing an option
type BuyOpt struct {
XMLName xml.Name `xml:"BUYOPT"`
@ -157,6 +165,10 @@ func (t BuyOpt) TransactionType() string {
return "BUYOPT"
}
func (t BuyOpt) InvTransaction() InvTran {
return t.InvBuy.InvTran
}
// BuyOther represents a transaction purchasing a type of security not covered
// by the other Buy* structs
type BuyOther struct {
@ -169,6 +181,10 @@ func (t BuyOther) TransactionType() string {
return "BUYOTHER"
}
func (t BuyOther) InvTransaction() InvTran {
return t.InvBuy.InvTran
}
// BuyStock represents a transaction purchasing stock
type BuyStock struct {
XMLName xml.Name `xml:"BUYSTOCK"`
@ -181,6 +197,10 @@ func (t BuyStock) TransactionType() string {
return "BUYSTOCK"
}
func (t BuyStock) InvTransaction() InvTran {
return t.InvBuy.InvTran
}
// ClosureOpt represents a transaction closing a position for an option
type ClosureOpt struct {
XMLName xml.Name `xml:"CLOSUREOPT"`
@ -199,6 +219,10 @@ func (t ClosureOpt) TransactionType() string {
return "CLOSUREOPT"
}
func (t ClosureOpt) InvTransaction() InvTran {
return t.InvTran
}
// Income represents a transaction where investment income is being realized as
// cash into the investment account
type Income struct {
@ -210,7 +234,7 @@ type Income struct {
SubAcctSec subAcctType `xml:"SUBACCTSEC"` // Sub-account type for this security. One of CASH, MARGIN, SHORT, OTHER
SubAcctFund subAcctType `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER
TaxExempt Boolean `xml:"TAXEXEMPT,omitempty"` // Tax-exempt transaction
Witholding Amount `xml:"WITHHOLDING,omitempty"` // Federal tax witholdings
Withholding Amount `xml:"WITHHOLDING,omitempty"` // Federal tax withholdings
Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency this transaction is in (instead of CURDEF in INVSTMTRS) if Valid()
OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency this transaction was converted to INVSTMTRS' CURDEF from if Valid
Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // Source of money for this transaction. One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST
@ -221,6 +245,10 @@ func (t Income) TransactionType() string {
return "INCOME"
}
func (t Income) InvTransaction() InvTran {
return t.InvTran
}
// InvExpense represents a transaction realizing an expense associated with an
// investment
type InvExpense struct {
@ -240,6 +268,10 @@ func (t InvExpense) TransactionType() string {
return "INVEXPENSE"
}
func (t InvExpense) InvTransaction() InvTran {
return t.InvTran
}
// JrnlFund represents a transaction journaling cash holdings between
// sub-accounts within the same investment account
type JrnlFund struct {
@ -255,6 +287,10 @@ func (t JrnlFund) TransactionType() string {
return "JRNLFUND"
}
func (t JrnlFund) InvTransaction() InvTran {
return t.InvTran
}
// JrnlSec represents a transaction journaling security holdings between
// sub-accounts within the same investment account
type JrnlSec struct {
@ -271,6 +307,10 @@ func (t JrnlSec) TransactionType() string {
return "JRNLSEC"
}
func (t JrnlSec) InvTransaction() InvTran {
return t.InvTran
}
// MarginInterest represents a transaction realizing a margin interest expense
type MarginInterest struct {
XMLName xml.Name `xml:"MARGININTEREST"`
@ -286,6 +326,10 @@ func (t MarginInterest) TransactionType() string {
return "MARGININTEREST"
}
func (t MarginInterest) InvTransaction() InvTran {
return t.InvTran
}
// Reinvest is a single transaction that contains both income and an investment
// transaction. If servers cant track this as a single transaction they should
// return an Income transaction and an InvTran.
@ -313,6 +357,10 @@ func (t Reinvest) TransactionType() string {
return "REINVEST"
}
func (t Reinvest) InvTransaction() InvTran {
return t.InvTran
}
// RetOfCap represents a transaction where capital is being returned to the
// account holder
type RetOfCap struct {
@ -332,6 +380,10 @@ func (t RetOfCap) TransactionType() string {
return "RETOFCAP"
}
func (t RetOfCap) InvTransaction() InvTran {
return t.InvTran
}
// SellDebt represents the sale of a debt security. Used when debt is sold,
// called, or reaches maturity.
type SellDebt struct {
@ -346,6 +398,10 @@ func (t SellDebt) TransactionType() string {
return "SELLDEBT"
}
func (t SellDebt) InvTransaction() InvTran {
return t.InvSell.InvTran
}
// SellMF represents a transaction selling a mutual fund
type SellMF struct {
XMLName xml.Name `xml:"SELLMF"`
@ -360,6 +416,10 @@ func (t SellMF) TransactionType() string {
return "SELLMF"
}
func (t SellMF) InvTransaction() InvTran {
return t.InvSell.InvTran
}
// SellOpt represents a transaction selling an option. Depending on the value
// of OptSellType, can be used to sell a previously bought option or write a
// new option.
@ -378,6 +438,10 @@ func (t SellOpt) TransactionType() string {
return "SELLOPT"
}
func (t SellOpt) InvTransaction() InvTran {
return t.InvSell.InvTran
}
// SellOther represents a transaction selling a security type not covered by
// the other Sell* structs
type SellOther struct {
@ -390,6 +454,10 @@ func (t SellOther) TransactionType() string {
return "SELLOTHER"
}
func (t SellOther) InvTransaction() InvTran {
return t.InvSell.InvTran
}
// SellStock represents a transaction selling stock
type SellStock struct {
XMLName xml.Name `xml:"SELLSTOCK"`
@ -402,6 +470,10 @@ func (t SellStock) TransactionType() string {
return "SELLSTOCK"
}
func (t SellStock) InvTransaction() InvTran {
return t.InvSell.InvTran
}
// Split represents a stock or mutual fund split
type Split struct {
XMLName xml.Name `xml:"SPLIT"`
@ -424,6 +496,10 @@ func (t Split) TransactionType() string {
return "SPLIT"
}
func (t Split) InvTransaction() InvTran {
return t.InvTran
}
// Transfer represents the transfer of securities into or out of an account
type Transfer struct {
XMLName xml.Name `xml:"TRANSFER"`
@ -445,10 +521,15 @@ func (t Transfer) TransactionType() string {
return "TRANSFER"
}
func (t Transfer) InvTransaction() InvTran {
return t.InvTran
}
// InvTransaction is a generic interface met by all investment transactions
// (Buy*, Sell*, & co.)
type InvTransaction interface {
TransactionType() string
InvTransaction() InvTran
}
// InvBankTransaction is a banking transaction performed in an investment
@ -764,6 +845,7 @@ type InvPosition struct {
// Position is an interface satisfied by all the other *Position types
type Position interface {
PositionType() string
InvPosition() InvPosition
}
// DebtPosition represents a position held in a debt security
@ -777,6 +859,11 @@ func (p DebtPosition) PositionType() string {
return "POSDEBT"
}
// InvPosition returns InvPos
func (p DebtPosition) InvPosition() InvPosition {
return p.InvPos
}
// MFPosition represents a position held in a mutual fund
type MFPosition struct {
XMLName xml.Name `xml:"POSMF"`
@ -792,6 +879,11 @@ func (p MFPosition) PositionType() string {
return "POSMF"
}
// InvPosition returns InvPos
func (p MFPosition) InvPosition() InvPosition {
return p.InvPos
}
// OptPosition represents a position held in an option
type OptPosition struct {
XMLName xml.Name `xml:"POSOPT"`
@ -804,6 +896,11 @@ func (p OptPosition) PositionType() string {
return "POSOPT"
}
// InvPosition returns InvPos
func (p OptPosition) InvPosition() InvPosition {
return p.InvPos
}
// OtherPosition represents a position held in a security type not covered by
// the other *Position elements
type OtherPosition struct {
@ -816,6 +913,11 @@ func (p OtherPosition) PositionType() string {
return "POSOTHER"
}
// InvPosition returns InvPos
func (p OtherPosition) InvPosition() InvPosition {
return p.InvPos
}
// StockPosition represents a position held in a stock
type StockPosition struct {
XMLName xml.Name `xml:"POSSTOCK"`
@ -830,6 +932,11 @@ func (p StockPosition) PositionType() string {
return "POSSTOCK"
}
// InvPosition returns InvPos
func (p StockPosition) InvPosition() InvPosition {
return p.InvPos
}
// PositionList represents a list of positions held in securities in an
// investment account
type PositionList []Position
@ -885,12 +992,12 @@ func (p *PositionList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
}
// MarshalXML handles marshalling a PositionList to an XML string
func (p *PositionList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (p PositionList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
invPosListElement := xml.StartElement{Name: xml.Name{Local: "INVPOSLIST"}}
if err := e.EncodeToken(invPosListElement); err != nil {
return err
}
for _, position := range *p {
for _, position := range p {
start := xml.StartElement{Name: xml.Name{Local: position.PositionType()}}
switch pos := position.(type) {
case DebtPosition:
@ -1190,12 +1297,12 @@ func (o *OOList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
}
// MarshalXML handles marshalling an OOList to an XML string
func (o *OOList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (o OOList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
ooListElement := xml.StartElement{Name: xml.Name{Local: "INVOOLIST"}}
if err := e.EncodeToken(ooListElement); err != nil {
return err
}
for _, openorder := range *o {
for _, openorder := range o {
start := xml.StartElement{Name: xml.Name{Local: openorder.OrderType()}}
switch oo := openorder.(type) {
case OOBuyDebt:

View File

@ -1,11 +1,12 @@
package ofxgo
import (
"github.com/aclindsa/xml"
"reflect"
"strings"
"testing"
"time"
"github.com/aclindsa/xml"
)
func TestMarshalInvStatementRequest(t *testing.T) {
@ -1866,3 +1867,84 @@ func TestUnmarshalOOList(t *testing.T) {
}
checkEqual(t, "OOList", reflect.ValueOf(&expected), reflect.ValueOf(&actual))
}
func TestSecurityInfo(t *testing.T) {
secInfo := SecInfo{
Ticker: "ABC",
}
tests := []Security{
DebtInfo{SecInfo: secInfo},
MFInfo{SecInfo: secInfo},
OptInfo{SecInfo: secInfo},
OtherInfo{SecInfo: secInfo},
StockInfo{SecInfo: secInfo},
}
for _, tc := range tests {
t.Run(tc.SecurityType(), func(t *testing.T) {
info := tc.SecurityInfo()
if info.Ticker != secInfo.Ticker {
t.Errorf("got %v, want %v", info, secInfo)
}
})
}
}
func TestInvPosition(t *testing.T) {
invPos := InvPosition{
Memo: "stuff",
}
tests := []Position{
DebtPosition{InvPos: invPos},
MFPosition{InvPos: invPos},
OptPosition{InvPos: invPos},
OtherPosition{InvPos: invPos},
StockPosition{InvPos: invPos},
}
for _, tc := range tests {
t.Run(tc.PositionType(), func(t *testing.T) {
pos := tc.InvPosition()
if pos.Memo != invPos.Memo {
t.Errorf("got %v, want %v", pos, invPos)
}
})
}
}
func TestInvTransaction(t *testing.T) {
invTran := InvTran{
Memo: "stuff",
}
tests := []InvTransaction{
BuyDebt{InvBuy: InvBuy{InvTran: invTran}},
BuyMF{InvBuy: InvBuy{InvTran: invTran}},
BuyOpt{InvBuy: InvBuy{InvTran: invTran}},
BuyOther{InvBuy: InvBuy{InvTran: invTran}},
BuyStock{InvBuy: InvBuy{InvTran: invTran}},
ClosureOpt{InvTran: invTran},
Income{InvTran: invTran},
InvExpense{InvTran: invTran},
JrnlFund{InvTran: invTran},
JrnlSec{InvTran: invTran},
MarginInterest{InvTran: invTran},
Reinvest{InvTran: invTran},
RetOfCap{InvTran: invTran},
SellDebt{InvSell: InvSell{InvTran: invTran}},
SellMF{InvSell: InvSell{InvTran: invTran}},
SellOpt{InvSell: InvSell{InvTran: invTran}},
SellOther{InvSell: InvSell{InvTran: invTran}},
SellStock{InvSell: InvSell{InvTran: invTran}},
Split{InvTran: invTran},
Transfer{InvTran: invTran},
}
for _, tc := range tests {
t.Run(tc.TransactionType(), func(t *testing.T) {
tran := tc.InvTransaction()
if tran.Memo != invTran.Memo {
t.Errorf("got %v, want %v", tran, invTran)
}
})
}
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"io"
"reflect"
"regexp"
"strings"
"github.com/aclindsa/xml"
@ -35,78 +36,75 @@ type Response struct {
}
func (or *Response) readSGMLHeaders(r *bufio.Reader) error {
var seenHeader, seenVersion bool = false, false
for {
// Some financial institutions do not properly leave an empty line after the last header.
// Avoid attempting to read another header in that case.
next, err := r.Peek(1)
b, err := r.ReadSlice('<')
if err != nil {
return err
}
if next[0] == '<' {
break
}
line, err := r.ReadString('\n')
s := string(b)
err = r.UnreadByte()
if err != nil {
return err
}
// r.ReadString leaves the '\n' on the end...
line = strings.TrimSpace(line)
if len(line) == 0 {
if seenHeader {
break
} else {
continue
}
}
header := strings.SplitN(line, ":", 2)
if header == nil || len(header) != 2 {
// According to the latest OFX SGML spec (1.6), headers should be CRLF-separated
// and written as KEY:VALUE. However, some banks include a whitespace after the
// colon (KEY: VALUE), while others include no line breaks at all. The spec doesn't
// require a line break after the OFX headers, but it is allowed, and will be
// optionally captured & discarded by the trailing `\s*`. Valid SGML headers must
// always be present in exactly this order, so a regular expression is acceptable.
headerExp := regexp.MustCompile(
`^OFXHEADER:\s*(?P<OFXHEADER>\d+)\s*` +
`DATA:\s*(?P<DATA>[A-Z]+)\s*` +
`VERSION:\s*(?P<VERSION>\d+)\s*` +
`SECURITY:\s*(?P<SECURITY>[\w]+)\s*` +
`ENCODING:\s*(?P<ENCODING>[A-Z0-9-]+)\s*` +
`CHARSET:\s*(?P<CHARSET>[\w-]+)\s*` +
`COMPRESSION:\s*(?P<COMPRESSION>[A-Z]+)\s*` +
`OLDFILEUID:\s*(?P<OLDFILEUID>[\w-]+)\s*` +
`NEWFILEUID:\s*(?P<NEWFILEUID>[\w-]+)\s*<$`)
matches := headerExp.FindStringSubmatch(s)
if len(matches) == 0 {
return errors.New("OFX headers malformed")
}
// Some OFX servers put a space after the colon
headervalue := strings.TrimSpace(header[1])
for i, name := range headerExp.SubexpNames() {
if i == 0 {
continue
}
switch header[0] {
headerValue := matches[i]
switch name {
case "OFXHEADER":
if headervalue != "100" {
if headerValue != "100" {
return errors.New("OFXHEADER is not 100")
}
seenHeader = true
case "DATA":
if headervalue != "OFXSGML" {
if headerValue != "OFXSGML" {
return errors.New("OFX DATA header does not contain OFXSGML")
}
case "VERSION":
err := or.Version.FromString(headervalue)
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" {
return errors.New("OFX SECURITY header not NONE")
if !(headerValue == "NONE" || headerValue == "TYPE1") {
return errors.New("OFX SECURITY header must be NONE or TYPE1")
}
case "COMPRESSION":
if headervalue != "NONE" {
if headerValue != "NONE" {
return errors.New("OFX COMPRESSION header not NONE")
}
case "ENCODING", "CHARSET", "OLDFILEUID", "NEWFILEUID":
// TODO check/handle these headers?
default:
return errors.New("Invalid OFX header: " + header[0])
// TODO: check/handle these headers?
}
}
if !seenVersion {
return errors.New("OFX VERSION header missing")
}
return nil
}

View File

@ -176,8 +176,7 @@ func TestValidSamples(t *testing.T) {
func TestInvalidResponse(t *testing.T) {
// in this example, the severity is invalid due to mixed upper and lower case letters
const invalidResponse = `
OFXHEADER:100
const invalidResponse = `OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:NONE

View File

@ -0,0 +1 @@
OFXHEADER:100DATA:OFXSGMLVERSION:102SECURITY:NONEENCODING:USASCIICHARSET:1252COMPRESSION:NONEOLDFILEUID:NONENEWFILEUID:NONE<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO<MESSAGE>SUCCESS</STATUS><DTSERVER>20210102211014.201[-8:PST]<LANGUAGE>ENG<FI><ORG>WF<FID>1000</FI><SESSCOOKIE>abc-123<INTU.BID>1000<INTU.USERID>jane_doe</SONRS></SIGNONMSGSRSV1><BANKMSGSRSV1><STMTTRNRS><TRNUID>0<STATUS><CODE>0<SEVERITY>INFO<MESSAGE>SUCCESS</STATUS><STMTRS><CURDEF>USD<BANKACCTFROM><BANKID>123456789<ACCTID>9876543210<ACCTTYPE>CHECKING</BANKACCTFROM><BANKTRANLIST><DTSTART>20201201120000.000[-8:PST]<DTEND>20201231120000.000[-8:PST]<STMTTRN><TRNTYPE>DIRECTDEBIT<DTPOSTED>20201201120000.000[-8:PST]<TRNAMT>-12.34<FITID>202012011<NAME>AE Visa Card AE EPAY<MEMO> XXXXX1234</STMTTRN></BANKTRANLIST><LEDGERBAL><BALAMT>123.45<DTASOF>20201231120000.000[-8:PST]</LEDGERBAL><AVAILBAL><BALAMT>123.45<DTASOF>20201231120000.000[-8:PST]</AVAILBAL></STMTRS></STMTTRNRS></BANKMSGSRSV1></OFX>

View File

@ -0,0 +1,75 @@
OFXHEADER:100
DATA:OFXSGML
VERSION:103
SECURITY:TYPE1
ENCODING:USASCII
CHARSET:1252
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
<OFX>
<SIGNONMSGSRSV1><SONRS>
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>
<DTSERVER>20170407001840.607[0:GMT]
<LANGUAGE>ENG
<FI>
<ORG>UJKDO
<FID>3534
</FI>
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID>e1707dfd-695d-4451-8d9c-0e142fdc456a
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>
<STMTRS>
<CURDEF>USD
<BANKACCTFROM>
<BANKID>598813374
<ACCTID>35342483513
<ACCTTYPE>MONEYMRKT
</BANKACCTFROM>
<BANKTRANLIST>
<DTSTART>20170107011841.262[0:GMT]
<DTEND>20170407001841.262[0:GMT]
<STMTTRN>
<TRNTYPE>CREDIT
<DTPOSTED>20170117120000.000[0:GMT]
<TRNAMT>-995.4190396554627
<FITID>2fb2640c-cee3-4643-8ba3-ea21a4d18954
<NAME>Dividend Earned
</STMTTRN>
<STMTTRN>
<TRNTYPE>CREDIT
<DTPOSTED>20170215120000.000[0:GMT]
<TRNAMT>788.5385340523635
<FITID>c9d856df-339c-47c6-9f6a-8c2e2910f62e
<NAME>Dividend Earned
</STMTTRN>
<STMTTRN>
<TRNTYPE>CREDIT
<DTPOSTED>20170315120000.000[0:GMT]
<TRNAMT>3070.1328011762807
<FITID>1107ace0-048b-4c0c-b5f3-45b6be4cd71d
<NAME>Dividend Earned
</STMTTRN>
</BANKTRANLIST>
<LEDGERBAL>
<BALAMT>2607.1664944585727
<DTASOF>20170407001841.262[0:GMT]
</LEDGERBAL>
<AVAILBAL>
<BALAMT>4503.683156768119
<DTASOF>20170407001841.262[0:GMT]
</AVAILBAL>
</STMTRS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>

View File

@ -94,6 +94,7 @@ func (r *SecListResponse) Type() messageType {
// securities for SecurityList
type Security interface {
SecurityType() string
SecurityInfo() SecInfo
}
// SecInfo represents the generic information about a security. It is included
@ -136,6 +137,11 @@ func (i DebtInfo) SecurityType() string {
return "DEBTINFO"
}
// SecurityInfo returns SecInfo
func (i DebtInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// AssetPortion represents the percentage of a mutual fund with the given asset
// classification
type AssetPortion struct {
@ -169,6 +175,11 @@ func (i MFInfo) SecurityType() string {
return "MFINFO"
}
// SecurityInfo returns SecInfo
func (i MFInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// OptInfo provides information about an option
type OptInfo struct {
XMLName xml.Name `xml:"OPTINFO"`
@ -187,6 +198,11 @@ func (i OptInfo) SecurityType() string {
return "OPTINFO"
}
// SecurityInfo returns SecInfo
func (i OptInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// OtherInfo provides information about a security type not covered by the
// other *Info elements
type OtherInfo struct {
@ -202,6 +218,11 @@ func (i OtherInfo) SecurityType() string {
return "OTHERINFO"
}
// SecurityInfo returns SecInfo
func (i OtherInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// StockInfo provides information about a security type
type StockInfo struct {
XMLName xml.Name `xml:"STOCKINFO"`
@ -218,6 +239,11 @@ func (i StockInfo) SecurityType() string {
return "STOCKINFO"
}
// SecurityInfo returns SecInfo
func (i StockInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// SecurityList is a container for Security objects containaing information
// about securities
type SecurityList struct {

View File

@ -14,6 +14,7 @@ type SignonRequest struct {
UserID String `xml:"USERID"`
UserPass String `xml:"USERPASS,omitempty"`
UserKey String `xml:"USERKEY,omitempty"`
GenUserKey Boolean `xml:"GENUSERKEY,omitempty"`
Language String `xml:"LANGUAGE"` // Defaults to ENG
Org String `xml:"FI>ORG"`
Fid String `xml:"FI>FID"`

View File

@ -76,7 +76,7 @@ func (a Amount) String() string {
}
// MarshalXML marshals an Amount to SGML/XML
func (a *Amount) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (a Amount) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(a.String(), start)
}
@ -188,7 +188,7 @@ func (od Date) String() string {
}
// MarshalXML marshals a Date to XML
func (od *Date) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (od Date) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(od.String(), start)
}
@ -260,8 +260,8 @@ func (ob *Boolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
}
// MarshalXML marshals a Boolean to XML
func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if *ob {
func (ob Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if ob {
return e.EncodeElement("Y", start)
}
return e.EncodeElement("N", start)
@ -358,7 +358,7 @@ func (c *CurrSymbol) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
}
// MarshalXML marshals a CurrSymbol to SGML/XML
func (c *CurrSymbol) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
func (c CurrSymbol) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(c.String(), start)
}

View File

@ -91,6 +91,13 @@ func TestMarshalAmount(t *testing.T) {
marshalHelper(t, "-768276587425", &a)
a.SetFrac64(1, 12)
marshalHelper(t, "0.0833333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333", &a)
type AmountStruct struct {
A Amount
}
var as AmountStruct
as.A.SetFrac64(1, 8)
marshalHelper(t, "<A>0.125</A>", as)
}
func TestUnmarshalAmount(t *testing.T) {
@ -155,7 +162,7 @@ func TestAmountEqual(t *testing.T) {
func TestMarshalDate(t *testing.T) {
var d *Date
UTC := time.FixedZone("UTC", 0)
GMT_nodesc := time.FixedZone("", 0)
GMTNodesc := time.FixedZone("", 0)
EST := time.FixedZone("EST", -5*60*60)
NPT := time.FixedZone("NPT", (5*60+45)*60)
IST := time.FixedZone("IST", (5*60+30)*60)
@ -183,8 +190,15 @@ func TestMarshalDate(t *testing.T) {
marshalHelper(t, "20170314000026.053[-3.50:NST]", d)
// Time zone without textual description
d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT_nodesc)
d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMTNodesc)
marshalHelper(t, "20170314150926.053[0]", d)
type DateStruct struct {
D Date
}
d = NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
ds := DateStruct{D: *d}
marshalHelper(t, "<D>20170314150926.053[0:GMT]</D>", ds)
}
func TestUnmarshalDate(t *testing.T) {
@ -195,7 +209,7 @@ func TestUnmarshalDate(t *testing.T) {
NPT := time.FixedZone("NPT", (5*60+45)*60)
IST := time.FixedZone("IST", (5*60+30)*60)
NST := time.FixedZone("NST", -(3*60+30)*60)
NST_nodesc := time.FixedZone("", -(3*60+30)*60)
NSTNodesc := time.FixedZone("", -(3*60+30)*60)
eq := func(a, b interface{}) bool {
if dateA, ok := a.(*Date); ok {
@ -245,7 +259,7 @@ func TestUnmarshalDate(t *testing.T) {
d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT)
unmarshalHelper2(t, "20170314150926.053[0]", d, &overwritten, eq)
// but not for others:
d = NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NST_nodesc)
d = NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NSTNodesc)
unmarshalHelper2(t, "20170314000026.053[-3.50]", d, &overwritten, eq)
// Make sure we handle poorly-formatted dates (from Vanguard)
@ -316,11 +330,24 @@ func TestUnmarshalString(t *testing.T) {
unmarshalHelper(t, "Some Name\n ", &s, &overwritten)
}
func TestStringString(t *testing.T) {
var s String = "foobar"
if s.String() != "foobar" {
t.Fatalf("Unexpected result when returning String.String(): %s\n", s.String())
}
}
func TestMarshalBoolean(t *testing.T) {
var b Boolean = true
marshalHelper(t, "Y", &b)
b = false
marshalHelper(t, "N", &b)
type BooleanStruct struct {
B Boolean
}
bs := BooleanStruct{B: true}
marshalHelper(t, "<B>Y</B>", bs)
}
func TestUnmarshalBoolean(t *testing.T) {
@ -333,6 +360,17 @@ func TestUnmarshalBoolean(t *testing.T) {
unmarshalHelper(t, "N\n \t", &b, &overwritten)
}
func TestStringBoolean(t *testing.T) {
var b Boolean = true
if b.String() != "true" {
t.Fatalf("Unexpected string for Boolean.String() for true: %s\n", b.String())
}
b = false
if b.String() != "false" {
t.Fatalf("Unexpected string for Boolean.String() for false: %s\n", b.String())
}
}
func TestMarshalUID(t *testing.T) {
var u UID = "d1cf3d3d-9ef9-4a97-b180-81706829cb04"
marshalHelper(t, "d1cf3d3d-9ef9-4a97-b180-81706829cb04", &u)
@ -394,6 +432,12 @@ func TestRandomUID(t *testing.T) {
func TestMarshalCurrSymbol(t *testing.T) {
c, _ := NewCurrSymbol("USD")
marshalHelper(t, "USD", &c)
type CurrSymbolStruct struct {
CS CurrSymbol
}
css := CurrSymbolStruct{CS: *c}
marshalHelper(t, "<CS>USD</CS>", css)
}
func TestUnmarshalCurrSymbol(t *testing.T) {

View File

@ -15,7 +15,7 @@ type VanguardClient struct {
}
// NewVanguardClient returns a Client interface configured to handle Vanguard's
// brand of idiosyncracy
// brand of idiosyncrasy
func NewVanguardClient(bc *BasicClient) Client {
return &VanguardClient{bc}
}
@ -47,6 +47,8 @@ func rawRequestCookies(URL string, r io.Reader, cookies []*http.Cookie) (*http.R
return response, nil
}
// RequestNoParse marshals a Request to XML, makes an HTTP request, and returns
// the raw HTTP response
func (c *VanguardClient) RequestNoParse(r *Request) (*http.Response, error) {
r.SetClientFields(c)
@ -62,7 +64,7 @@ func (c *VanguardClient) RequestNoParse(r *Request) (*http.Response, error) {
// Fortunately, the initial response contains the cookie we need, so if we
// detect an empty response with cookies set that didn't have any errors,
// re-try the request while sending their cookies back to them.
if err == nil && response.ContentLength <= 0 && len(response.Cookies()) > 0 {
if response != nil && response.ContentLength <= 0 && len(response.Cookies()) > 0 {
b, err = r.Marshal()
if err != nil {
return nil, err
@ -74,6 +76,8 @@ func (c *VanguardClient) RequestNoParse(r *Request) (*http.Response, error) {
return response, err
}
// Request marshals a Request to XML, makes an HTTP request, and then
// unmarshals the response into a Response object.
func (c *VanguardClient) Request(r *Request) (*Response, error) {
return clientRequest(c, r)
}