Add BasicClient, update Client to be interface

This paves the way for more easily implementing different clients for
different financial institutions
This commit is contained in:
Aaron Lindsay 2018-10-02 20:55:25 -04:00
parent 88e5521348
commit 94a77ac754
11 changed files with 45 additions and 31 deletions

View File

@ -44,7 +44,7 @@ The following code snippet demonstrates how to use OFXGo to query and parse
OFX code from a checking account, printing the balance and returned transactions: OFX code from a checking account, printing the balance and returned transactions:
```go ```go
client := ofxgo.Client{} // Accept the default Client settings client := ofxgo.BasicClient{} // Accept the default Client settings
// These values are specific to your bank // These values are specific to your bank
var query ofxgo.Request var query ofxgo.Request

View File

@ -42,7 +42,7 @@ func TestMarshalBankStatementRequest(t *testing.T) {
</BANKMSGSRQV1> </BANKMSGSRQV1>
</OFX>` </OFX>`
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: "OFXGO", AppID: "OFXGO",
AppVer: "0001", AppVer: "0001",
SpecVersion: ofxgo.OfxVersion203, SpecVersion: ofxgo.OfxVersion203,
@ -116,7 +116,7 @@ NEWFILEUID:NONE
</BANKMSGSRQV1> </BANKMSGSRQV1>
</OFX>` </OFX>`
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: "OFXGO", AppID: "OFXGO",
AppVer: "0001", AppVer: "0001",
SpecVersion: ofxgo.OfxVersion103, SpecVersion: ofxgo.OfxVersion103,

View File

@ -8,11 +8,25 @@ import (
) )
// Client serves to aggregate OFX client settings that may be necessary to talk // Client serves to aggregate OFX client settings that may be necessary to talk
// to a particular server due to quirks in that server's implementation. Client // to a particular server due to quirks in that server's implementation.
// also provides the Request, RequestNoParse, and RawRequest helper methods to // Client also provides the Request and RequestNoParse helper methods to aid in
// aid in making and parsing requests. Client uses default, non-zero settings, // making and parsing requests.
// even if its fields are not initialized. type Client interface {
type Client struct { // Used to fill out a Request object
OfxVersion() ofxVersion
ID() String
Version() String
IndentRequests() bool
// Used to initiate requests to servers
Request(r *Request) (*Response, error)
RequestNoParse(r *Request) (*http.Response, error)
}
// BasicClient provides a standard Client implementation suitable for most
// financial institutions. BasicClient uses default, non-zero settings, even if
// its fields are not initialized.
type BasicClient struct {
// Request fields to overwrite with the client's values. If nonempty, // Request fields to overwrite with the client's values. If nonempty,
// defaults are used // defaults are used
SpecVersion ofxVersion // VERSION in header SpecVersion ofxVersion // VERSION in header
@ -23,27 +37,27 @@ type Client struct {
NoIndent bool NoIndent bool
} }
// OfxVersion returns the OFX specification version this Client will marshal // OfxVersion returns the OFX specification version this BasicClient will marshal
// Requests as. Defaults to "203" if the client's SpecVersion field is empty. // Requests as. Defaults to "203" if the client's SpecVersion field is empty.
func (c *Client) OfxVersion() ofxVersion { func (c *BasicClient) OfxVersion() ofxVersion {
if c.SpecVersion.Valid() { if c.SpecVersion.Valid() {
return c.SpecVersion return c.SpecVersion
} }
return OfxVersion203 return OfxVersion203
} }
// ID returns this Client's OFX AppID field, defaulting to "OFXGO" if // ID returns this BasicClient's OFX AppID field, defaulting to "OFXGO" if
// unspecified. // unspecified.
func (c *Client) ID() String { func (c *BasicClient) ID() String {
if len(c.AppID) > 0 { if len(c.AppID) > 0 {
return String(c.AppID) return String(c.AppID)
} }
return String("OFXGO") return String("OFXGO")
} }
// Version returns this Client's version number as a string, defaulting to // Version returns this BasicClient's version number as a string, defaulting to
// "0001" if unspecified. // "0001" if unspecified.
func (c *Client) Version() String { func (c *BasicClient) Version() String {
if len(c.AppVer) > 0 { if len(c.AppVer) > 0 {
return String(c.AppVer) return String(c.AppVer)
} }
@ -52,7 +66,7 @@ func (c *Client) Version() String {
// IndentRequests returns true if the marshaled XML should be indented (and // IndentRequests returns true if the marshaled XML should be indented (and
// contain newlines, since the two are linked in the current implementation) // contain newlines, since the two are linked in the current implementation)
func (c *Client) IndentRequests() bool { func (c *BasicClient) IndentRequests() bool {
return !c.NoIndent return !c.NoIndent
} }
@ -65,9 +79,9 @@ func (c *Client) IndentRequests() bool {
// like to try. // like to try.
// //
// Caveats: RawRequest does *not* take client settings into account as // Caveats: RawRequest does *not* take client settings into account as
// Request() does, so your particular server may or may not like whatever we // Client.Request() does, so your particular server may or may not like
// read from 'r'. The caller is responsible for closing the http Response.Body // whatever we read from 'r'. The caller is responsible for closing the http
// (see the http module's documentation for more information) // Response.Body (see the http module's documentation for more information)
func RawRequest(URL string, r io.Reader) (*http.Response, error) { func RawRequest(URL string, r io.Reader) (*http.Response, error) {
if !strings.HasPrefix(URL, "https://") { if !strings.HasPrefix(URL, "https://") {
return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol") return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
@ -119,7 +133,7 @@ func rawRequestCookies(URL string, r io.Reader, cookies []*http.Cookie) (*http.R
// //
// Caveat: The caller is responsible for closing the http Response.Body (see // Caveat: The caller is responsible for closing the http Response.Body (see
// the http module's documentation for more information) // the http module's documentation for more information)
func (c *Client) RequestNoParse(r *Request) (*http.Response, error) { func (c *BasicClient) RequestNoParse(r *Request) (*http.Response, error) {
r.SetClientFields(c) r.SetClientFields(c)
b, err := r.Marshal() b, err := r.Marshal()
@ -150,11 +164,11 @@ func (c *Client) RequestNoParse(r *Request) (*http.Response, error) {
// it's URL, and then unmarshals the response into a Response object. // it's URL, and then unmarshals the response into a Response object.
// //
// Before being marshaled, some of the the Request object's values are // Before being marshaled, some of the the Request object's values are
// overwritten, namely those dictated by the Client's configuration (Version, // overwritten, namely those dictated by the BasicClient's configuration (Version,
// AppID, AppVer fields), and the client's curren time (DtClient). These are // AppID, AppVer fields), and the client's current time (DtClient). These are
// updated in place in the supplied Request object so they may later be // updated in place in the supplied Request object so they may later be
// inspected by the caller. // inspected by the caller.
func (c *Client) Request(r *Request) (*Response, error) { func (c *BasicClient) Request(r *Request) (*Response, error) {
response, err := c.RequestNoParse(r) response, err := c.RequestNoParse(r)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -126,7 +126,7 @@ func tryProfile(appID, appVer, version string, noindent bool) bool {
fmt.Println("Error creating new OfxVersion enum:", err) fmt.Println("Error creating new OfxVersion enum:", err)
os.Exit(1) os.Exit(1)
} }
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: appID, AppID: appID,
AppVer: appVer, AppVer: appVer,
SpecVersion: ver, SpecVersion: ver,

View File

@ -6,13 +6,13 @@ import (
"os" "os"
) )
func newRequest() (*ofxgo.Client, *ofxgo.Request) { func newRequest() (ofxgo.Client, *ofxgo.Request) {
ver, err := ofxgo.NewOfxVersion(ofxVersion) ver, err := ofxgo.NewOfxVersion(ofxVersion)
if err != nil { if err != nil {
fmt.Println("Error creating new OfxVersion enum:", err) fmt.Println("Error creating new OfxVersion enum:", err)
os.Exit(1) os.Exit(1)
} }
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: appID, AppID: appID,
AppVer: appVer, AppVer: appVer,
SpecVersion: ver, SpecVersion: ver,

View File

@ -41,7 +41,7 @@ func TestMarshalCCStatementRequest(t *testing.T) {
</CREDITCARDMSGSRQV1> </CREDITCARDMSGSRQV1>
</OFX>` </OFX>`
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: "OFXGO", AppID: "OFXGO",
AppVer: "0001", AppVer: "0001",
SpecVersion: ofxgo.OfxVersion203, SpecVersion: ofxgo.OfxVersion203,

View File

@ -49,7 +49,7 @@ func TestMarshalInvStatementRequest(t *testing.T) {
</INVSTMTMSGSRQV1> </INVSTMTMSGSRQV1>
</OFX>` </OFX>`
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: "MYAPP", AppID: "MYAPP",
AppVer: "1234", AppVer: "1234",
SpecVersion: ofxgo.OfxVersion203, SpecVersion: ofxgo.OfxVersion203,

View File

@ -36,7 +36,7 @@ func TestMarshalProfileRequest(t *testing.T) {
</PROFMSGSRQV1> </PROFMSGSRQV1>
</OFX>` </OFX>`
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: "OFXGO", AppID: "OFXGO",
AppVer: "0001", AppVer: "0001",
SpecVersion: ofxgo.OfxVersion203, SpecVersion: ofxgo.OfxVersion203,

View File

@ -63,7 +63,7 @@ func marshalMessageSet(e *xml.Encoder, requests []Message, set messageType, vers
// SetClientFields overwrites the fields in this Request object controlled by // SetClientFields overwrites the fields in this Request object controlled by
// the Client // the Client
func (oq *Request) SetClientFields(c *Client) { func (oq *Request) SetClientFields(c Client) {
oq.Signon.DtClient.Time = time.Now() oq.Signon.DtClient.Time = time.Now()
// Overwrite fields that the client controls // Overwrite fields that the client controls

View File

@ -6,7 +6,7 @@ import (
) )
func TestMarshalInvalidSignons(t *testing.T) { func TestMarshalInvalidSignons(t *testing.T) {
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: "OFXGO", AppID: "OFXGO",
AppVer: "0001", AppVer: "0001",
SpecVersion: ofxgo.OfxVersion203, SpecVersion: ofxgo.OfxVersion203,

View File

@ -37,7 +37,7 @@ func TestMarshalAcctInfoRequest(t *testing.T) {
EST := time.FixedZone("EST", -5*60*60) EST := time.FixedZone("EST", -5*60*60)
var client = ofxgo.Client{ var client = ofxgo.BasicClient{
AppID: "OFXGO", AppID: "OFXGO",
AppVer: "0001", AppVer: "0001",
SpecVersion: ofxgo.OfxVersion203, SpecVersion: ofxgo.OfxVersion203,