mirror of
https://github.com/aclindsa/ofxgo.git
synced 2024-11-22 11:30:05 -05:00
Comment investments, profiles, seclist, signon, signup, and types
This commit is contained in:
parent
a1aec204a8
commit
1ff64a9d55
@ -468,7 +468,8 @@ type InvTranList struct {
|
|||||||
BankTransactions []InvBankTransaction
|
BankTransactions []InvBankTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalXML handles unmarshalling an InvTranList element from an XML string
|
// UnmarshalXML handles unmarshalling an InvTranList element from an SGML/XML
|
||||||
|
// string
|
||||||
func (l *InvTranList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (l *InvTranList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
for {
|
for {
|
||||||
tok, err := nextNonWhitespaceToken(d)
|
tok, err := nextNonWhitespaceToken(d)
|
||||||
|
34
profile.go
34
profile.go
@ -5,6 +5,9 @@ import (
|
|||||||
"github.com/aclindsa/go/src/encoding/xml"
|
"github.com/aclindsa/go/src/encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ProfileRequest represents a request for a server to provide a profile of its
|
||||||
|
// capabilities (which message sets and versions it supports, how to access
|
||||||
|
// them, which languages and which types of synchronization they support, etc.)
|
||||||
type ProfileRequest struct {
|
type ProfileRequest struct {
|
||||||
XMLName xml.Name `xml:"PROFTRNRQ"`
|
XMLName xml.Name `xml:"PROFTRNRQ"`
|
||||||
TrnUID UID `xml:"TRNUID"`
|
TrnUID UID `xml:"TRNUID"`
|
||||||
@ -12,26 +15,35 @@ type ProfileRequest struct {
|
|||||||
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
|
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
|
||||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||||
ClientRouting String `xml:"PROFRQ>CLIENTROUTING"` // Forced to NONE
|
ClientRouting String `xml:"PROFRQ>CLIENTROUTING"` // Forced to NONE
|
||||||
DtProfUp Date `xml:"PROFRQ>DTPROFUP"`
|
DtProfUp Date `xml:"PROFRQ>DTPROFUP"` // Date and time client last received a profile update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (r *ProfileRequest) Name() string {
|
func (r *ProfileRequest) Name() string {
|
||||||
return "PROFTRNRQ"
|
return "PROFTRNRQ"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||||
|
// into XML/SGML
|
||||||
func (r *ProfileRequest) Valid() (bool, error) {
|
func (r *ProfileRequest) Valid() (bool, error) {
|
||||||
// TODO implement
|
// TODO implement
|
||||||
r.ClientRouting = "NONE"
|
r.ClientRouting = "NONE"
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns which message set this message belongs to (which Request
|
||||||
|
// element of type []Message it should appended to)
|
||||||
func (r *ProfileRequest) Type() messageType {
|
func (r *ProfileRequest) Type() messageType {
|
||||||
return ProfRq
|
return ProfRq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignonInfo provides the requirements to login to a single signon realm. A
|
||||||
|
// signon realm consists of all MessageSets which can be accessed using one set
|
||||||
|
// of login credentials. Most FI's only use one signon realm to make it easier
|
||||||
|
// and less confusing for the user.
|
||||||
type SignonInfo struct {
|
type SignonInfo struct {
|
||||||
XMLName xml.Name `xml:"SIGNONINFO"`
|
XMLName xml.Name `xml:"SIGNONINFO"`
|
||||||
SignonRealm String `xml:"SIGNONREALM"`
|
SignonRealm String `xml:"SIGNONREALM"` // The SignonRealm for which this SignonInfo provides information. This SignonInfo is valid for all MessageSets with SignonRealm fields matching this one
|
||||||
Min Int `xml:"MIN"` // Minimum number of password characters
|
Min Int `xml:"MIN"` // Minimum number of password characters
|
||||||
Max Int `xml:"MAX"` // Maximum number of password characters
|
Max Int `xml:"MAX"` // Maximum number of password characters
|
||||||
CharType charType `xml:"CHARTYPE"` // One of ALPHAONLY, NUMERICONLY, ALPHAORNUMERIC, ALPHAANDNUMERIC
|
CharType charType `xml:"CHARTYPE"` // One of ALPHAONLY, NUMERICONLY, ALPHAORNUMERIC, ALPHAANDNUMERIC
|
||||||
@ -51,11 +63,13 @@ type SignonInfo struct {
|
|||||||
AccessTokenReq Boolean `xml:"ACCESSTOKENREQ,omitempty"` // Server requires ACCESSTOKEN to be sent with all requests except profile
|
AccessTokenReq Boolean `xml:"ACCESSTOKENREQ,omitempty"` // Server requires ACCESSTOKEN to be sent with all requests except profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageSet represents one message set supported by an FI and its
|
||||||
|
// capabilities
|
||||||
type MessageSet struct {
|
type MessageSet struct {
|
||||||
XMLName xml.Name // <xxxMSGSETVn>
|
XMLName xml.Name // <xxxMSGSETVn>
|
||||||
Name string // <xxxMSGSETVn> (copy of XMLName.Local)
|
Name string // <xxxMSGSETVn> (copy of XMLName.Local)
|
||||||
Ver Int `xml:"MSGSETCORE>VER"` // Message set version - should always match 'n' in <xxxMSGSETVn>
|
Ver Int `xml:"MSGSETCORE>VER"` // Message set version - should always match 'n' in <xxxMSGSETVn> of Name
|
||||||
Url String `xml:"MSGSETCORE>URL"` // URL where messages in this set are to be set
|
URL String `xml:"MSGSETCORE>URL"` // URL where messages in this set are to be set
|
||||||
OfxSec ofxSec `xml:"MSGSETCORE>OFXSEC"` // NONE or 'TYPE 1'
|
OfxSec ofxSec `xml:"MSGSETCORE>OFXSEC"` // NONE or 'TYPE 1'
|
||||||
TranspSec Boolean `xml:"MSGSETCORE>TRANSPSEC"` // Transport-level security must be used
|
TranspSec Boolean `xml:"MSGSETCORE>TRANSPSEC"` // Transport-level security must be used
|
||||||
SignonRealm String `xml:"MSGSETCORE>SIGNONREALM"` // Used to identify which SignonInfo to use for to this MessageSet
|
SignonRealm String `xml:"MSGSETCORE>SIGNONREALM"` // Used to identify which SignonInfo to use for to this MessageSet
|
||||||
@ -67,8 +81,11 @@ type MessageSet struct {
|
|||||||
// TODO MessageSet-specific stuff?
|
// TODO MessageSet-specific stuff?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageSetList is a list of MessageSets (necessary because they must be
|
||||||
|
// manually parsed)
|
||||||
type MessageSetList []MessageSet
|
type MessageSetList []MessageSet
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling a MessageSetList element from an XML string
|
||||||
func (msl *MessageSetList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (msl *MessageSetList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
for {
|
for {
|
||||||
var msgset MessageSet
|
var msgset MessageSet
|
||||||
@ -106,6 +123,11 @@ func (msl *MessageSetList) UnmarshalXML(d *xml.Decoder, start xml.StartElement)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProfileResponse contains a requested profile of the server's capabilities
|
||||||
|
// (which message sets and versions it supports, how to access them, which
|
||||||
|
// languages and which types of synchronization they support, etc.). Note that
|
||||||
|
// if the server does not support ClientRouting=NONE (as we always send with
|
||||||
|
// ProfileRequest), this may be an error)
|
||||||
type ProfileResponse struct {
|
type ProfileResponse struct {
|
||||||
XMLName xml.Name `xml:"PROFTRNRS"`
|
XMLName xml.Name `xml:"PROFTRNRS"`
|
||||||
TrnUID UID `xml:"TRNUID"`
|
TrnUID UID `xml:"TRNUID"`
|
||||||
@ -130,15 +152,19 @@ type ProfileResponse struct {
|
|||||||
Email String `xml:"PROFRS>EMAIL,omitempty"`
|
Email String `xml:"PROFRS>EMAIL,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (pr *ProfileResponse) Name() string {
|
func (pr *ProfileResponse) Name() string {
|
||||||
return "PROFTRNRS"
|
return "PROFTRNRS"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||||
func (pr *ProfileResponse) Valid() (bool, error) {
|
func (pr *ProfileResponse) Valid() (bool, error) {
|
||||||
//TODO implement
|
//TODO implement
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns which message set this message belongs to (which Response
|
||||||
|
// element of type []Message it belongs to)
|
||||||
func (pr *ProfileResponse) Type() messageType {
|
func (pr *ProfileResponse) Type() messageType {
|
||||||
return ProfRs
|
return ProfRs
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ NEWFILEUID:NONE
|
|||||||
ofxgo.MessageSet{
|
ofxgo.MessageSet{
|
||||||
Name: "SIGNONMSGSETV1",
|
Name: "SIGNONMSGSETV1",
|
||||||
Ver: 1,
|
Ver: 1,
|
||||||
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
|
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
|
||||||
OfxSec: ofxgo.OfxSecNone,
|
OfxSec: ofxgo.OfxSecNone,
|
||||||
TranspSec: true,
|
TranspSec: true,
|
||||||
SignonRealm: "Example Trade",
|
SignonRealm: "Example Trade",
|
||||||
@ -244,7 +244,7 @@ NEWFILEUID:NONE
|
|||||||
ofxgo.MessageSet{
|
ofxgo.MessageSet{
|
||||||
Name: "SIGNUPMSGSETV1",
|
Name: "SIGNUPMSGSETV1",
|
||||||
Ver: 1,
|
Ver: 1,
|
||||||
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
|
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
|
||||||
OfxSec: ofxgo.OfxSecNone,
|
OfxSec: ofxgo.OfxSecNone,
|
||||||
TranspSec: true,
|
TranspSec: true,
|
||||||
SignonRealm: "Example Trade",
|
SignonRealm: "Example Trade",
|
||||||
@ -256,7 +256,7 @@ NEWFILEUID:NONE
|
|||||||
ofxgo.MessageSet{
|
ofxgo.MessageSet{
|
||||||
Name: "INVSTMTMSGSETV1",
|
Name: "INVSTMTMSGSETV1",
|
||||||
Ver: 1,
|
Ver: 1,
|
||||||
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
|
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
|
||||||
OfxSec: ofxgo.OfxSecNone,
|
OfxSec: ofxgo.OfxSecNone,
|
||||||
TranspSec: true,
|
TranspSec: true,
|
||||||
SignonRealm: "Example Trade",
|
SignonRealm: "Example Trade",
|
||||||
@ -268,7 +268,7 @@ NEWFILEUID:NONE
|
|||||||
ofxgo.MessageSet{
|
ofxgo.MessageSet{
|
||||||
Name: "SECLISTMSGSETV1",
|
Name: "SECLISTMSGSETV1",
|
||||||
Ver: 1,
|
Ver: 1,
|
||||||
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
|
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
|
||||||
OfxSec: ofxgo.OfxSecNone,
|
OfxSec: ofxgo.OfxSecNone,
|
||||||
TranspSec: true,
|
TranspSec: true,
|
||||||
SignonRealm: "Example Trade",
|
SignonRealm: "Example Trade",
|
||||||
@ -280,7 +280,7 @@ NEWFILEUID:NONE
|
|||||||
ofxgo.MessageSet{
|
ofxgo.MessageSet{
|
||||||
Name: "PROFMSGSETV1",
|
Name: "PROFMSGSETV1",
|
||||||
Ver: 1,
|
Ver: 1,
|
||||||
Url: "https://ofx.example.com/cgi-ofx/exampleofx",
|
URL: "https://ofx.example.com/cgi-ofx/exampleofx",
|
||||||
OfxSec: ofxgo.OfxSecNone,
|
OfxSec: ofxgo.OfxSecNone,
|
||||||
TranspSec: true,
|
TranspSec: true,
|
||||||
SignonRealm: "Example Trade",
|
SignonRealm: "Example Trade",
|
||||||
|
@ -60,7 +60,8 @@ func marshalMessageSet(e *xml.Encoder, requests []Message, set messageType) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite the fields in this Request object controlled by the Client
|
// SetClientFields overwrites the fields in this Request object controlled by
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
48
seclist.go
48
seclist.go
@ -5,12 +5,17 @@ import (
|
|||||||
"github.com/aclindsa/go/src/encoding/xml"
|
"github.com/aclindsa/go/src/encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SecurityID identifies a security by its CUSIP (for US-based FI's, others may
|
||||||
|
// use UniqueID types other than CUSIP)
|
||||||
type SecurityID struct {
|
type SecurityID struct {
|
||||||
XMLName xml.Name `xml:"SECID"`
|
XMLName xml.Name `xml:"SECID"`
|
||||||
UniqueID String `xml:"UNIQUEID"` // CUSIP for US FI's
|
UniqueID String `xml:"UNIQUEID"` // CUSIP for US FI's
|
||||||
UniqueIDType String `xml:"UNIQUEIDTYPE"` // Should always be "CUSIP" for US FI's
|
UniqueIDType String `xml:"UNIQUEIDTYPE"` // Should always be "CUSIP" for US FI's
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityRequest represents a request for one security. It is specified with
|
||||||
|
// a SECID aggregate, a ticker symbol, or an FI assigned identifier (but no
|
||||||
|
// more than one of them at a time)
|
||||||
type SecurityRequest struct {
|
type SecurityRequest struct {
|
||||||
XMLName xml.Name `xml:"SECRQ"`
|
XMLName xml.Name `xml:"SECRQ"`
|
||||||
// Only one of the next three should be present
|
// Only one of the next three should be present
|
||||||
@ -19,6 +24,8 @@ type SecurityRequest struct {
|
|||||||
FiID String `xml:"FIID,omitempty"`
|
FiID String `xml:"FIID,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecListRequest represents a request for information (namely price) about one
|
||||||
|
// or more securities
|
||||||
type SecListRequest struct {
|
type SecListRequest struct {
|
||||||
XMLName xml.Name `xml:"SECLISTTRNRQ"`
|
XMLName xml.Name `xml:"SECLISTTRNRQ"`
|
||||||
TrnUID UID `xml:"TRNUID"`
|
TrnUID UID `xml:"TRNUID"`
|
||||||
@ -28,19 +35,29 @@ type SecListRequest struct {
|
|||||||
Securities []SecurityRequest `xml:"SECLISTRQ>SECRQ,omitempty"`
|
Securities []SecurityRequest `xml:"SECLISTRQ>SECRQ,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (r *SecListRequest) Name() string {
|
func (r *SecListRequest) Name() string {
|
||||||
return "SECLISTTRNRQ"
|
return "SECLISTTRNRQ"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||||
|
// into XML/SGML
|
||||||
func (r *SecListRequest) Valid() (bool, error) {
|
func (r *SecListRequest) Valid() (bool, error) {
|
||||||
// TODO implement
|
// TODO implement
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns which message set this message belongs to (which Request
|
||||||
|
// element of type []Message it should appended to)
|
||||||
func (r *SecListRequest) Type() messageType {
|
func (r *SecListRequest) Type() messageType {
|
||||||
return SecListRq
|
return SecListRq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecListResponse is always empty (except for the transaction UID, status, and
|
||||||
|
// optional client cookie). Its presence signifies that the SecurityList (a
|
||||||
|
// different element from this one) immediately after this element in
|
||||||
|
// Response.SecList was been generated in response to the same SecListRequest
|
||||||
|
// this is a response to.
|
||||||
type SecListResponse struct {
|
type SecListResponse struct {
|
||||||
XMLName xml.Name `xml:"SECLISTTRNRS"`
|
XMLName xml.Name `xml:"SECLISTTRNRS"`
|
||||||
TrnUID UID `xml:"TRNUID"`
|
TrnUID UID `xml:"TRNUID"`
|
||||||
@ -50,23 +67,31 @@ type SecListResponse struct {
|
|||||||
// SECLISTRS is always empty, so we don't parse it here. The actual securities list will be in a top-level element parallel to SECLISTTRNRS
|
// SECLISTRS is always empty, so we don't parse it here. The actual securities list will be in a top-level element parallel to SECLISTTRNRS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (r *SecListResponse) Name() string {
|
func (r *SecListResponse) Name() string {
|
||||||
return "SECLISTTRNRS"
|
return "SECLISTTRNRS"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||||
func (r *SecListResponse) Valid() (bool, error) {
|
func (r *SecListResponse) Valid() (bool, error) {
|
||||||
// TODO implement
|
// TODO implement
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns which message set this message belongs to (which Response
|
||||||
|
// element of type []Message it belongs to)
|
||||||
func (r *SecListResponse) Type() messageType {
|
func (r *SecListResponse) Type() messageType {
|
||||||
return SecListRs
|
return SecListRs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Security is satisfied by all *Info elements providing information about
|
||||||
|
// securities for SecurityList
|
||||||
type Security interface {
|
type Security interface {
|
||||||
SecurityType() string
|
SecurityType() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecInfo represents the generic information about a security. It is included
|
||||||
|
// in most other *Info elements.
|
||||||
type SecInfo struct {
|
type SecInfo struct {
|
||||||
XMLName xml.Name `xml:"SECINFO"`
|
XMLName xml.Name `xml:"SECINFO"`
|
||||||
SecID SecurityID `xml:"SECID"`
|
SecID SecurityID `xml:"SECID"`
|
||||||
@ -80,6 +105,7 @@ type SecInfo struct {
|
|||||||
Memo String `xml:"MEMO,omitempty"`
|
Memo String `xml:"MEMO,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DebtInfo provides information about a debt security
|
||||||
type DebtInfo struct {
|
type DebtInfo struct {
|
||||||
XMLName xml.Name `xml:"DEBTINFO"`
|
XMLName xml.Name `xml:"DEBTINFO"`
|
||||||
SecInfo SecInfo `xml:"SECINFO"`
|
SecInfo SecInfo `xml:"SECINFO"`
|
||||||
@ -99,22 +125,29 @@ type DebtInfo struct {
|
|||||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityType returns a string representation of this security's type
|
||||||
func (i DebtInfo) SecurityType() string {
|
func (i DebtInfo) SecurityType() string {
|
||||||
return "DEBTINFO"
|
return "DEBTINFO"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssetPortion represents the percentage of a mutual fund with the given asset
|
||||||
|
// classification
|
||||||
type AssetPortion struct {
|
type AssetPortion struct {
|
||||||
XMLName xml.Name `xml:"PORTION"`
|
XMLName xml.Name `xml:"PORTION"`
|
||||||
AssetClass assetClass `xml:"ASSETCLASS"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
|
AssetClass assetClass `xml:"ASSETCLASS"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
|
||||||
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
|
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FiAssetPortion represents the percentage of a mutual fund with the given
|
||||||
|
// FI-defined asset classification (AssetPortion should be used for all asset
|
||||||
|
// classifications defined by the assetClass enum)
|
||||||
type FiAssetPortion struct {
|
type FiAssetPortion struct {
|
||||||
XMLName xml.Name `xml:"FIPORTION"`
|
XMLName xml.Name `xml:"FIPORTION"`
|
||||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||||
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
|
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MFInfo provides information about a mutual fund
|
||||||
type MFInfo struct {
|
type MFInfo struct {
|
||||||
XMLName xml.Name `xml:"MFINFO"`
|
XMLName xml.Name `xml:"MFINFO"`
|
||||||
SecInfo SecInfo `xml:"SECINFO"`
|
SecInfo SecInfo `xml:"SECINFO"`
|
||||||
@ -125,10 +158,12 @@ type MFInfo struct {
|
|||||||
FiAssetClasses []FiAssetPortion `xml:"FIMFASSETCLASS>FIPORTION"`
|
FiAssetClasses []FiAssetPortion `xml:"FIMFASSETCLASS>FIPORTION"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityType returns a string representation of this security's type
|
||||||
func (i MFInfo) SecurityType() string {
|
func (i MFInfo) SecurityType() string {
|
||||||
return "MFINFO"
|
return "MFINFO"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptInfo provides information about an option
|
||||||
type OptInfo struct {
|
type OptInfo struct {
|
||||||
XMLName xml.Name `xml:"OPTINFO"`
|
XMLName xml.Name `xml:"OPTINFO"`
|
||||||
SecInfo SecInfo `xml:"SECINFO"`
|
SecInfo SecInfo `xml:"SECINFO"`
|
||||||
@ -141,10 +176,13 @@ type OptInfo struct {
|
|||||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityType returns a string representation of this security's type
|
||||||
func (i OptInfo) SecurityType() string {
|
func (i OptInfo) SecurityType() string {
|
||||||
return "OPTINFO"
|
return "OPTINFO"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OtherInfo provides information about a security type not covered by the
|
||||||
|
// other *Info elements
|
||||||
type OtherInfo struct {
|
type OtherInfo struct {
|
||||||
XMLName xml.Name `xml:"OTHERINFO"`
|
XMLName xml.Name `xml:"OTHERINFO"`
|
||||||
SecInfo SecInfo `xml:"SECINFO"`
|
SecInfo SecInfo `xml:"SECINFO"`
|
||||||
@ -153,10 +191,12 @@ type OtherInfo struct {
|
|||||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityType returns a string representation of this security's type
|
||||||
func (i OtherInfo) SecurityType() string {
|
func (i OtherInfo) SecurityType() string {
|
||||||
return "OTHERINFO"
|
return "OTHERINFO"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StockInfo provides information about a security type
|
||||||
type StockInfo struct {
|
type StockInfo struct {
|
||||||
XMLName xml.Name `xml:"STOCKINFO"`
|
XMLName xml.Name `xml:"STOCKINFO"`
|
||||||
SecInfo SecInfo `xml:"SECINFO"`
|
SecInfo SecInfo `xml:"SECINFO"`
|
||||||
@ -167,27 +207,35 @@ type StockInfo struct {
|
|||||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityType returns a string representation of this security's type
|
||||||
func (i StockInfo) SecurityType() string {
|
func (i StockInfo) SecurityType() string {
|
||||||
return "STOCKINFO"
|
return "STOCKINFO"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityList is a container for Security objects containaing information
|
||||||
|
// about securities
|
||||||
type SecurityList struct {
|
type SecurityList struct {
|
||||||
Securities []Security
|
Securities []Security
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (r *SecurityList) Name() string {
|
func (r *SecurityList) Name() string {
|
||||||
return "SECLIST"
|
return "SECLIST"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||||
func (r *SecurityList) Valid() (bool, error) {
|
func (r *SecurityList) Valid() (bool, error) {
|
||||||
// TODO implement
|
// TODO implement
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns which message set this message belongs to (which Response
|
||||||
|
// element of type []Message it belongs to)
|
||||||
func (r *SecurityList) Type() messageType {
|
func (r *SecurityList) Type() messageType {
|
||||||
return SecListRs
|
return SecListRs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling a SecurityList from an SGML/XML string
|
||||||
func (r *SecurityList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (r *SecurityList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
for {
|
for {
|
||||||
tok, err := nextNonWhitespaceToken(d)
|
tok, err := nextNonWhitespaceToken(d)
|
||||||
|
13
signon.go
13
signon.go
@ -6,6 +6,8 @@ import (
|
|||||||
"github.com/aclindsa/go/src/encoding/xml"
|
"github.com/aclindsa/go/src/encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SignonRequest identifies and authenticates a user to their FI and is
|
||||||
|
// provided with every Request
|
||||||
type SignonRequest struct {
|
type SignonRequest struct {
|
||||||
XMLName xml.Name `xml:"SONRQ"`
|
XMLName xml.Name `xml:"SONRQ"`
|
||||||
DtClient Date `xml:"DTCLIENT"` // Current time on client, overwritten in Client.Request()
|
DtClient Date `xml:"DTCLIENT"` // Current time on client, overwritten in Client.Request()
|
||||||
@ -20,10 +22,13 @@ type SignonRequest struct {
|
|||||||
ClientUID UID `xml:"CLIENTUID,omitempty"`
|
ClientUID UID `xml:"CLIENTUID,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (r *SignonRequest) Name() string {
|
func (r *SignonRequest) Name() string {
|
||||||
return "SONRQ"
|
return "SONRQ"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||||
|
// into XML/SGML
|
||||||
func (r *SignonRequest) Valid() (bool, error) {
|
func (r *SignonRequest) Valid() (bool, error) {
|
||||||
if len(r.UserID) < 1 || len(r.UserID) > 32 {
|
if len(r.UserID) < 1 || len(r.UserID) > 32 {
|
||||||
return false, errors.New("SONRQ>USERID invalid length")
|
return false, errors.New("SONRQ>USERID invalid length")
|
||||||
@ -40,7 +45,7 @@ func (r *SignonRequest) Valid() (bool, error) {
|
|||||||
if len(r.Language) == 0 {
|
if len(r.Language) == 0 {
|
||||||
r.Language = "ENG"
|
r.Language = "ENG"
|
||||||
} else if len(r.Language) != 3 {
|
} else if len(r.Language) != 3 {
|
||||||
return false, fmt.Errorf("SONRQ>LANGUAGE invalid length: \"%s\"\n", r.Language)
|
return false, fmt.Errorf("SONRQ>LANGUAGE invalid length: \"%s\"", r.Language)
|
||||||
}
|
}
|
||||||
if len(r.AppID) < 1 || len(r.AppID) > 5 {
|
if len(r.AppID) < 1 || len(r.AppID) > 5 {
|
||||||
return false, errors.New("SONRQ>APPID invalid length")
|
return false, errors.New("SONRQ>APPID invalid length")
|
||||||
@ -51,6 +56,8 @@ func (r *SignonRequest) Valid() (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignonResponse is provided with every Response and indicates the success or
|
||||||
|
// failure of the SignonRequest in the corresponding Request
|
||||||
type SignonResponse struct {
|
type SignonResponse struct {
|
||||||
XMLName xml.Name `xml:"SONRS"`
|
XMLName xml.Name `xml:"SONRS"`
|
||||||
Status Status `xml:"STATUS"`
|
Status Status `xml:"STATUS"`
|
||||||
@ -66,13 +73,15 @@ type SignonResponse struct {
|
|||||||
AccessKey String `xml:"ACCESSKEY,omitempty"`
|
AccessKey String `xml:"ACCESSKEY,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (r *SignonResponse) Name() string {
|
func (r *SignonResponse) Name() string {
|
||||||
return "SONRS"
|
return "SONRS"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||||
func (r *SignonResponse) Valid() (bool, error) {
|
func (r *SignonResponse) Valid() (bool, error) {
|
||||||
if len(r.Language) != 3 {
|
if len(r.Language) != 3 {
|
||||||
return false, fmt.Errorf("SONRS>LANGUAGE invalid length: \"%s\"\n", r.Language)
|
return false, fmt.Errorf("SONRS>LANGUAGE invalid length: \"%s\"", r.Language)
|
||||||
}
|
}
|
||||||
return r.Status.Valid()
|
return r.Status.Valid()
|
||||||
}
|
}
|
||||||
|
33
signup.go
33
signup.go
@ -5,6 +5,8 @@ import (
|
|||||||
"github.com/aclindsa/go/src/encoding/xml"
|
"github.com/aclindsa/go/src/encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AcctInfoRequest represents a request for the server to provide information
|
||||||
|
// for all of the user's available accounts at this FI
|
||||||
type AcctInfoRequest struct {
|
type AcctInfoRequest struct {
|
||||||
XMLName xml.Name `xml:"ACCTINFOTRNRQ"`
|
XMLName xml.Name `xml:"ACCTINFOTRNRQ"`
|
||||||
TrnUID UID `xml:"TRNUID"`
|
TrnUID UID `xml:"TRNUID"`
|
||||||
@ -14,19 +16,25 @@ type AcctInfoRequest struct {
|
|||||||
DtAcctUp Date `xml:"ACCTINFORQ>DTACCTUP"`
|
DtAcctUp Date `xml:"ACCTINFORQ>DTACCTUP"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (r *AcctInfoRequest) Name() string {
|
func (r *AcctInfoRequest) Name() string {
|
||||||
return "ACCTINFOTRNRQ"
|
return "ACCTINFOTRNRQ"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||||
|
// into XML/SGML
|
||||||
func (r *AcctInfoRequest) Valid() (bool, error) {
|
func (r *AcctInfoRequest) Valid() (bool, error) {
|
||||||
// TODO implement
|
// TODO implement
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns which message set this message belongs to (which Request
|
||||||
|
// element of type []Message it should appended to)
|
||||||
func (r *AcctInfoRequest) Type() messageType {
|
func (r *AcctInfoRequest) Type() messageType {
|
||||||
return SignupRq
|
return SignupRq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HolderInfo contains the information a FI has about an account-holder
|
||||||
type HolderInfo struct {
|
type HolderInfo struct {
|
||||||
XMLName xml.Name
|
XMLName xml.Name
|
||||||
FirstName String `xml:"FIRSTNAME"`
|
FirstName String `xml:"FIRSTNAME"`
|
||||||
@ -45,6 +53,9 @@ type HolderInfo struct {
|
|||||||
HolderType holderType `xml:"HOLDERTYPE,omitempty"` // One of INDIVIDUAL, JOINT, CUSTODIAL, TRUST, OTHER
|
HolderType holderType `xml:"HOLDERTYPE,omitempty"` // One of INDIVIDUAL, JOINT, CUSTODIAL, TRUST, OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BankAcctInfo contains information about a bank account, including how to
|
||||||
|
// access it (BankAcct), and whether it supports downloading transactions
|
||||||
|
// (SupTxDl).
|
||||||
type BankAcctInfo struct {
|
type BankAcctInfo struct {
|
||||||
XMLName xml.Name `xml:"BANKACCTINFO"`
|
XMLName xml.Name `xml:"BANKACCTINFO"`
|
||||||
BankAcctFrom BankAcct `xml:"BANKACCTFROM"`
|
BankAcctFrom BankAcct `xml:"BANKACCTFROM"`
|
||||||
@ -59,11 +70,14 @@ type BankAcctInfo struct {
|
|||||||
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
|
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make pointers to these structs print nicely
|
// String makes pointers to BankAcctInfo structs print nicely
|
||||||
func (bai *BankAcctInfo) String() string {
|
func (bai *BankAcctInfo) String() string {
|
||||||
return fmt.Sprintf("%+v", *bai)
|
return fmt.Sprintf("%+v", *bai)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CCAcctInfo contains information about a credit card account, including how
|
||||||
|
// to access it (CCAcct), and whether it supports downloading transactions
|
||||||
|
// (SupTxDl).
|
||||||
type CCAcctInfo struct {
|
type CCAcctInfo struct {
|
||||||
XMLName xml.Name `xml:"CCACCTINFO"`
|
XMLName xml.Name `xml:"CCACCTINFO"`
|
||||||
CCAcctFrom CCAcct `xml:"CCACCTFROM"`
|
CCAcctFrom CCAcct `xml:"CCACCTFROM"`
|
||||||
@ -74,11 +88,14 @@ type CCAcctInfo struct {
|
|||||||
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
|
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make pointers to these structs print nicely
|
// String makes pointers to CCAcctInfo structs print nicely
|
||||||
func (ci *CCAcctInfo) String() string {
|
func (ci *CCAcctInfo) String() string {
|
||||||
return fmt.Sprintf("%+v", *ci)
|
return fmt.Sprintf("%+v", *ci)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvAcctInfo contains information about an investment account, including how
|
||||||
|
// to access it (InvAcct), and whether it supports downloading transactions
|
||||||
|
// (SupTxDl).
|
||||||
type InvAcctInfo struct {
|
type InvAcctInfo struct {
|
||||||
XMLName xml.Name `xml:"INVACCTINFO"`
|
XMLName xml.Name `xml:"INVACCTINFO"`
|
||||||
InvAcctFrom InvAcct `xml:"INVACCTFROM"`
|
InvAcctFrom InvAcct `xml:"INVACCTFROM"`
|
||||||
@ -89,11 +106,14 @@ type InvAcctInfo struct {
|
|||||||
OptionLevel String `xml:"OPTIONLEVEL,omitempty"` // Text desribing option trading privileges
|
OptionLevel String `xml:"OPTIONLEVEL,omitempty"` // Text desribing option trading privileges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make pointers to these structs print nicely
|
// String makes pointers to InvAcctInfo structs print nicely
|
||||||
func (iai *InvAcctInfo) String() string {
|
func (iai *InvAcctInfo) String() string {
|
||||||
return fmt.Sprintf("%+v", *iai)
|
return fmt.Sprintf("%+v", *iai)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AcctInfo represents generic account information. It should contain one (and
|
||||||
|
// only one) *AcctInfo element corresponding to the tyep of account it
|
||||||
|
// represents.
|
||||||
type AcctInfo struct {
|
type AcctInfo struct {
|
||||||
XMLName xml.Name `xml:"ACCTINFO"`
|
XMLName xml.Name `xml:"ACCTINFO"`
|
||||||
Name String `xml:"NAME,omitempty"`
|
Name String `xml:"NAME,omitempty"`
|
||||||
@ -101,6 +121,7 @@ type AcctInfo struct {
|
|||||||
Phone String `xml:"PHONE,omitempty"`
|
Phone String `xml:"PHONE,omitempty"`
|
||||||
PrimaryHolder HolderInfo `xml:"HOLDERINFO>PRIMARYHOLDER,omitempty"`
|
PrimaryHolder HolderInfo `xml:"HOLDERINFO>PRIMARYHOLDER,omitempty"`
|
||||||
SecondaryHolder HolderInfo `xml:"HOLDERINFO>SECONDARYHOLDER,omitempty"`
|
SecondaryHolder HolderInfo `xml:"HOLDERINFO>SECONDARYHOLDER,omitempty"`
|
||||||
|
|
||||||
// Only one of the rest of the fields will be valid for any given AcctInfo
|
// Only one of the rest of the fields will be valid for any given AcctInfo
|
||||||
BankAcctInfo *BankAcctInfo `xml:"BANKACCTINFO,omitempty"`
|
BankAcctInfo *BankAcctInfo `xml:"BANKACCTINFO,omitempty"`
|
||||||
CCAcctInfo *CCAcctInfo `xml:"CCACCTINFO,omitempty"`
|
CCAcctInfo *CCAcctInfo `xml:"CCACCTINFO,omitempty"`
|
||||||
@ -109,6 +130,8 @@ type AcctInfo struct {
|
|||||||
// TODO BPACCTINFO?
|
// TODO BPACCTINFO?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AcctInfoResponse contains the information about all a user's accounts
|
||||||
|
// accessible from this FI
|
||||||
type AcctInfoResponse struct {
|
type AcctInfoResponse struct {
|
||||||
XMLName xml.Name `xml:"ACCTINFOTRNRS"`
|
XMLName xml.Name `xml:"ACCTINFOTRNRS"`
|
||||||
TrnUID UID `xml:"TRNUID"`
|
TrnUID UID `xml:"TRNUID"`
|
||||||
@ -119,15 +142,19 @@ type AcctInfoResponse struct {
|
|||||||
AcctInfo []AcctInfo `xml:"ACCTINFORS>ACCTINFO,omitempty"`
|
AcctInfo []AcctInfo `xml:"ACCTINFORS>ACCTINFO,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the top-level transaction XML/SGML element
|
||||||
func (air *AcctInfoResponse) Name() string {
|
func (air *AcctInfoResponse) Name() string {
|
||||||
return "ACCTINFOTRNRS"
|
return "ACCTINFOTRNRS"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||||
func (air *AcctInfoResponse) Valid() (bool, error) {
|
func (air *AcctInfoResponse) Valid() (bool, error) {
|
||||||
//TODO implement
|
//TODO implement
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns which message set this message belongs to (which Response
|
||||||
|
// element of type []Message it belongs to)
|
||||||
func (air *AcctInfoResponse) Type() messageType {
|
func (air *AcctInfoResponse) Type() messageType {
|
||||||
return SignupRs
|
return SignupRs
|
||||||
}
|
}
|
||||||
|
46
types.go
46
types.go
@ -12,8 +12,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Int provides helper methods to unmarshal int64 values from SGML/XML
|
||||||
type Int int64
|
type Int int64
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling an Int from an SGML/XML string. Leading
|
||||||
|
// and trailing whitespace is ignored.
|
||||||
func (i *Int) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (i *Int) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
var value string
|
var value string
|
||||||
|
|
||||||
@ -33,14 +36,19 @@ func (i *Int) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal returns true if the two Ints are equal in value
|
||||||
func (i Int) Equal(o Int) bool {
|
func (i Int) Equal(o Int) bool {
|
||||||
return i == o
|
return i == o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Amount represents non-integer values (or at least values for fields that may
|
||||||
|
// not necessarily be integers)
|
||||||
type Amount struct {
|
type Amount struct {
|
||||||
big.Rat
|
big.Rat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling an Amount from an SGML/XML string.
|
||||||
|
// Leading and trailing whitespace is ignored.
|
||||||
func (a *Amount) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (a *Amount) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
var value string
|
var value string
|
||||||
|
|
||||||
@ -61,18 +69,22 @@ func (a *Amount) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String prints a string representation of an Amount
|
||||||
func (a Amount) String() string {
|
func (a Amount) String() string {
|
||||||
return strings.TrimRight(strings.TrimRight(a.FloatString(100), "0"), ".")
|
return strings.TrimRight(strings.TrimRight(a.FloatString(100), "0"), ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
return e.EncodeElement(a.String(), start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal returns true if two Amounts are equal in value
|
||||||
func (a Amount) Equal(o Amount) bool {
|
func (a Amount) Equal(o Amount) bool {
|
||||||
return (&a).Cmp(&o.Rat) == 0
|
return (&a).Cmp(&o.Rat) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Date represents OFX date/time values
|
||||||
type Date struct {
|
type Date struct {
|
||||||
time.Time
|
time.Time
|
||||||
}
|
}
|
||||||
@ -86,6 +98,10 @@ var ofxDateFormats = []string{
|
|||||||
}
|
}
|
||||||
var ofxDateZoneRegex = regexp.MustCompile(`^([+-]?[0-9]+)(\.([0-9]{2}))?(:([A-Z]+))?$`)
|
var ofxDateZoneRegex = regexp.MustCompile(`^([+-]?[0-9]+)(\.([0-9]{2}))?(:([A-Z]+))?$`)
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling a Date from an SGML/XML string. It
|
||||||
|
// attempts to unmarshal the valid date formats in order of decreasing length
|
||||||
|
// and defaults to GMT if a time zone is not provided, as per the OFX spec.
|
||||||
|
// Leading and trailing whitespace is ignored.
|
||||||
func (od *Date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (od *Date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
var value, zone, zoneFormat string
|
var value, zone, zoneFormat string
|
||||||
err := d.DecodeElement(&value, &start)
|
err := d.DecodeElement(&value, &start)
|
||||||
@ -148,6 +164,7 @@ func (od *Date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
return errors.New("OFX: Couldn't parse date:" + value)
|
return errors.New("OFX: Couldn't parse date:" + value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the Date abiding by the OFX spec
|
||||||
func (od Date) String() string {
|
func (od Date) String() string {
|
||||||
format := od.Format(ofxDateFormats[0])
|
format := od.Format(ofxDateFormats[0])
|
||||||
zonename, zoneoffset := od.Zone()
|
zonename, zoneoffset := od.Zone()
|
||||||
@ -165,31 +182,39 @@ func (od Date) String() string {
|
|||||||
|
|
||||||
if len(zonename) > 0 {
|
if len(zonename) > 0 {
|
||||||
return format + ":" + zonename + "]"
|
return format + ":" + zonename + "]"
|
||||||
} else {
|
|
||||||
return format + "]"
|
|
||||||
}
|
}
|
||||||
|
return format + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
return e.EncodeElement(od.String(), start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal returns true if the two Dates represent the same time (time zones are
|
||||||
|
// accounted for when comparing, but are not required to match)
|
||||||
func (od Date) Equal(o Date) bool {
|
func (od Date) Equal(o Date) bool {
|
||||||
return od.Time.Equal(o.Time)
|
return od.Time.Equal(o.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDate returns a new Date object with the provided date, time, and timezone
|
||||||
func NewDate(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) *Date {
|
func NewDate(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) *Date {
|
||||||
return &Date{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)}
|
return &Date{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)}
|
||||||
}
|
}
|
||||||
|
|
||||||
var gmt = time.FixedZone("GMT", 0)
|
var gmt = time.FixedZone("GMT", 0)
|
||||||
|
|
||||||
|
// NewDateGMT returns a new Date object with the provided date and time in the
|
||||||
|
// GMT timezone
|
||||||
func NewDateGMT(year int, month time.Month, day, hour, min, sec, nsec int) *Date {
|
func NewDateGMT(year int, month time.Month, day, hour, min, sec, nsec int) *Date {
|
||||||
return &Date{Time: time.Date(year, month, day, hour, min, sec, nsec, gmt)}
|
return &Date{Time: time.Date(year, month, day, hour, min, sec, nsec, gmt)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String provides helper methods to unmarshal OFX string values from SGML/XML
|
||||||
type String string
|
type String string
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling a String from an SGML/XML string. Leading
|
||||||
|
// and trailing whitespace is ignored.
|
||||||
func (os *String) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (os *String) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
var value string
|
var value string
|
||||||
err := d.DecodeElement(&value, &start)
|
err := d.DecodeElement(&value, &start)
|
||||||
@ -200,16 +225,21 @@ func (os *String) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string
|
||||||
func (os *String) String() string {
|
func (os *String) String() string {
|
||||||
return string(*os)
|
return string(*os)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal returns true if the two Strings are equal in value
|
||||||
func (os String) Equal(o String) bool {
|
func (os String) Equal(o String) bool {
|
||||||
return os == o
|
return os == o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Boolean provides helper methods to unmarshal bool values from OFX SGML/XML
|
||||||
type Boolean bool
|
type Boolean bool
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling a Boolean from an SGML/XML string.
|
||||||
|
// Leading and trailing whitespace is ignored.
|
||||||
func (ob *Boolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (ob *Boolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
var value string
|
var value string
|
||||||
err := d.DecodeElement(&value, &start)
|
err := d.DecodeElement(&value, &start)
|
||||||
@ -228,6 +258,7 @@ func (ob *Boolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalXML marshals a Boolean to XML
|
||||||
func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
if *ob {
|
if *ob {
|
||||||
return e.EncodeElement("Y", start)
|
return e.EncodeElement("Y", start)
|
||||||
@ -235,16 +266,21 @@ func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
return e.EncodeElement("N", start)
|
return e.EncodeElement("N", start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of a Boolean value
|
||||||
func (ob *Boolean) String() string {
|
func (ob *Boolean) String() string {
|
||||||
return fmt.Sprintf("%v", *ob)
|
return fmt.Sprintf("%v", *ob)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal returns true if the two Booleans are the same
|
||||||
func (ob Boolean) Equal(o Boolean) bool {
|
func (ob Boolean) Equal(o Boolean) bool {
|
||||||
return ob == o
|
return ob == o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UID represents an UID according to the OFX spec
|
||||||
type UID string
|
type UID string
|
||||||
|
|
||||||
|
// UnmarshalXML handles unmarshalling an UID from an SGML/XML string. Leading
|
||||||
|
// and trailing whitespace is ignored.
|
||||||
func (ou *UID) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (ou *UID) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
var value string
|
var value string
|
||||||
err := d.DecodeElement(&value, &start)
|
err := d.DecodeElement(&value, &start)
|
||||||
@ -255,8 +291,8 @@ func (ou *UID) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The OFX specification recommends that UIDs follow the standard UUID
|
// RecommendedFormat returns true iff this UID meets the OFX specification's
|
||||||
// 36-character format
|
// recommendation that UIDs follow the standard UUID 36-character format
|
||||||
func (ou UID) RecommendedFormat() (bool, error) {
|
func (ou UID) RecommendedFormat() (bool, error) {
|
||||||
if len(ou) != 36 {
|
if len(ou) != 36 {
|
||||||
return false, errors.New("UID not 36 characters long")
|
return false, errors.New("UID not 36 characters long")
|
||||||
@ -267,10 +303,12 @@ func (ou UID) RecommendedFormat() (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal returns true if the two UIDs are the same
|
||||||
func (ou UID) Equal(o UID) bool {
|
func (ou UID) Equal(o UID) bool {
|
||||||
return ou == o
|
return ou == o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RandomUID creates a new randomly-generated UID
|
||||||
func RandomUID() (*UID, error) {
|
func RandomUID() (*UID, error) {
|
||||||
uidbytes := make([]byte, 16)
|
uidbytes := make([]byte, 16)
|
||||||
n, err := rand.Read(uidbytes[:])
|
n, err := rand.Read(uidbytes[:])
|
||||||
|
Loading…
Reference in New Issue
Block a user