no more ofx
This commit is contained in:
280
vendor/github.com/aclindsa/ofxgo/LICENSE
generated
vendored
280
vendor/github.com/aclindsa/ofxgo/LICENSE
generated
vendored
@@ -1,280 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
132
vendor/github.com/aclindsa/ofxgo/README.md
generated
vendored
132
vendor/github.com/aclindsa/ofxgo/README.md
generated
vendored
@@ -1,132 +0,0 @@
|
||||
# OFXGo
|
||||
|
||||
[](https://goreportcard.com/report/github.com/aclindsa/ofxgo)
|
||||
[](https://github.com/aclindsa/ofxgo/actions?query=workflow%3A%22ofxgo+CI+Test%22+branch%3Amaster)
|
||||
[](https://coveralls.io/github/aclindsa/ofxgo?branch=master)
|
||||
[](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
|
||||
library.
|
||||
|
||||
## Goals
|
||||
|
||||
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
|
||||
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
|
||||
finance software in Go (as it was created to allow me to fetch OFX transactions
|
||||
for my own project, [MoneyGo](https://github.com/aclindsa/moneygo)).
|
||||
|
||||
Because the OFX specification is rather... 'comprehensive,' it can be difficult
|
||||
for those unfamiliar with it to figure out where to start. To that end, I have
|
||||
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](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
|
||||
directory](https://github.com/aclindsa/ofxgo/tree/master/cmd/ofx) of this
|
||||
repository.
|
||||
|
||||
## Library documentation
|
||||
|
||||
Documentation can be found with the `go doc` tool, or at
|
||||
https://pkg.go.dev/github.com/aclindsa/ofxgo
|
||||
|
||||
## Example Usage
|
||||
|
||||
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:
|
||||
|
||||
```go
|
||||
client := ofxgo.BasicClient{} // Accept the default Client settings
|
||||
|
||||
// These values are specific to your bank
|
||||
var query ofxgo.Request
|
||||
query.URL = "https://secu.example.com/ofx"
|
||||
query.Signon.Org = ofxgo.String("SECU")
|
||||
query.Signon.Fid = ofxgo.String("1234")
|
||||
|
||||
// Set your username/password
|
||||
query.Signon.UserID = ofxgo.String("username")
|
||||
query.Signon.UserPass = ofxgo.String("hunter2")
|
||||
|
||||
uid, _ := ofxgo.RandomUID() // Handle error in real code
|
||||
query.Bank = append(query.Bank, &ofxgo.StatementRequest{
|
||||
TrnUID: *uid,
|
||||
BankAcctFrom: ofxgo.BankAcct{
|
||||
BankID: ofxgo.String("123456789"), // Possibly your routing number
|
||||
AcctID: ofxgo.String("00011122233"), // Possibly your account number
|
||||
AcctType: ofxgo.AcctTypeChecking,
|
||||
},
|
||||
Include: true, // Include transactions (instead of only balance information)
|
||||
})
|
||||
|
||||
response, _ := client.Request(&query) // Handle error in real code
|
||||
|
||||
// Was there an OFX error while processing our request?
|
||||
if response.Signon.Status.Code != 0 {
|
||||
meaning, _ := response.Signon.Status.CodeMeaning()
|
||||
fmt.Printf("Nonzero signon status (%d: %s) with message: %s\n", response.Signon.Status.Code, meaning, response.Signon.Status.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(response.Bank) < 1 {
|
||||
fmt.Println("No banking messages received")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if stmt, ok := response.Bank[0].(*ofxgo.StatementResponse); ok {
|
||||
fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf)
|
||||
fmt.Println("Transactions:")
|
||||
for _, tran := range stmt.BankTranList.Transactions {
|
||||
currency := stmt.CurDef
|
||||
if ok, _ := tran.Currency.Valid(); ok {
|
||||
currency = tran.Currency.CurSym
|
||||
}
|
||||
fmt.Printf("%s %-15s %-11s %s%s%s\n", tran.DtPosted, tran.TrnAmt.String()+" "+currency.String(), tran.TrnType, tran.Name, tran.Payee.Name, tran.Memo)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.12
|
||||
|
||||
## Using the command-line client
|
||||
|
||||
To install the command-line client and test it out, you may do the following:
|
||||
|
||||
$ go get -v github.com/aclindsa/ofxgo/cmd/ofx && go install -v github.com/aclindsa/ofxgo/cmd/ofx
|
||||
|
||||
Once installed (at ~/go/bin/ofx by default, if you haven't set $GOPATH), the
|
||||
command's usage should help you to use it (`./ofx --help` for a listing of the
|
||||
available subcommands and their purposes, `./ofx subcommand --help` for
|
||||
individual subcommand usage).
|
||||
366
vendor/github.com/aclindsa/ofxgo/bank.go
generated
vendored
366
vendor/github.com/aclindsa/ofxgo/bank.go
generated
vendored
@@ -1,366 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
|
||||
// StatementRequest represents a request for a bank statement. It is used to
|
||||
// request balances and/or transactions for checking, savings, money market,
|
||||
// and line of credit accounts. See CCStatementRequest for the analog for
|
||||
// credit card accounts.
|
||||
type StatementRequest struct {
|
||||
XMLName xml.Name `xml:"STMTTRNRQ"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
BankAcctFrom BankAcct `xml:"STMTRQ>BANKACCTFROM"`
|
||||
DtStart *Date `xml:"STMTRQ>INCTRAN>DTSTART,omitempty"`
|
||||
DtEnd *Date `xml:"STMTRQ>INCTRAN>DTEND,omitempty"`
|
||||
Include Boolean `xml:"STMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance)
|
||||
IncludePending Boolean `xml:"STMTRQ>INCLUDEPENDING,omitempty"` // Include pending transactions
|
||||
IncTranImg Boolean `xml:"STMTRQ>INCTRANIMG,omitempty"` // Include transaction images
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (r *StatementRequest) Name() string {
|
||||
return "STMTTRNRQ"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||
// into XML/SGML
|
||||
func (r *StatementRequest) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := r.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
if r.IncludePending && version < OfxVersion220 {
|
||||
return false, errors.New("StatementRequest.IncludePending invalid for OFX < 2.2")
|
||||
}
|
||||
if r.IncTranImg && version < OfxVersion210 {
|
||||
return false, errors.New("StatementRequest.IncTranImg invalid for OFX < 2.1")
|
||||
}
|
||||
return r.BankAcctFrom.Valid()
|
||||
}
|
||||
|
||||
// Type returns which message set this message belongs to (which Request
|
||||
// element of type []Message it should appended to)
|
||||
func (r *StatementRequest) Type() messageType {
|
||||
return BankRq
|
||||
}
|
||||
|
||||
// Payee specifies a complete billing address for a payee
|
||||
type Payee struct {
|
||||
XMLName xml.Name `xml:"PAYEE"`
|
||||
Name String `xml:"NAME"`
|
||||
Addr1 String `xml:"ADDR1"`
|
||||
Addr2 String `xml:"ADDR2,omitempty"`
|
||||
Addr3 String `xml:"ADDR3,omitempty"`
|
||||
City String `xml:"CITY"`
|
||||
State String `xml:"STATE"`
|
||||
PostalCode String `xml:"POSTALCODE"`
|
||||
Country String `xml:"COUNTRY,omitempty"`
|
||||
Phone String `xml:"PHONE"`
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct is valid OFX
|
||||
func (p Payee) Valid() (bool, error) {
|
||||
if len(p.Name) == 0 {
|
||||
return false, errors.New("Payee.Name empty")
|
||||
} else if len(p.Addr1) == 0 {
|
||||
return false, errors.New("Payee.Addr1 empty")
|
||||
} else if len(p.City) == 0 {
|
||||
return false, errors.New("Payee.City empty")
|
||||
} else if len(p.State) == 0 {
|
||||
return false, errors.New("Payee.State empty")
|
||||
} else if len(p.PostalCode) == 0 {
|
||||
return false, errors.New("Payee.PostalCode empty")
|
||||
} else if len(p.Country) != 0 && len(p.Country) != 3 {
|
||||
return false, errors.New("Payee.Country invalid length")
|
||||
} else if len(p.Phone) == 0 {
|
||||
return false, errors.New("Payee.Phone empty")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ImageData represents the metadata surrounding a check or other image file,
|
||||
// including how to retrieve the image
|
||||
type ImageData struct {
|
||||
XMLName xml.Name `xml:"IMAGEDATA"`
|
||||
ImageType imageType `xml:"IMAGETYPE"` // One of STATEMENT, TRANSACTION, TAX
|
||||
ImageRef String `xml:"IMAGEREF"` // URL or identifier, depending on IMAGEREFTYPE
|
||||
ImageRefType imageRefType `xml:"IMAGEREFTYPE"` // One of OPAQUE, URL, FORMURL (see spec for more details on how to access images of each of these types)
|
||||
// Only one of the next two should be valid at any given time
|
||||
ImageDelay Int `xml:"IMAGEDELAY,omitempty"` // Number of calendar days from DTSERVER (for statement images) or DTPOSTED (for transaction image) the image will become available
|
||||
DtImageAvail *Date `xml:"DTIMAGEAVAIL,omitempty"` // Date image will become available
|
||||
ImageTTL Int `xml:"IMAGETTL,omitempty"` // Number of days after image becomes available that it will remain available
|
||||
CheckSup checkSup `xml:"CHECKSUP,omitempty"` // What is contained in check images. One of FRONTONLY, BACKONLY, FRONTANDBACK
|
||||
}
|
||||
|
||||
// Transaction represents a single banking transaction. At a minimum, it
|
||||
// identifies the type of transaction (TrnType) and the date it was posted
|
||||
// (DtPosted). Ideally it also provides metadata to help the user recognize
|
||||
// this transaction (i.e. CheckNum, Name or Payee, Memo, etc.)
|
||||
type Transaction struct {
|
||||
XMLName xml.Name `xml:"STMTTRN"`
|
||||
TrnType trnType `xml:"TRNTYPE"` // One of CREDIT, DEBIT, INT (interest earned or paid. Note: Depends on signage of amount), DIV, FEE, SRVCHG (service charge), DEP (deposit), ATM (Note: Depends on signage of amount), POS (Note: Depends on signage of amount), XFER, CHECK, PAYMENT, CASH, DIRECTDEP, DIRECTDEBIT, REPEATPMT, OTHER
|
||||
DtPosted Date `xml:"DTPOSTED"`
|
||||
DtUser *Date `xml:"DTUSER,omitempty"`
|
||||
DtAvail *Date `xml:"DTAVAIL,omitempty"`
|
||||
TrnAmt Amount `xml:"TRNAMT"`
|
||||
FiTID String `xml:"FITID"` // Client uses FITID to detect whether it has previously downloaded the transaction
|
||||
CorrectFiTID String `xml:"CORRECTFITID,omitempty"` // Transaction ID that this transaction corrects, if present
|
||||
CorrectAction correctAction `xml:"CORRECTACTION,omitempty"` // One of DELETE, REPLACE
|
||||
SrvrTID String `xml:"SRVRTID,omitempty"`
|
||||
CheckNum String `xml:"CHECKNUM,omitempty"`
|
||||
RefNum String `xml:"REFNUM,omitempty"`
|
||||
SIC Int `xml:"SIC,omitempty"` // Standard Industrial Code
|
||||
PayeeID String `xml:"PAYEEID,omitempty"`
|
||||
// Note: Servers should provide NAME or PAYEE, but not both
|
||||
Name String `xml:"NAME,omitempty"`
|
||||
Payee *Payee `xml:"PAYEE,omitempty"`
|
||||
ExtdName String `xml:"EXTDNAME,omitempty"` // Extended name of payee or transaction description
|
||||
BankAcctTo *BankAcct `xml:"BANKACCTTO,omitempty"` // If the transfer was to a bank account we have the account information for
|
||||
CCAcctTo *CCAcct `xml:"CCACCTTO,omitempty"` // If the transfer was to a credit card account we have the account information for
|
||||
Memo String `xml:"MEMO,omitempty"` // Extra information (not in NAME)
|
||||
ImageData []ImageData `xml:"IMAGEDATA,omitempty"`
|
||||
|
||||
// Only one of Currency and OrigCurrency can ever be Valid() for the same transaction
|
||||
Currency *Currency `xml:"CURRENCY,omitempty"` // Represents the currency of TrnAmt (instead of CURDEF in STMTRS) if Valid
|
||||
OrigCurrency *Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency TrnAmt was converted to STMTRS' CURDEF from if Valid
|
||||
Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST (Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST.)
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct is valid OFX
|
||||
func (t Transaction) Valid(version ofxVersion) (bool, error) {
|
||||
var emptyDate Date
|
||||
if !t.TrnType.Valid() || t.TrnType == TrnTypeHold {
|
||||
return false, errors.New("Transaction.TrnType invalid")
|
||||
} else if t.DtPosted.Equal(emptyDate) {
|
||||
return false, errors.New("Transaction.DtPosted not filled")
|
||||
} else if len(t.FiTID) == 0 {
|
||||
return false, errors.New("Transaction.FiTID empty")
|
||||
} else if len(t.CorrectFiTID) > 0 && t.CorrectAction.Valid() {
|
||||
return false, errors.New("Transaction.CorrectFiTID nonempty but CorrectAction invalid")
|
||||
} else if len(t.Name) > 0 && t.Payee != nil {
|
||||
return false, errors.New("Only one of Transaction.Name and Payee may be specified")
|
||||
}
|
||||
if t.Payee != nil {
|
||||
if ok, err := t.Payee.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if t.BankAcctTo != nil && t.CCAcctTo != nil {
|
||||
return false, errors.New("Only one of Transaction.BankAcctTo and CCAcctTo may be specified")
|
||||
} else if t.BankAcctTo != nil {
|
||||
if ok, err := t.BankAcctTo.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
} else if t.CCAcctTo != nil {
|
||||
if ok, err := t.CCAcctTo.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if version < OfxVersion220 && len(t.ImageData) > 0 {
|
||||
return false, errors.New("Transaction.ImageData only supportd for OFX > 220")
|
||||
} else if len(t.ImageData) > 2 {
|
||||
return false, errors.New("Only 2 of ImageData allowed in Transaction")
|
||||
}
|
||||
var ok1, ok2 bool
|
||||
if t.Currency != nil {
|
||||
ok1, _ = t.Currency.Valid()
|
||||
}
|
||||
if t.OrigCurrency != nil {
|
||||
ok2, _ = t.OrigCurrency.Valid()
|
||||
}
|
||||
if ok1 && ok2 {
|
||||
return false, errors.New("Currency and OrigCurrency both supplied for Pending Transaction, only one allowed")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TransactionList represents a list of bank transactions, and also includes
|
||||
// the date range its transactions cover.
|
||||
type TransactionList struct {
|
||||
XMLName xml.Name `xml:"BANKTRANLIST"`
|
||||
DtStart Date `xml:"DTSTART"` // Start date for transaction data
|
||||
DtEnd Date `xml:"DTEND"` // Value that client should send in next <DTSTART> request to ensure that it does not miss any transactions
|
||||
Transactions []Transaction `xml:"STMTTRN,omitempty"`
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct is valid OFX
|
||||
func (l TransactionList) Valid(version ofxVersion) (bool, error) {
|
||||
var emptyDate Date
|
||||
if l.DtStart.Equal(emptyDate) {
|
||||
return false, errors.New("TransactionList.DtStart not filled")
|
||||
} else if l.DtEnd.Equal(emptyDate) {
|
||||
return false, errors.New("TransactionList.DtEnd not filled")
|
||||
}
|
||||
for _, t := range l.Transactions {
|
||||
if ok, err := t.Valid(version); !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PendingTransaction represents a single pending transaction. It is similar to
|
||||
// Transaction, but is not finalized (and may never be). For instance, it lacks
|
||||
// FiTID and DtPosted fields.
|
||||
type PendingTransaction struct {
|
||||
XMLName xml.Name `xml:"STMTTRNP"`
|
||||
TrnType trnType `xml:"TRNTYPE"` // One of CREDIT, DEBIT, INT (interest earned or paid. Note: Depends on signage of amount), DIV, FEE, SRVCHG (service charge), DEP (deposit), ATM (Note: Depends on signage of amount), POS (Note: Depends on signage of amount), XFER, CHECK, PAYMENT, CASH, DIRECTDEP, DIRECTDEBIT, REPEATPMT, HOLD, OTHER
|
||||
DtTran Date `xml:"DTTRAN"`
|
||||
DtExpire *Date `xml:"DTEXPIRE,omitempty"` // only valid for TrnType==HOLD, the date the hold will expire
|
||||
TrnAmt Amount `xml:"TRNAMT"`
|
||||
RefNum String `xml:"REFNUM,omitempty"`
|
||||
Name String `xml:"NAME,omitempty"`
|
||||
ExtdName String `xml:"EXTDNAME,omitempty"` // Extended name of payee or transaction description
|
||||
Memo String `xml:"MEMO,omitempty"` // Extra information (not in NAME)
|
||||
ImageData []ImageData `xml:"IMAGEDATA,omitempty"`
|
||||
|
||||
// Only one of Currency and OrigCurrency can ever be Valid() for the same transaction
|
||||
Currency Currency `xml:"CURRENCY,omitempty"` // Represents the currency of TrnAmt (instead of CURDEF in STMTRS) if Valid
|
||||
OrigCurrency Currency `xml:"ORIGCURRENCY,omitempty"` // Represents the currency TrnAmt was converted to STMTRS' CURDEF from if Valid
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct is valid OFX
|
||||
func (t PendingTransaction) Valid() (bool, error) {
|
||||
var emptyDate Date
|
||||
if !t.TrnType.Valid() {
|
||||
return false, errors.New("PendingTransaction.TrnType invalid")
|
||||
} else if t.DtTran.Equal(emptyDate) {
|
||||
return false, errors.New("PendingTransaction.DtTran not filled")
|
||||
} else if len(t.Name) == 0 {
|
||||
return false, errors.New("PendingTransaction.Name empty")
|
||||
}
|
||||
ok1, _ := t.Currency.Valid()
|
||||
ok2, _ := t.OrigCurrency.Valid()
|
||||
if ok1 && ok2 {
|
||||
return false, errors.New("Currency and OrigCurrency both supplied for Pending Transaction, only one allowed")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PendingTransactionList represents a list of pending transactions, along with
|
||||
// the date they were generated
|
||||
type PendingTransactionList struct {
|
||||
XMLName xml.Name `xml:"BANKTRANLISTP"`
|
||||
DtAsOf Date `xml:"DTASOF"` // Date and time this set of pending transactions was generated
|
||||
Transactions []PendingTransaction `xml:"STMTTRNP,omitempty"`
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct is valid OFX
|
||||
func (l PendingTransactionList) Valid() (bool, error) {
|
||||
var emptyDate Date
|
||||
if l.DtAsOf.Equal(emptyDate) {
|
||||
return false, errors.New("PendingTransactionList.DtAsOf not filled")
|
||||
}
|
||||
for _, t := range l.Transactions {
|
||||
if ok, err := t.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Balance represents a generic (free-form) balance defined by an FI.
|
||||
type Balance struct {
|
||||
XMLName xml.Name `xml:"BAL"`
|
||||
Name String `xml:"NAME"`
|
||||
Desc String `xml:"DESC"`
|
||||
|
||||
// Balance type:
|
||||
// DOLLAR = dollar (value formatted DDDD.cc)
|
||||
// PERCENT = percentage (value formatted XXXX.YYYY)
|
||||
// NUMBER = number (value formatted as is)
|
||||
BalType balType `xml:"BALTYPE"`
|
||||
|
||||
Value Amount `xml:"VALUE"`
|
||||
DtAsOf *Date `xml:"DTASOF,omitempty"`
|
||||
Currency *Currency `xml:"CURRENCY,omitempty"` // if BALTYPE is DOLLAR
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct is valid OFX
|
||||
func (b Balance) Valid() (bool, error) {
|
||||
if len(b.Name) == 0 || len(b.Desc) == 0 {
|
||||
return false, errors.New("Balance Name and Desc not supplied")
|
||||
}
|
||||
if !b.BalType.Valid() {
|
||||
return false, errors.New("Balance BALTYPE not specified")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// StatementResponse represents a bank account statement, including its
|
||||
// balances and possibly transactions. It is a response to StatementRequest, or
|
||||
// sometimes provided as part of an OFX file downloaded manually from an FI.
|
||||
type StatementResponse struct {
|
||||
XMLName xml.Name `xml:"STMTTRNRS"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
Status Status `xml:"STATUS"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
CurDef CurrSymbol `xml:"STMTRS>CURDEF"`
|
||||
BankAcctFrom BankAcct `xml:"STMTRS>BANKACCTFROM"`
|
||||
BankTranList *TransactionList `xml:"STMTRS>BANKTRANLIST,omitempty"`
|
||||
BankTranListP *PendingTransactionList `xml:"STMTRS>BANKTRANLISTP,omitempty"`
|
||||
BalAmt Amount `xml:"STMTRS>LEDGERBAL>BALAMT"`
|
||||
DtAsOf Date `xml:"STMTRS>LEDGERBAL>DTASOF"`
|
||||
AvailBalAmt *Amount `xml:"STMTRS>AVAILBAL>BALAMT,omitempty"`
|
||||
AvailDtAsOf *Date `xml:"STMTRS>AVAILBAL>DTASOF,omitempty"`
|
||||
CashAdvBalAmt *Amount `xml:"STMTRS>CASHADVBALAMT,omitempty"` // Only for CREDITLINE accounts, available balance for cash advances
|
||||
IntRate *Amount `xml:"STMTRS>INTRATE,omitempty"` // Current interest rate
|
||||
BalList []Balance `xml:"STMTRS>BALLIST>BAL,omitempty"`
|
||||
MktgInfo String `xml:"STMTRS>MKTGINFO,omitempty"` // Marketing information
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (sr *StatementResponse) Name() string {
|
||||
return "STMTTRNRS"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||
func (sr *StatementResponse) Valid(version ofxVersion) (bool, error) {
|
||||
var emptyDate Date
|
||||
if ok, err := sr.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
} else if ok, err := sr.Status.Valid(); !ok {
|
||||
return false, err
|
||||
} else if ok, err := sr.CurDef.Valid(); !ok {
|
||||
return false, err
|
||||
} else if ok, err := sr.BankAcctFrom.Valid(); !ok {
|
||||
return false, err
|
||||
} else if sr.DtAsOf.Equal(emptyDate) {
|
||||
return false, errors.New("StatementResponse.DtAsOf not filled")
|
||||
} else if (sr.AvailBalAmt == nil) != (sr.AvailDtAsOf == nil) {
|
||||
return false, errors.New("StatementResponse.Avail* must both either be present or absent")
|
||||
}
|
||||
if sr.BankTranList != nil {
|
||||
if ok, err := sr.BankTranList.Valid(version); !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if sr.BankTranListP != nil {
|
||||
if version < OfxVersion220 {
|
||||
return false, errors.New("StatementResponse.BankTranListP invalid for OFX < 2.2")
|
||||
}
|
||||
if ok, err := sr.BankTranListP.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
for _, bal := range sr.BalList {
|
||||
if ok, err := bal.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Type returns which message set this message belongs to (which Response
|
||||
// element of type []Message it belongs to)
|
||||
func (sr *StatementResponse) Type() messageType {
|
||||
return BankRs
|
||||
}
|
||||
104
vendor/github.com/aclindsa/ofxgo/basic_client.go
generated
vendored
104
vendor/github.com/aclindsa/ofxgo/basic_client.go
generated
vendored
@@ -1,104 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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,
|
||||
// defaults are used
|
||||
SpecVersion ofxVersion // VERSION in header
|
||||
AppID string // SONRQ>APPID
|
||||
AppVer string // SONRQ>APPVER
|
||||
|
||||
// Don't insert newlines or indentation when marshalling to SGML/XML
|
||||
NoIndent bool
|
||||
// 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
|
||||
// Requests as. Defaults to "203" if the client's SpecVersion field is empty.
|
||||
func (c *BasicClient) OfxVersion() ofxVersion {
|
||||
if c.SpecVersion.Valid() {
|
||||
return c.SpecVersion
|
||||
}
|
||||
return OfxVersion203
|
||||
}
|
||||
|
||||
// ID returns this BasicClient's OFX AppID field, defaulting to "OFXGO" if
|
||||
// unspecified.
|
||||
func (c *BasicClient) ID() String {
|
||||
if len(c.AppID) > 0 {
|
||||
return String(c.AppID)
|
||||
}
|
||||
return String("OFXGO")
|
||||
}
|
||||
|
||||
// Version returns this BasicClient's version number as a string, defaulting to
|
||||
// "0001" if unspecified.
|
||||
func (c *BasicClient) Version() String {
|
||||
if len(c.AppVer) > 0 {
|
||||
return String(c.AppVer)
|
||||
}
|
||||
return String("0001")
|
||||
}
|
||||
|
||||
// IndentRequests returns true if the marshaled XML should be indented (and
|
||||
// contain newlines, since the two are linked in the current implementation)
|
||||
func (c *BasicClient) IndentRequests() bool {
|
||||
return !c.NoIndent
|
||||
}
|
||||
|
||||
// CarriageReturnNewLines returns true if carriage returns should be used on new lines, false otherwise
|
||||
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")
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
105
vendor/github.com/aclindsa/ofxgo/client.go
generated
vendored
105
vendor/github.com/aclindsa/ofxgo/client.go
generated
vendored
@@ -1,105 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 also provides the Request and RequestNoParse helper methods to aid in
|
||||
// making and parsing requests.
|
||||
type Client interface {
|
||||
// Used to fill out a Request object
|
||||
OfxVersion() ofxVersion
|
||||
ID() String
|
||||
Version() String
|
||||
IndentRequests() bool
|
||||
CarriageReturnNewLines() bool
|
||||
|
||||
// Request marshals a Request object into XML, makes an HTTP request
|
||||
// against it's URL, and then unmarshals the response into a Response
|
||||
// object.
|
||||
//
|
||||
// Before being marshaled, some of the the Request object's values are
|
||||
// overwritten, namely those dictated by the BasicClient's configuration
|
||||
// (Version, 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 inspected by the caller.
|
||||
Request(r *Request) (*Response, error)
|
||||
|
||||
// RequestNoParse marshals a Request object into XML, makes an HTTP
|
||||
// request, and returns the raw HTTP response. Unlike RawRequest(), it
|
||||
// takes client settings into account. Unlike Request(), it doesn't parse
|
||||
// the response into an ofxgo.Request object.
|
||||
//
|
||||
// Caveat: The caller is responsible for closing the http Response.Body
|
||||
// (see the http module's documentation for more information)
|
||||
RequestNoParse(r *Request) (*http.Response, error)
|
||||
|
||||
// RawRequest is little more than a thin wrapper around http.Post
|
||||
//
|
||||
// In most cases, you should probably be using Request() instead, but
|
||||
// RawRequest can be useful if you need to read the raw unparsed http
|
||||
// response yourself (perhaps for downloading an OFX file for use by an
|
||||
// external program, or debugging server behavior), or have a handcrafted
|
||||
// request you'd like to try.
|
||||
//
|
||||
// Caveats: RawRequest does *not* take client settings into account as
|
||||
// Client.Request() does, so your particular server may or may not like
|
||||
// whatever we read from 'r'. The caller is responsible for closing the
|
||||
// http Response.Body (see the http module's documentation for more
|
||||
// information)
|
||||
RawRequest(URL string, r io.Reader) (*http.Response, error)
|
||||
}
|
||||
|
||||
type clientCreationFunc func(*BasicClient) Client
|
||||
|
||||
// GetClient returns a new Client for a given URL. It attempts to find a
|
||||
// specialized client for this URL, but simply returns the passed-in
|
||||
// BasicClient if no such match is found.
|
||||
func GetClient(URL string, bc *BasicClient) Client {
|
||||
clients := []struct {
|
||||
URL string
|
||||
Func clientCreationFunc
|
||||
}{
|
||||
{"https://ofx.discovercard.com", NewDiscoverCardClient},
|
||||
{"https://vesnc.vanguard.com/us/OfxDirectConnectServlet", NewVanguardClient},
|
||||
}
|
||||
for _, client := range clients {
|
||||
if client.URL == strings.Trim(URL, "/") {
|
||||
return client.Func(bc)
|
||||
}
|
||||
}
|
||||
return bc
|
||||
}
|
||||
|
||||
// clientRequestNoParse can be used for building clients' RequestNoParse
|
||||
// methods if they require fairly standard behavior
|
||||
func clientRequestNoParse(c Client, r *Request) (*http.Response, error) {
|
||||
r.SetClientFields(c)
|
||||
|
||||
b, err := r.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.RawRequest(r.URL, b)
|
||||
}
|
||||
|
||||
// clientRequest can be used for building clients' Request methods if they
|
||||
// require fairly standard behavior
|
||||
func clientRequest(c Client, r *Request) (*Response, error) {
|
||||
response, err := c.RequestNoParse(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
ofxresp, err := ParseResponse(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ofxresp, nil
|
||||
}
|
||||
372
vendor/github.com/aclindsa/ofxgo/common.go
generated
vendored
372
vendor/github.com/aclindsa/ofxgo/common.go
generated
vendored
@@ -1,372 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
//go:generate ./generate_constants.py
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
|
||||
func writeHeader(b *bytes.Buffer, v ofxVersion, carriageReturn bool) error {
|
||||
// Write the header appropriate to our version
|
||||
switch v {
|
||||
case OfxVersion102, OfxVersion103, OfxVersion151, OfxVersion160:
|
||||
header := `OFXHEADER:100
|
||||
DATA:OFXSGML
|
||||
VERSION:` + v.String() + `
|
||||
SECURITY:NONE
|
||||
ENCODING:USASCII
|
||||
CHARSET:1252
|
||||
COMPRESSION:NONE
|
||||
OLDFILEUID:NONE
|
||||
NEWFILEUID:NONE
|
||||
|
||||
`
|
||||
if carriageReturn {
|
||||
header = strings.Replace(header, "\n", "\r\n", -1)
|
||||
}
|
||||
b.WriteString(header)
|
||||
case OfxVersion200, OfxVersion201, OfxVersion202, OfxVersion203, OfxVersion210, OfxVersion211, OfxVersion220:
|
||||
b.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>`)
|
||||
if carriageReturn {
|
||||
b.WriteByte('\r')
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(`<?OFX OFXHEADER="200" VERSION="` + v.String() + `" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>`)
|
||||
if carriageReturn {
|
||||
b.WriteByte('\r')
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
default:
|
||||
return fmt.Errorf("%d is not a valid OFX version string", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Message represents an OFX message in a message set. it is used to ease
|
||||
// marshalling and unmarshalling.
|
||||
type Message interface {
|
||||
Name() string // The name of the OFX transaction wrapper element this represents
|
||||
Valid(version ofxVersion) (bool, error) // Called before a Message is marshaled and after it's unmarshaled to ensure the request or response is valid
|
||||
Type() messageType // The message set this message belongs to
|
||||
}
|
||||
|
||||
type messageType uint
|
||||
|
||||
// These constants are returned by Messages' Type() functions to determine
|
||||
// which message set they belong to
|
||||
const (
|
||||
// Requests
|
||||
SignonRq messageType = iota
|
||||
SignupRq
|
||||
BankRq
|
||||
CreditCardRq
|
||||
LoanRq
|
||||
InvStmtRq
|
||||
InterXferRq
|
||||
WireXferRq
|
||||
BillpayRq
|
||||
EmailRq
|
||||
SecListRq
|
||||
PresDirRq
|
||||
PresDlvRq
|
||||
ProfRq
|
||||
ImageRq
|
||||
|
||||
// Responses
|
||||
SignonRs
|
||||
SignupRs
|
||||
BankRs
|
||||
CreditCardRs
|
||||
LoanRs
|
||||
InvStmtRs
|
||||
InterXferRs
|
||||
WireXferRs
|
||||
BillpayRs
|
||||
EmailRs
|
||||
SecListRs
|
||||
PresDirRs
|
||||
PresDlvRs
|
||||
ProfRs
|
||||
ImageRs
|
||||
)
|
||||
|
||||
func (t messageType) String() string {
|
||||
switch t {
|
||||
case SignonRq:
|
||||
return "SIGNONMSGSRQV1"
|
||||
case SignupRq:
|
||||
return "SIGNUPMSGSRQV1"
|
||||
case BankRq:
|
||||
return "BANKMSGSRQV1"
|
||||
case CreditCardRq:
|
||||
return "CREDITCARDMSGSRQV1"
|
||||
case LoanRq:
|
||||
return "LOANMSGSRQV1"
|
||||
case InvStmtRq:
|
||||
return "INVSTMTMSGSRQV1"
|
||||
case InterXferRq:
|
||||
return "INTERXFERMSGSRQV1"
|
||||
case WireXferRq:
|
||||
return "WIREXFERMSGSRQV1"
|
||||
case BillpayRq:
|
||||
return "BILLPAYMSGSRQV1"
|
||||
case EmailRq:
|
||||
return "EMAILMSGSRQV1"
|
||||
case SecListRq:
|
||||
return "SECLISTMSGSRQV1"
|
||||
case PresDirRq:
|
||||
return "PRESDIRMSGSRQV1"
|
||||
case PresDlvRq:
|
||||
return "PRESDLVMSGSRQV1"
|
||||
case ProfRq:
|
||||
return "PROFMSGSRQV1"
|
||||
case ImageRq:
|
||||
return "IMAGEMSGSRQV1"
|
||||
case SignonRs:
|
||||
return "SIGNONMSGSRSV1"
|
||||
case SignupRs:
|
||||
return "SIGNUPMSGSRSV1"
|
||||
case BankRs:
|
||||
return "BANKMSGSRSV1"
|
||||
case CreditCardRs:
|
||||
return "CREDITCARDMSGSRSV1"
|
||||
case LoanRs:
|
||||
return "LOANMSGSRSV1"
|
||||
case InvStmtRs:
|
||||
return "INVSTMTMSGSRSV1"
|
||||
case InterXferRs:
|
||||
return "INTERXFERMSGSRSV1"
|
||||
case WireXferRs:
|
||||
return "WIREXFERMSGSRSV1"
|
||||
case BillpayRs:
|
||||
return "BILLPAYMSGSRSV1"
|
||||
case EmailRs:
|
||||
return "EMAILMSGSRSV1"
|
||||
case SecListRs:
|
||||
return "SECLISTMSGSRSV1"
|
||||
case PresDirRs:
|
||||
return "PRESDIRMSGSRSV1"
|
||||
case PresDlvRs:
|
||||
return "PRESDLVMSGSRSV1"
|
||||
case ProfRs:
|
||||
return "PROFMSGSRSV1"
|
||||
case ImageRs:
|
||||
return "IMAGEMSGSRSV1"
|
||||
}
|
||||
panic("Invalid messageType")
|
||||
}
|
||||
|
||||
// Map of error codes to their meanings, SEVERITY, and conditions under which
|
||||
// OFX servers are expected to return them
|
||||
var statusMeanings = map[Int][3]string{
|
||||
0: {"Success", "INFO", "The server successfully processed the request."},
|
||||
1: {"Client is up-to-date", "INFO", "Based on the client timestamp, the client has the latest information. The response does not supply any additional information."},
|
||||
2000: {"General error", "ERROR", "Error other than those specified by the remaining error codes. Note: Servers should provide a more specific error whenever possible. Error code 2000 should be reserved for cases in which a more specific code is not available."},
|
||||
2001: {"Invalid account", "ERROR", ""},
|
||||
2002: {"General account error", "ERROR", "Account error not specified by the remaining error codes."},
|
||||
2003: {"Account not found", "ERROR", "The specified account number does not correspond to one of the user’s accounts."},
|
||||
2004: {"Account closed", "ERROR", "The specified account number corresponds to an account that has been closed."},
|
||||
2005: {"Account not authorized", "ERROR", "The user is not authorized to perform this action on the account, or the server does not allow this type of action to be performed on the account."},
|
||||
2006: {"Source account not found", "ERROR", "The specified account number does not correspond to one of the user’s accounts."},
|
||||
2007: {"Source account closed", "ERROR", "The specified account number corresponds to an account that has been closed."},
|
||||
2008: {"Source account not authorized", "ERROR", "The user is not authorized to perform this action on the account, or the server does not allow this type of action to be performed on the account."},
|
||||
2009: {"Destination account not found", "ERROR", "The specified account number does not correspond to one of the user’s accounts."},
|
||||
2010: {"Destination account closed", "ERROR", "The specified account number corresponds to an account that has been closed."},
|
||||
2011: {"Destination account not authorized", "ERROR", "The user is not authorized to perform this action on the account, or the server does not allow this type of action to be performed on the account."},
|
||||
2012: {"Invalid amount", "ERROR", "The specified amount is not valid for this action; for example, the user specified a negative payment amount."},
|
||||
2014: {"Date too soon", "ERROR", "The server cannot process the requested action by the date specified by the user."},
|
||||
2015: {"Date too far in future", "ERROR", "The server cannot accept requests for an action that far in the future."},
|
||||
2016: {"Transaction already committed", "ERROR", "Transaction has entered the processing loop and cannot be modified/cancelled using OFX. The transaction may still be cancelled or modified using other means (for example, a phone call to Customer Service)."},
|
||||
2017: {"Already canceled", "ERROR", "The transaction cannot be canceled or modified because it has already been canceled."},
|
||||
2018: {"Unknown server ID", "ERROR", "The specified server ID does not exist or no longer exists."},
|
||||
2019: {"Duplicate request", "ERROR", "A request with this <TRNUID> has already been received and processed."},
|
||||
2020: {"Invalid date", "ERROR", "The specified datetime stamp cannot be parsed; for instance, the datetime stamp specifies 25:00 hours."},
|
||||
2021: {"Unsupported version", "ERROR", "The server does not support the requested version. The version of the message set specified by the client is not supported by this server."},
|
||||
2022: {"Invalid TAN", "ERROR", "The server was unable to validate the TAN sent in the request."},
|
||||
2023: {"Unknown FITID", "ERROR", "The specified FITID/BILLID does not exist or no longer exists. [BILLID not found (ERROR) in the billing message sets]"},
|
||||
2025: {"Branch ID missing", "ERROR", "A <BRANCHID> value must be provided in the <BANKACCTFROM> aggregate for this country system, but this field is missing."},
|
||||
2026: {"Bank name doesn’t match bank ID", "ERROR", "The value of <BANKNAME> in the <EXTBANKACCTTO> aggregate is inconsistent with the value of <BANKID> in the <BANKACCTTO> aggregate."},
|
||||
2027: {"Invalid date range", "ERROR", "Response for non-overlapping dates, date ranges in the future, et cetera."},
|
||||
2028: {"Requested element unknown", "WARN", "One or more elements of the request were not recognized by the server or the server (as noted in the FI Profile) does not support the elements. The server executed the element transactions it understood and supported. For example, the request file included private tags in a <PMTRQ> but the server was able to execute the rest of the request."},
|
||||
3000: {"MFA Challenge authentication required", "ERROR", "User credentials are correct, but further authentication required. Client should send <MFACHALLENGERQ> in next request."},
|
||||
3001: {"MFA Challenge information is invalid", "ERROR", "User or client information sent in MFACHALLENGEA contains invalid information"},
|
||||
6500: {"<REJECTIFMISSING>Y invalid without <TOKEN>", "ERROR", "This error code may appear in the <SYNCERROR> element of an <xxxSYNCRS> wrapper (in <PRESDLVMSGSRSV1> and V2 message set responses) or the <CODE> contained in any embedded transaction wrappers within a sync response. The corresponding sync request wrapper included <REJECTIFMISSING>Y with <REFRESH>Y or <TOKENONLY>Y, which is illegal."},
|
||||
6501: {"Embedded transactions in request failed to process: Out of date", "WARN", "<REJECTIFMISSING>Y and embedded transactions appeared in the request sync wrapper and the provided <TOKEN> was out of date. This code should be used in the <SYNCERROR> of the response sync wrapper."},
|
||||
6502: {"Unable to process embedded transaction due to out-of-date <TOKEN>", "ERROR", "Used in response transaction wrapper for embedded transactions when <SYNCERROR>6501 appears in the surrounding sync wrapper."},
|
||||
10000: {"Stop check in process", "INFO", "Stop check is already in process."},
|
||||
10500: {"Too many checks to process", "ERROR", "The stop-payment request <STPCHKRQ> specifies too many checks."},
|
||||
10501: {"Invalid payee", "ERROR", "Payee error not specified by the remaining error codes."},
|
||||
10502: {"Invalid payee address", "ERROR", "Some portion of the payee’s address is incorrect or unknown."},
|
||||
10503: {"Invalid payee account number", "ERROR", "The account number <PAYACCT> of the requested payee is invalid."},
|
||||
10504: {"Insufficient funds", "ERROR", "The server cannot process the request because the specified account does not have enough funds."},
|
||||
10505: {"Cannot modify element", "ERROR", "The server does not allow modifications to one or more values in a modification request."},
|
||||
10506: {"Cannot modify source account", "ERROR", "Reserved for future use."},
|
||||
10507: {"Cannot modify destination account", "ERROR", "Reserved for future use."},
|
||||
10508: {"Invalid frequency", "ERROR", "The specified frequency <FREQ> does not match one of the accepted frequencies for recurring transactions."},
|
||||
10509: {"Model already canceled", "ERROR", "The server has already canceled the specified recurring model."},
|
||||
10510: {"Invalid payee ID", "ERROR", "The specified payee ID does not exist or no longer exists."},
|
||||
10511: {"Invalid payee city", "ERROR", "The specified city is incorrect or unknown."},
|
||||
10512: {"Invalid payee state", "ERROR", "The specified state is incorrect or unknown."},
|
||||
10513: {"Invalid payee postal code", "ERROR", "The specified postal code is incorrect or unknown."},
|
||||
10514: {"Transaction already processed", "ERROR", "Transaction has already been sent or date due is past"},
|
||||
10515: {"Payee not modifiable by client", "ERROR", "The server does not allow clients to change payee information."},
|
||||
10516: {"Wire beneficiary invalid", "ERROR", "The specified wire beneficiary does not exist or no longer exists."},
|
||||
10517: {"Invalid payee name", "ERROR", "The server does not recognize the specified payee name."},
|
||||
10518: {"Unknown model ID", "ERROR", "The specified model ID does not exist or no longer exists."},
|
||||
10519: {"Invalid payee list ID", "ERROR", "The specified payee list ID does not exist or no longer exists."},
|
||||
10600: {"Table type not found", "ERROR", "The specified table type is not recognized or does not exist."},
|
||||
12250: {"Investment transaction download not supported", "WARN", "The server does not support investment transaction download."},
|
||||
12251: {"Investment position download not supported", "WARN", "The server does not support investment position download."},
|
||||
12252: {"Investment positions for specified date not available", "WARN", "The server does not support investment positions for the specified date."},
|
||||
12253: {"Investment open order download not supported", "WARN", "The server does not support open order download."},
|
||||
12254: {"Investment balances download not supported", "WARN", "The server does not support investment balances download."},
|
||||
12255: {"401(k) not available for this account", "ERROR", "401(k) information requested from a non- 401(k) account."},
|
||||
12500: {"One or more securities not found", "ERROR", "The server could not find the requested securities."},
|
||||
13000: {"User ID & password will be sent out-of-band", "INFO", "The server will send the user ID and password via postal mail, e-mail, or another means. The accompanying message will provide details."},
|
||||
13500: {"Unable to enroll user", "ERROR", "The server could not enroll the user."},
|
||||
13501: {"User already enrolled", "ERROR", "The server has already enrolled the user."},
|
||||
13502: {"Invalid service", "ERROR", "The server does not support the service <SVC> specified in the service-activation request."},
|
||||
13503: {"Cannot change user information", "ERROR", "The server does not support the <CHGUSERINFORQ> request."},
|
||||
13504: {"<FI> Missing or Invalid in <SONRQ>", "ERROR", "The FI requires the client to provide the <FI> aggregate in the <SONRQ> request, but either none was provided, or the one provided was invalid."},
|
||||
14500: {"1099 forms not available", "ERROR", "1099 forms are not yet available for the tax year requested."},
|
||||
14501: {"1099 forms not available for user ID", "ERROR", "This user does not have any 1099 forms available."},
|
||||
14600: {"W2 forms not available", "ERROR", "W2 forms are not yet available for the tax year requested."},
|
||||
14601: {"W2 forms not available for user ID", "ERROR", "The user does not have any W2 forms available."},
|
||||
14700: {"1098 forms not available", "ERROR", "1098 forms are not yet available for the tax year requested."},
|
||||
14701: {"1098 forms not available for user ID", "ERROR", "The user does not have any 1098 forms available."},
|
||||
15000: {"Must change USERPASS", "INFO", "The user must change his or her <USERPASS> number as part of the next OFX request."},
|
||||
15500: {"Signon invalid", "ERROR", "The user cannot signon because he or she entered an invalid user ID or password."},
|
||||
15501: {"Customer account already in use", "ERROR", "The server allows only one connection at a time, and another user is already signed on. Please try again later."},
|
||||
15502: {"USERPASS lockout", "ERROR", "The server has received too many failed signon attempts for this user. Please call the FI’s technical support number."},
|
||||
15503: {"Could not change USERPASS", "ERROR", "The server does not support the <PINCHRQ> request."},
|
||||
15504: {"Could not provide random data", "ERROR", "The server could not generate random data as requested by the <CHALLENGERQ>."},
|
||||
15505: {"Country system not supported", "ERROR", "The server does not support the country specified in the <COUNTRY> field of the <SONRQ> aggregate."},
|
||||
15506: {"Empty signon not supported", "ERROR", "The server does not support signons not accompanied by some other transaction."},
|
||||
15507: {"Signon invalid without supporting pin change request", "ERROR", "The OFX block associated with the signon does not contain a pin change request and should."},
|
||||
15508: {"Transaction not authorized. ", "ERROR", "Current user is not authorized to perform this action on behalf of the <USERID>."},
|
||||
15510: {"CLIENTUID error", "ERROR", "The CLIENTUID sent by the client was incorrect. User must register the Client UID."},
|
||||
15511: {"MFA error", "ERROR", "User should contact financial institution."},
|
||||
15512: {"AUTHTOKEN required", "ERROR", "User needs to contact financial institution to obtain AUTHTOKEN. Client should send it in the next request."},
|
||||
15513: {"AUTHTOKEN invalid", "ERROR", "The AUTHTOKEN sent by the client was invalid."},
|
||||
16500: {"HTML not allowed", "ERROR", "The server does not accept HTML formatting in the request."},
|
||||
16501: {"Unknown mail To:", "ERROR", "The server was unable to send mail to the specified Internet address."},
|
||||
16502: {"Invalid URL", "ERROR", "The server could not parse the URL."},
|
||||
16503: {"Unable to get URL", "ERROR", "The server was unable to retrieve the information at this URL (e.g., an HTTP 400 or 500 series error)."},
|
||||
}
|
||||
|
||||
// Status represents the status of a Response (both top-level Request objects,
|
||||
// and *Response objects)
|
||||
type Status struct {
|
||||
XMLName xml.Name `xml:"STATUS"`
|
||||
Code Int `xml:"CODE"`
|
||||
Severity String `xml:"SEVERITY"`
|
||||
Message String `xml:"MESSAGE,omitempty"`
|
||||
}
|
||||
|
||||
// Valid returns whether the Status is valid according to the OFX spec
|
||||
func (s *Status) Valid() (bool, error) {
|
||||
switch s.Severity {
|
||||
case "INFO", "WARN", "ERROR":
|
||||
default:
|
||||
return false, errors.New("Invalid STATUS>SEVERITY")
|
||||
}
|
||||
|
||||
if arr, ok := statusMeanings[s.Code]; ok {
|
||||
if arr[1] != string(s.Severity) {
|
||||
return false, errors.New("Unexpected SEVERITY for STATUS>CODE")
|
||||
}
|
||||
} else {
|
||||
return false, errors.New("Unknown OFX status code")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CodeMeaning returns the meaning of the current status Code
|
||||
func (s *Status) CodeMeaning() (string, error) {
|
||||
if arr, ok := statusMeanings[s.Code]; ok {
|
||||
return arr[0], nil
|
||||
}
|
||||
return "", errors.New("Unknown OFX status code")
|
||||
}
|
||||
|
||||
// CodeConditions returns the conditions under which an OFX server is expected
|
||||
// to return the current status Code
|
||||
func (s *Status) CodeConditions() (string, error) {
|
||||
if arr, ok := statusMeanings[s.Code]; ok {
|
||||
return arr[2], nil
|
||||
}
|
||||
return "", errors.New("Unknown OFX status code")
|
||||
}
|
||||
|
||||
// BankAcct represents the identifying information for one bank account
|
||||
type BankAcct struct {
|
||||
XMLName xml.Name // BANKACCTTO or BANKACCTFROM
|
||||
BankID String `xml:"BANKID"`
|
||||
BranchID String `xml:"BRANCHID,omitempty"` // Unused in USA
|
||||
AcctID String `xml:"ACCTID"`
|
||||
AcctType acctType `xml:"ACCTTYPE"` // One of CHECKING, SAVINGS, MONEYMRKT, CREDITLINE, CD
|
||||
AcctKey String `xml:"ACCTKEY,omitempty"` // Unused in USA
|
||||
}
|
||||
|
||||
// Valid returns whether the BankAcct is valid according to the OFX spec
|
||||
func (b BankAcct) Valid() (bool, error) {
|
||||
if len(b.BankID) == 0 {
|
||||
return false, errors.New("BankAcct.BankID empty")
|
||||
}
|
||||
if len(b.AcctID) == 0 {
|
||||
return false, errors.New("BankAcct.AcctID empty")
|
||||
}
|
||||
if !b.AcctType.Valid() {
|
||||
return false, errors.New("Invalid or unspecified BankAcct.AcctType")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CCAcct represents the identifying information for one checking account
|
||||
type CCAcct struct {
|
||||
XMLName xml.Name // CCACCTTO or CCACCTFROM
|
||||
AcctID String `xml:"ACCTID"`
|
||||
AcctKey String `xml:"ACCTKEY,omitempty"` // Unused in USA
|
||||
}
|
||||
|
||||
// Valid returns whether the CCAcct is valid according to the OFX spec
|
||||
func (c CCAcct) Valid() (bool, error) {
|
||||
if len(c.AcctID) == 0 {
|
||||
return false, errors.New("CCAcct.AcctID empty")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InvAcct represents the identifying information for one investment account
|
||||
type InvAcct struct {
|
||||
XMLName xml.Name // INVACCTTO or INVACCTFROM
|
||||
BrokerID String `xml:"BROKERID"`
|
||||
AcctID String `xml:"ACCTID"`
|
||||
}
|
||||
|
||||
// Currency represents one ISO-4217 currency. CURRENCY elements signify that
|
||||
// the transaction containing this Currency struct is in this currency instead
|
||||
// of being converted to the statement's default. ORIGCURRENCY elements signify
|
||||
// that the transaction containing this Currency struct was converted to the
|
||||
// statement's default from the specified currency.
|
||||
type Currency struct {
|
||||
XMLName xml.Name // CURRENCY or ORIGCURRENCY
|
||||
CurRate Amount `xml:"CURRATE"` // Ratio of statement's currency (CURDEF) to transaction currency (CURSYM)
|
||||
CurSym CurrSymbol `xml:"CURSYM"` // ISO-4217 3-character currency identifier
|
||||
}
|
||||
|
||||
// Valid returns whether the Currency is valid according to the OFX spec
|
||||
func (c Currency) Valid() (bool, error) {
|
||||
if c.CurRate.IsInt() && c.CurRate.Num().Int64() == 0 {
|
||||
return false, errors.New("CurRate may not be zero")
|
||||
} else if ok, err := c.CurSym.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
2703
vendor/github.com/aclindsa/ofxgo/constants.go
generated
vendored
2703
vendor/github.com/aclindsa/ofxgo/constants.go
generated
vendored
File diff suppressed because it is too large
Load Diff
91
vendor/github.com/aclindsa/ofxgo/creditcard.go
generated
vendored
91
vendor/github.com/aclindsa/ofxgo/creditcard.go
generated
vendored
@@ -1,91 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
|
||||
// CCStatementRequest represents a request for a credit card statement. It is
|
||||
// used to request balances and/or transactions. See StatementRequest for the
|
||||
// analog for all other bank accounts.
|
||||
type CCStatementRequest struct {
|
||||
XMLName xml.Name `xml:"CCSTMTTRNRQ"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
TAN String `xml:"TAN,omitempty"`
|
||||
// TODO OFXEXTENSION
|
||||
CCAcctFrom CCAcct `xml:"CCSTMTRQ>CCACCTFROM"`
|
||||
DtStart *Date `xml:"CCSTMTRQ>INCTRAN>DTSTART,omitempty"`
|
||||
DtEnd *Date `xml:"CCSTMTRQ>INCTRAN>DTEND,omitempty"`
|
||||
Include Boolean `xml:"CCSTMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance)
|
||||
IncludePending Boolean `xml:"CCSTMTRQ>INCLUDEPENDING,omitempty"` // Include pending transactions
|
||||
IncTranImg Boolean `xml:"CCSTMTRQ>INCTRANIMG,omitempty"` // Include transaction images
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (r *CCStatementRequest) Name() string {
|
||||
return "CCSTMTTRNRQ"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||
// into XML/SGML
|
||||
func (r *CCStatementRequest) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := r.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
// TODO implement
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Type returns which message set this message belongs to (which Request
|
||||
// element of type []Message it should appended to)
|
||||
func (r *CCStatementRequest) Type() messageType {
|
||||
return CreditCardRq
|
||||
}
|
||||
|
||||
// CCStatementResponse represents a credit card statement, including its
|
||||
// balances and possibly transactions. It is a response to CCStatementRequest,
|
||||
// or sometimes provided as part of an OFX file downloaded manually from an FI.
|
||||
type CCStatementResponse struct {
|
||||
XMLName xml.Name `xml:"CCSTMTTRNRS"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
Status Status `xml:"STATUS"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
CurDef CurrSymbol `xml:"CCSTMTRS>CURDEF"`
|
||||
CCAcctFrom CCAcct `xml:"CCSTMTRS>CCACCTFROM"`
|
||||
BankTranList *TransactionList `xml:"CCSTMTRS>BANKTRANLIST,omitempty"`
|
||||
//BANKTRANLISTP
|
||||
BalAmt Amount `xml:"CCSTMTRS>LEDGERBAL>BALAMT"`
|
||||
DtAsOf Date `xml:"CCSTMTRS>LEDGERBAL>DTASOF"`
|
||||
AvailBalAmt *Amount `xml:"CCSTMTRS>AVAILBAL>BALAMT,omitempty"`
|
||||
AvailDtAsOf *Date `xml:"CCSTMTRS>AVAILBAL>DTASOF,omitempty"`
|
||||
CashAdvBalAmt Amount `xml:"CCSTMTRS>CASHADVBALAMT,omitempty"` // Only for CREDITLINE accounts, available balance for cash advances
|
||||
IntRatePurch Amount `xml:"CCSTMTRS>INTRATEPURCH,omitempty"` // Current interest rate for purchases
|
||||
IntRateCash Amount `xml:"CCSTMTRS>INTRATECASH,omitempty"` // Current interest rate for cash advances
|
||||
IntRateXfer Amount `xml:"CCSTMTRS>INTRATEXFER,omitempty"` // Current interest rate for cash advances
|
||||
RewardName String `xml:"CCSTMTRS>REWARDINFO>NAME,omitempty"` // Name of the reward program referred to by the next two elements
|
||||
RewardBal Amount `xml:"CCSTMTRS>REWARDINFO>REWARDBAL,omitempty"` // Current balance of the reward program
|
||||
RewardEarned Amount `xml:"CCSTMTRS>REWARDINFO>REWARDEARNED,omitempty"` // Reward amount earned YTD
|
||||
BalList []Balance `xml:"CCSTMTRS>BALLIST>BAL,omitempty"`
|
||||
MktgInfo String `xml:"CCSTMTRS>MKTGINFO,omitempty"` // Marketing information
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (sr *CCStatementResponse) Name() string {
|
||||
return "CCSTMTTRNRS"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||
func (sr *CCStatementResponse) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := sr.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
//TODO implement
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Type returns which message set this message belongs to (which Response
|
||||
// element of type []Message it belongs to)
|
||||
func (sr *CCStatementResponse) Type() messageType {
|
||||
return CreditCardRs
|
||||
}
|
||||
109
vendor/github.com/aclindsa/ofxgo/discovercard_client.go
generated
vendored
109
vendor/github.com/aclindsa/ofxgo/discovercard_client.go
generated
vendored
@@ -1,109 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DiscoverCardClient provides a Client implementation which handles
|
||||
// DiscoverCard's broken HTTP header behavior. DiscoverCardClient uses default,
|
||||
// non-zero settings, if its fields are not initialized.
|
||||
type DiscoverCardClient struct {
|
||||
*BasicClient
|
||||
}
|
||||
|
||||
// NewDiscoverCardClient returns a Client interface configured to handle
|
||||
// Discover Card's brand of idiosyncrasy
|
||||
func NewDiscoverCardClient(bc *BasicClient) Client {
|
||||
return &DiscoverCardClient{bc}
|
||||
}
|
||||
|
||||
func discoverCardHTTPPost(URL string, r io.Reader) (*http.Response, error) {
|
||||
// Either convert or copy to a bytes.Buffer to be able to determine the
|
||||
// request length for the Content-Length header
|
||||
buf, ok := r.(*bytes.Buffer)
|
||||
if !ok {
|
||||
buf = &bytes.Buffer{}
|
||||
_, err := io.Copy(buf, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
url, err := url.Parse(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := url.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
|
||||
// Discover requires only these headers and in this exact order, or it
|
||||
// returns HTTP 403
|
||||
headers := fmt.Sprintf("POST %s HTTP/1.1\r\n"+
|
||||
"Content-Type: application/x-ofx\r\n"+
|
||||
"Host: %s\r\n"+
|
||||
"Content-Length: %d\r\n"+
|
||||
"Connection: Keep-Alive\r\n"+
|
||||
"\r\n", path, url.Hostname(), buf.Len())
|
||||
|
||||
host := url.Host
|
||||
if url.Port() == "" {
|
||||
host += ":443"
|
||||
}
|
||||
|
||||
// BUGBUG: cannot do defer conn.Close() until body is read,
|
||||
// we are "leaking" a socket here, but it will be finalized
|
||||
conn, err := tls.Dial("tcp", host, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Fprint(conn, headers)
|
||||
_, err = io.Copy(conn, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
response, err := discoverCardHTTPPost(URL, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode != 200 {
|
||||
return nil, 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 *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)
|
||||
}
|
||||
127
vendor/github.com/aclindsa/ofxgo/doc.go
generated
vendored
127
vendor/github.com/aclindsa/ofxgo/doc.go
generated
vendored
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
Package ofxgo seeks to provide a library to make it easier to query and/or
|
||||
parse financial information with OFX from the comfort of Golang, without having
|
||||
to deal with marshalling/unmarshalling the 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. For more information on OFX and to read
|
||||
the specification, see http://ofx.net.
|
||||
|
||||
There are three main top-level objects defined in ofxgo. These are Client,
|
||||
Request, and Response. The Request and Response objects represent OFX requests
|
||||
and responses as Golang structs. Client contains settings which control how
|
||||
requests and responses are marshalled and unmarshalled (the OFX version used,
|
||||
client id and version, whether to indent SGML/XML tags, etc.), and provides
|
||||
helper methods for making requests and optionally parsing the response using
|
||||
those settings.
|
||||
|
||||
Every Request object contains a SignonRequest element, called Signon. This
|
||||
element contains the username, password (or key), and the ORG and FID fields
|
||||
particular to the financial institution being queried, and an optional ClientUID
|
||||
field (required by some FIs). Likewise, each Response contains a SignonResponse
|
||||
object which contains, among other things, the Status of the request. Any status
|
||||
with a nonzero Code should be inspected for a possible error (using the Severity
|
||||
and Message fields populated by the server, or the CodeMeaning() and
|
||||
CodeConditions() functions which return information about a particular code as
|
||||
specified by the OFX specification).
|
||||
|
||||
Each top-level Request or Response object may contain zero or more messages,
|
||||
sorted into named slices by message set, just as the OFX specification groups
|
||||
them. Here are the supported types of Request/Response objects (along with the
|
||||
name of the slice of Messages they belong to in parentheses):
|
||||
|
||||
Requests:
|
||||
var r AcctInfoRequest // (Signup) Request a list of the valid accounts
|
||||
// for this user
|
||||
var r CCStatementRequest // (CreditCard) Request the balance (and optionally
|
||||
// list of transactions) for a credit card
|
||||
var r StatementRequest // (Bank) Request the balance (and optionally list
|
||||
// of transactions) for a bank account
|
||||
var r InvStatementRequest // (InvStmt) Request balance, transactions,
|
||||
// existing positions, and/or open orders for an
|
||||
// investment account
|
||||
var r SecListRequest // (SecList) Request securities details and prices
|
||||
var r ProfileRequest // (Prof) Request the server's capabilities (which
|
||||
// messages sets it supports, along with features)
|
||||
|
||||
Responses:
|
||||
var r AcctInfoResponse // (Signup) List of the valid accounts for this
|
||||
// user
|
||||
var r CCStatementResponse // (CreditCard) The balance (and optionally list of
|
||||
// transactions) for a credit card
|
||||
var r StatementResponse // (Bank) The balance (and optionally list of
|
||||
// transactions) for a bank account
|
||||
var r InvStatementResponse // (InvStmt) The balance, transactions, existing
|
||||
// positions, and/or open orders for an
|
||||
// investment account
|
||||
var r SecListResponse // (SecList) Returned as a result of
|
||||
// SecListRequest, but only contains request
|
||||
// status
|
||||
var r SecurityList // (SecList) The actual list of securities, prices,
|
||||
// etc. (sent as a result of SecListRequest or
|
||||
// InvStatementRequest)
|
||||
var r ProfileResponse // (Prof) Describes the server's capabilities
|
||||
|
||||
When constructing a Request, simply append the desired message to the message
|
||||
set it belongs to. For Responses, it is the user's responsibility to make type
|
||||
assertions on objects found inside one of these message sets before using them.
|
||||
|
||||
For example, the following code would request a bank statement for a checking
|
||||
account and print the balance:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var client Client // By not initializing them, we accept all default
|
||||
// client values
|
||||
var request Request
|
||||
|
||||
// These are all specific to you and your financial institution
|
||||
request.URL = "https://ofx.example.com"
|
||||
request.Signon.UserID = String("john")
|
||||
request.Signon.UserPass = String("hunter2")
|
||||
request.Signon.Org = String("MyBank")
|
||||
request.Signon.Fid = String("0001")
|
||||
|
||||
uid, err := RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statementRequest := StatementRequest{
|
||||
TrnUID: *uid,
|
||||
BankAcctFrom: BankAcct{
|
||||
BankID: String("123456789"),
|
||||
AcctID: String("11111111111"),
|
||||
AcctType: AcctTypeChecking,
|
||||
},
|
||||
}
|
||||
|
||||
request.Bank = append(request.Bank, &statementRequest)
|
||||
|
||||
response, err := client.Request(request)
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account statement:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if response.Signon.Status.Code != 0 {
|
||||
meaning, _ := response.Signon.Status.CodeMeaning()
|
||||
fmt.Printf("Nonzero signon status (%d: %s) with message: %s\n", response.Signon.Status.Code, meaning, response.Signon.Status.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(response.Bank) < 1 {
|
||||
fmt.Println("No banking messages received")
|
||||
} else if stmt, ok := response.Bank[0].(*StatementResponse); ok {
|
||||
fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf)
|
||||
}
|
||||
|
||||
More usage examples may be found in the example command-line client provided
|
||||
with this library, in the cmd/ofx directory of the source.
|
||||
|
||||
*/
|
||||
package ofxgo
|
||||
239
vendor/github.com/aclindsa/ofxgo/generate_constants.py
generated
vendored
239
vendor/github.com/aclindsa/ofxgo/generate_constants.py
generated
vendored
@@ -1,239 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
enums = {
|
||||
# OFX spec version
|
||||
"OfxVersion": (["102", "103", "151", "160", "200", "201", "202", "203", "210", "211", "220"], "the OFX specification version in use"),
|
||||
|
||||
# Bank/general
|
||||
"AcctType": (["Checking", "Savings", "MoneyMrkt", "CreditLine", "CD"], "types of bank accounts"),
|
||||
"TrnType": (["Credit", "Debit", "Int", "Div", "Fee", "SrvChg", "Dep", "ATM", "POS", "Xfer", "Check", "Payment", "Cash", "DirectDep", "DirectDebit", "RepeatPmt", "Hold", "Other"], "types of transactions. INT, ATM, and POS depend on the signage of the account."),
|
||||
"ImageType": (["Statement", "Transaction", "Tax"], "what this image contains"),
|
||||
"ImageRefType": (["Opaque", "URL", "FormURL"], "the type of reference to the image"),
|
||||
"CheckSup": (["FrontOnly", "BackOnly", "FrontAndBack"], "what portions of the check this image contains"),
|
||||
"CorrectAction": (["Delete", "Replace"], "whether this transaction correction replaces or deletes the transaction matching its CORRECTFITID"),
|
||||
"BalType": (["Dollar", "Percent", "Number"], "how this BAL's VALUE field should be interpreted"),
|
||||
|
||||
# InvStmt
|
||||
"Inv401kSource": (["PreTax", "AfterTax", "Match", "ProfitSharing", "Rollover", "OtherVest", "OtherNonVest"], "the source of money used for this security in a 401(k) account. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST."),
|
||||
"SubAcctType": (["Cash", "Margin", "Short", "Other"], "the sub-account type for a source and/or destination of a transaction. Used in fields named SubAcctFrom, SubAcctTo, SubAcctSec, SubAcctFund, HeldInAcct."),
|
||||
"BuyType": (["Buy", "BuyToCover"], "types of purchases"),
|
||||
"OptAction": (["Exercise", "Assign", "Expire"], "types of actions for options"),
|
||||
"TferAction": (["In", "Out"], "whether the transfer is into or out of this account"),
|
||||
"PosType": (["Long", "Short"], "position type"),
|
||||
"Secured": (["Naked", "Covered"], "how an option is secured"),
|
||||
"Duration": (["Day", "GoodTilCancel", "Immediate"], "how long the investment order is good for"),
|
||||
"Restriction": (["AllOrNone", "MinUnits", "None"], "a special restriction on an investment order"),
|
||||
"UnitType": (["Shares", "Currency"], "type of the UNITS value"),
|
||||
"OptBuyType": (["BuyToOpen", "BuyToClose"], "types of purchases for options"),
|
||||
"SellType": (["Sell", "SellShort"], "types of sales"),
|
||||
"LoanPmtFreq": (["Weekly", "Biweekly", "TwiceMonthly", "Monthly", "FourWeeks", "BiMonthly", "Quarterly", "Semiannually", "Annually", "Other"], "the frequency of loan payments"),
|
||||
"IncomeType": (["CGLong", "CGShort", "Div", "Interest", "Misc"], "types of investment income"),
|
||||
"SellReason": (["Call", "Sell", "Maturity"], "the reason the sell of a debt security was generated: CALL (the debt was called), SELL (the debt was sold), MATURITY (the debt reached maturity)"),
|
||||
"OptSellType": (["SellToClose", "SellToOpen"], "types of sales for options"),
|
||||
"RelType": (["Spread", "Straddle", "None", "Other"], "related option transaction types"),
|
||||
|
||||
# Prof
|
||||
"CharType": (["AlphaOnly", "NumericOnly", "AlphaOrNumeric", "AlphaAndNumeric"], "types of characters allowed in password"),
|
||||
"SyncMode": (["Full", "Lite"], "data synchronization mode supported (see OFX spec for more details)"),
|
||||
"OfxSec": (["None", "Type 1"], "the type of application-level security required for the message set"),
|
||||
|
||||
# SecList
|
||||
"DebtType": (["Coupon", "Zero"], "debt type"),
|
||||
"DebtClass": (["Treasury", "Municipal", "Corporate", "Other"], "the class of debt"),
|
||||
"CouponFreq": (["Monthly", "Quarterly", "Semiannual", "Annual", "Other"], "when debt coupons mature"),
|
||||
"CallType": (["Call", "Put", "Prefund", "Maturity"], "type of next call (for a debt)"),
|
||||
"AssetClass": (["DomesticBond", "IntlBond", "LargeStock", "SmallStock", "IntlStock", "MoneyMrkt", "Other"], "type of asset classes"),
|
||||
"MfType": (["OpenEnd", "CloseEnd", "Other"], "types of mutual funds"),
|
||||
"OptType": (["Put", "Call"], "whether the option is a PUT or a CALL"),
|
||||
"StockType": (["Common", "Preferred", "Convertible", "Other"], "types of stock"),
|
||||
|
||||
# Signup
|
||||
"HolderType": (["Individual", "Joint", "Custodial", "Trust", "Other"], "how the account is held"),
|
||||
"AcctClassification": (["Personal", "Business", "Corporate", "Other"], "the type of an account"),
|
||||
"SvcStatus": (["Avail", "Pend", "Active"], "the status of the account: AVAIL = Available, but not yet requested, PEND = Requested, but not yet available, ACTIVE = In use"),
|
||||
"UsProductType": (["401K", "403B", "IRA", "KEOGH", "Other", "SARSEP", "Simple", "Normal", "TDA", "Trust", "UGMA"], "type of investment account (in the US)"),
|
||||
}
|
||||
|
||||
header = """package ofxgo
|
||||
|
||||
/*
|
||||
* Do not edit this file by hand. It is auto-generated by calling `go generate`.
|
||||
* To make changes, edit generate_constants.py, re-run `go generate`, and check
|
||||
* in the result.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
"""
|
||||
|
||||
template = """
|
||||
type {enumLower} uint
|
||||
|
||||
// {enum}* constants represent {comment}
|
||||
const (
|
||||
{constNames})
|
||||
|
||||
var {enumLower}s = [...]string{{"{upperValueString}"}}
|
||||
|
||||
func (e {enumLower}) Valid() bool {{
|
||||
// This check is mostly out of paranoia, ensuring e != 0 should be
|
||||
// sufficient
|
||||
return e >= {firstValue} && e <= {lastValue}
|
||||
}}
|
||||
|
||||
func (e {enumLower}) String() string {{
|
||||
if e.Valid() {{
|
||||
return {enumLower}s[e-1]
|
||||
}}
|
||||
return fmt.Sprintf("invalid {enumLower} (%d)", e)
|
||||
}}
|
||||
|
||||
func (e *{enumLower}) FromString(in string) error {{
|
||||
value := strings.TrimSpace(in)
|
||||
|
||||
for i, s := range {enumLower}s {{
|
||||
if s == value {{
|
||||
*e = {enumLower}(i + 1)
|
||||
return nil
|
||||
}}
|
||||
}}
|
||||
*e = 0
|
||||
return errors.New("Invalid {enum}: \\\"" + in + "\\\"")
|
||||
}}
|
||||
|
||||
func (e *{enumLower}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{
|
||||
var value string
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {{
|
||||
return err
|
||||
}}
|
||||
|
||||
return e.FromString(value)
|
||||
}}
|
||||
|
||||
func (e *{enumLower}) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {{
|
||||
if !e.Valid() {{
|
||||
return nil
|
||||
}}
|
||||
enc.EncodeElement({enumLower}s[*e-1], start)
|
||||
return nil
|
||||
}}
|
||||
|
||||
// New{enum} returns returns an 'enum' value of type {enumLower} given its
|
||||
// string representation
|
||||
func New{enum}(s string) ({enumLower}, error) {{
|
||||
var e {enumLower}
|
||||
err := e.FromString(s)
|
||||
if err != nil {{
|
||||
return 0, err
|
||||
}}
|
||||
return e, nil
|
||||
}}
|
||||
"""
|
||||
|
||||
with open("constants.go", 'w') as f:
|
||||
f.write(header)
|
||||
|
||||
for enum in enums:
|
||||
enumLower = enum[:1].lower() + enum[1:].replace(" ", "")
|
||||
firstValue = enum+enums[enum][0][0].replace(" ", "")
|
||||
lastValue = enum+enums[enum][0][-1].replace(" ", "")
|
||||
|
||||
comment = enums[enum][1]
|
||||
|
||||
constNames = "\t{firstValue} {enumLower} = 1 + iota\n".format(
|
||||
enum=enum,
|
||||
firstValue=firstValue,
|
||||
enumLower=enumLower)
|
||||
for value in enums[enum][0][1:]:
|
||||
constNames += "\t{enum}{value}\n".format(
|
||||
enum=enum,
|
||||
value=value.replace(" ", ""))
|
||||
|
||||
upperValueString = "\", \"".join([s.upper() for s in enums[enum][0]])
|
||||
|
||||
f.write(template.format(enum=enum,
|
||||
enumLower=enumLower,
|
||||
comment=comment,
|
||||
firstValue=firstValue,
|
||||
lastValue=lastValue,
|
||||
constNames=constNames,
|
||||
upperValueString=upperValueString))
|
||||
|
||||
test_header = """package ofxgo
|
||||
|
||||
/*
|
||||
* Do not edit this file by hand. It is auto-generated by calling `go generate`.
|
||||
* To make changes, edit generate_constants.py, re-run `go generate`, and check
|
||||
* in the result.
|
||||
*/
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
"""
|
||||
|
||||
test_template = """
|
||||
func Test{enum}(t *testing.T) {{
|
||||
e, err := New{enum}("{firstValueUpper}")
|
||||
if err != nil {{
|
||||
t.Fatalf("Unexpected error creating new {enum} from string \\\"{firstValueUpper}\\\"\\n")
|
||||
}}
|
||||
if !e.Valid() {{
|
||||
t.Fatalf("{enum} unexpectedly invalid\\n")
|
||||
}}
|
||||
err = e.FromString("{lastValueUpper}")
|
||||
if err != nil {{
|
||||
t.Fatalf("Unexpected error on {enum}.FromString(\\\"{lastValueUpper}\\\")\\n")
|
||||
}}
|
||||
if e.String() != "{lastValueUpper}" {{
|
||||
t.Fatalf("{enum}.String() expected to be \\\"{lastValueUpper}\\\"\\n")
|
||||
}}
|
||||
|
||||
marshalHelper(t, "{lastValueUpper}", &e)
|
||||
|
||||
overwritten, err := New{enum}("THISWILLNEVERBEAVALIDENUMSTRING")
|
||||
if err == nil {{
|
||||
t.Fatalf("Expected error creating new {enum} from string \\\"THISWILLNEVERBEAVALIDENUMSTRING\\\"\\n")
|
||||
}}
|
||||
if overwritten.Valid() {{
|
||||
t.Fatalf("{enum} created with string \\\"THISWILLNEVERBEAVALIDENUMSTRING\\\" should not be valid\\n")
|
||||
}}
|
||||
if !strings.Contains(strings.ToLower(overwritten.String()), "invalid") {{
|
||||
t.Fatalf("{enum} created with string \\\"THISWILLNEVERBEAVALIDENUMSTRING\\\" should not return valid string from String()\\n")
|
||||
}}
|
||||
|
||||
b, err := xml.Marshal(&overwritten)
|
||||
if err != nil {{
|
||||
t.Fatalf("Unexpected error on xml.Marshal({enum}): %s\\n", err)
|
||||
}}
|
||||
if string(b) != "" {{
|
||||
t.Fatalf("Expected empty string, got '%s'\\n", string(b))
|
||||
}}
|
||||
|
||||
unmarshalHelper(t, "{lastValueUpper}", &e, &overwritten)
|
||||
|
||||
err = xml.Unmarshal([]byte("<GARBAGE><!LALDK>"), &overwritten)
|
||||
if err == nil {{
|
||||
t.Fatalf("Expected error unmarshalling garbage value\\n")
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
with open("constants_test.go", 'w') as f:
|
||||
f.write(test_header)
|
||||
|
||||
for enum in enums:
|
||||
firstValueUpper = enums[enum][0][0].upper()
|
||||
lastValueUpper = enums[enum][0][-1].upper()
|
||||
f.write(test_template.format(enum=enum,
|
||||
firstValueUpper=firstValueUpper,
|
||||
lastValueUpper=lastValueUpper))
|
||||
1414
vendor/github.com/aclindsa/ofxgo/invstmt.go
generated
vendored
1414
vendor/github.com/aclindsa/ofxgo/invstmt.go
generated
vendored
File diff suppressed because it is too large
Load Diff
344
vendor/github.com/aclindsa/ofxgo/leaf_elements.go
generated
vendored
344
vendor/github.com/aclindsa/ofxgo/leaf_elements.go
generated
vendored
@@ -1,344 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
// A list of all the leaf elements in OFX 1.0.3 (the last SGML version of the
|
||||
// spec). These are all the elements that are possibly left unclosed, and which
|
||||
// can have no children of their own. Fortunately these two sets of elements
|
||||
// are the same. We use this list when parsing to remove ambiguities about
|
||||
// element nesting.
|
||||
//
|
||||
// Generated using the following command with the 1.0.3 SPEC .dtd file:
|
||||
// # sed -rn 's/^<!ELEMENT\s+([A-Z0-9]+)\s+-\s+[oO]\s+%.*TYPE\s*>.*$/\t"\1",/p' *.dtd | sort
|
||||
var ofxLeafElements = []string{
|
||||
"ACCESSKEY",
|
||||
"ACCRDINT",
|
||||
"ACCTID",
|
||||
"ACCTKEY",
|
||||
"ACCTREQUIRED",
|
||||
"ACCTTYPE",
|
||||
"ADDR1",
|
||||
"ADDR2",
|
||||
"ADDR3",
|
||||
"ADJAMT",
|
||||
"ADJDATE",
|
||||
"ADJDESC",
|
||||
"ADJNO",
|
||||
"APPID",
|
||||
"APPVER",
|
||||
"ASSETCLASS",
|
||||
"AUCTION",
|
||||
"AUTHTOKEN",
|
||||
"AUTHTOKENFIRST",
|
||||
"AUTHTOKENINFOURL",
|
||||
"AUTHTOKENLABEL",
|
||||
"AVAILACCTS",
|
||||
"AVAILCASH",
|
||||
"AVGCOSTBASIS",
|
||||
"BALAMT",
|
||||
"BALCLOSE",
|
||||
"BALDNLD",
|
||||
"BALMIN",
|
||||
"BALOPEN",
|
||||
"BALTYPE",
|
||||
"BANKID",
|
||||
"BILLREFINFO",
|
||||
"BRANCHID",
|
||||
"BROKERID",
|
||||
"BUYPOWER",
|
||||
"BUYTYPE",
|
||||
"CALLPRICE",
|
||||
"CALLTYPE",
|
||||
"CANADDPAYEE",
|
||||
"CANBILLPAY",
|
||||
"CANCELWND",
|
||||
"CANEMAIL",
|
||||
"CANMODMDLS",
|
||||
"CANMODPMTS",
|
||||
"CANMODXFERS",
|
||||
"CANNOTIFY",
|
||||
"CANPENDING",
|
||||
"CANRECUR",
|
||||
"CANSCHED",
|
||||
"CANUSEDESC",
|
||||
"CANUSERANGE",
|
||||
"CASESEN",
|
||||
"CHARTYPE",
|
||||
"CHECKING",
|
||||
"CHECKNUM",
|
||||
"CHGPINFIRST",
|
||||
"CHGUSERINFO",
|
||||
"CHKANDDEB",
|
||||
"CHKERROR",
|
||||
"CHKNUMEND",
|
||||
"CHKNUMSTART",
|
||||
"CHKSTATUS",
|
||||
"CITY",
|
||||
"CLIENTACTREQ",
|
||||
"CLIENTROUTING",
|
||||
"CLIENTUID",
|
||||
"CLIENTUIDREQ",
|
||||
"CLOSINGAVAIL",
|
||||
"CLTCOOKIE",
|
||||
"CODE",
|
||||
"COMMISSION",
|
||||
"CONFMSG",
|
||||
"CORRECTACTION",
|
||||
"CORRECTFITID",
|
||||
"COUNTRY",
|
||||
"COUPONFREQ",
|
||||
"COUPONRT",
|
||||
"CREDITLIMIT",
|
||||
"CSPHONE",
|
||||
"CURDEF",
|
||||
"CURRATE",
|
||||
"CURSYM",
|
||||
"DATEBIRTH",
|
||||
"DAYPHONE",
|
||||
"DAYSTOPAY",
|
||||
"DAYSWITH",
|
||||
"DEBADJ",
|
||||
"DEBTCLASS",
|
||||
"DEBTTYPE",
|
||||
"DENOMINATOR",
|
||||
"DEPANDCREDIT",
|
||||
"DESC",
|
||||
"DFLTDAYSTOPAY",
|
||||
"DIFFFIRSTPMT",
|
||||
"DIFFLASTPMT",
|
||||
"DOMXFERFEE",
|
||||
"DSCAMT",
|
||||
"DSCDATE",
|
||||
"DSCDESC",
|
||||
"DSCRATE",
|
||||
"DTACCTUP",
|
||||
"DTASOF",
|
||||
"DTAUCTION",
|
||||
"DTAVAIL",
|
||||
"DTCALL",
|
||||
"DTCHANGED",
|
||||
"DTCLIENT",
|
||||
"DTCLOSE",
|
||||
"DTCOUPON",
|
||||
"DTCREATED",
|
||||
"DTDUE",
|
||||
"DTEND",
|
||||
"DTEXPIRE",
|
||||
"DTINFOCHG",
|
||||
"DTMAT",
|
||||
"DTNEXT",
|
||||
"DTOPEN",
|
||||
"DTPLACED",
|
||||
"DTPMTDUE",
|
||||
"DTPMTPRC",
|
||||
"DTPOSTED",
|
||||
"DTPOSTEND",
|
||||
"DTPOSTSTART",
|
||||
"DTPRICEASOF",
|
||||
"DTPROFUP",
|
||||
"DTPURCHASE",
|
||||
"DTSERVER",
|
||||
"DTSETTLE",
|
||||
"DTSTART",
|
||||
"DTTRADE",
|
||||
"DTUSER",
|
||||
"DTXFERPRC",
|
||||
"DTXFERPRJ",
|
||||
"DTYIELDASOF",
|
||||
"DURATION",
|
||||
"EMAIL",
|
||||
"EVEPHONE",
|
||||
"EXTDPMTCHK",
|
||||
"EXTDPMTFOR",
|
||||
"FAXPHONE",
|
||||
"FEE",
|
||||
"FEEMSG",
|
||||
"FEES",
|
||||
"FIASSETCLASS",
|
||||
"FICERTID",
|
||||
"FID",
|
||||
"FIID",
|
||||
"FINALAMT",
|
||||
"FINAME",
|
||||
"FINCHG",
|
||||
"FIRSTNAME",
|
||||
"FITID",
|
||||
"FRACCASH",
|
||||
"FREQ",
|
||||
"FROM",
|
||||
"GAIN",
|
||||
"GENUSERKEY",
|
||||
"GETMIMESUP",
|
||||
"HASEXTDPMT",
|
||||
"HELDINACCT",
|
||||
"IDSCOPE",
|
||||
"INCBAL",
|
||||
"INCIMAGES",
|
||||
"INCLUDE",
|
||||
"INCOMETYPE",
|
||||
"INCOO",
|
||||
"INITIALAMT",
|
||||
"INTLXFERFEE",
|
||||
"INVACCTTYPE",
|
||||
"INVALIDACCTTYPE",
|
||||
"INVDATE",
|
||||
"INVDESC",
|
||||
"INVNO",
|
||||
"INVPAIDAMT",
|
||||
"INVTOTALAMT",
|
||||
"LANGUAGE",
|
||||
"LASTNAME",
|
||||
"LIMITPRICE",
|
||||
"LITMAMT",
|
||||
"LITMDESC",
|
||||
"LOAD",
|
||||
"LOSTSYNC",
|
||||
"MAILSUP",
|
||||
"MARGINBALANCE",
|
||||
"MARKDOWN",
|
||||
"MARKUP",
|
||||
"MAX",
|
||||
"MEMO",
|
||||
"MESSAGE",
|
||||
"MFACHALLENGEFIRST",
|
||||
"MFACHALLENGESUPT",
|
||||
"MFAPHRASEA",
|
||||
"MFAPHRASEID",
|
||||
"MFAPHRASELABEL",
|
||||
"MFTYPE",
|
||||
"MIDDLENAME",
|
||||
"MIN",
|
||||
"MINPMTDUE",
|
||||
"MINUNITS",
|
||||
"MKTGINFO",
|
||||
"MKTVAL",
|
||||
"MODELWND",
|
||||
"MODPENDING",
|
||||
"NAME",
|
||||
"NEWUNITS",
|
||||
"NEWUSERPASS",
|
||||
"NINSTS",
|
||||
"NONCE",
|
||||
"NUMERATOR",
|
||||
"OFXSEC",
|
||||
"OLDUNITS",
|
||||
"OODNLD",
|
||||
"OPTACTION",
|
||||
"OPTBUYTYPE",
|
||||
"OPTIONLEVEL",
|
||||
"OPTSELLTYPE",
|
||||
"OPTTYPE",
|
||||
"ORG",
|
||||
"PARVALUE",
|
||||
"PAYACCT",
|
||||
"PAYANDCREDIT",
|
||||
"PAYEEID",
|
||||
"PAYEELSTID",
|
||||
"PAYINSTRUCT",
|
||||
"PERCENT",
|
||||
"PHONE",
|
||||
"PINCH",
|
||||
"PMTBYADDR",
|
||||
"PMTBYPAYEEID",
|
||||
"PMTBYXFER",
|
||||
"PMTPRCCODE",
|
||||
"POSDNLD",
|
||||
"POSTALCODE",
|
||||
"POSTPROCWND",
|
||||
"POSTYPE",
|
||||
"PROCDAYSOFF",
|
||||
"PROCENDTM",
|
||||
"PURANDADV",
|
||||
"RATING",
|
||||
"RECSRVRTID",
|
||||
"REFNUM",
|
||||
"REFRESH",
|
||||
"REFRESHSUPT",
|
||||
"REINVCG",
|
||||
"REINVDIV",
|
||||
"REJECTIFMISSING",
|
||||
"RELFITID",
|
||||
"RELTYPE",
|
||||
"RESPFILEER",
|
||||
"RESTRICTION",
|
||||
"SECLISTRQDNLD",
|
||||
"SECNAME",
|
||||
"SECURED",
|
||||
"SECURITYNAME",
|
||||
"SELLALL",
|
||||
"SELLREASON",
|
||||
"SELLTYPE",
|
||||
"SESSCOOKIE",
|
||||
"SEVERITY",
|
||||
"SHORTBALANCE",
|
||||
"SHPERCTRCT",
|
||||
"SIC",
|
||||
"SIGNONREALM",
|
||||
"SPACES",
|
||||
"SPECIAL",
|
||||
"SPNAME",
|
||||
"SRVRTID",
|
||||
"STATE",
|
||||
"STOCKTYPE",
|
||||
"STOPPRICE",
|
||||
"STPCHKFEE",
|
||||
"STRIKEPRICE",
|
||||
"STSVIAMODS",
|
||||
"SUBACCT",
|
||||
"SUBACCTFROM",
|
||||
"SUBACCTSEC",
|
||||
"SUBACCTTO",
|
||||
"SUBJECT",
|
||||
"SUPTXDL",
|
||||
"SVC",
|
||||
"SVCSTATUS",
|
||||
"SWITCHALL",
|
||||
"SYNCMODE",
|
||||
"TAN",
|
||||
"TAXES",
|
||||
"TAXEXEMPT",
|
||||
"TAXID",
|
||||
"TEMPPASS",
|
||||
"TFERACTION",
|
||||
"TICKER",
|
||||
"TO",
|
||||
"TOKEN",
|
||||
"TOKENONLY",
|
||||
"TOTAL",
|
||||
"TOTALFEES",
|
||||
"TOTALINT",
|
||||
"TRANDNLD",
|
||||
"TRANSPSEC",
|
||||
"TRNAMT",
|
||||
"TRNTYPE",
|
||||
"TRNUID",
|
||||
"TSKEYEXPIRE",
|
||||
"TSPHONE",
|
||||
"TYPEDESC",
|
||||
"UNIQUEID",
|
||||
"UNIQUEIDTYPE",
|
||||
"UNITPRICE",
|
||||
"UNITS",
|
||||
"UNITSSTREET",
|
||||
"UNITSUSER",
|
||||
"UNITTYPE",
|
||||
"URL",
|
||||
"USEHTML",
|
||||
"USERCRED1",
|
||||
"USERCRED1LABEL",
|
||||
"USERCRED2",
|
||||
"USERCRED2LABEL",
|
||||
"USERID",
|
||||
"USERKEY",
|
||||
"USERPASS",
|
||||
"USPRODUCTTYPE",
|
||||
"VALUE",
|
||||
"VER",
|
||||
"WITHHOLDING",
|
||||
"XFERDAYSWITH",
|
||||
"XFERDEST",
|
||||
"XFERDFLTDAYSTOPAY",
|
||||
"XFERPRCCODE",
|
||||
"XFERSRC",
|
||||
"YIELD",
|
||||
"YIELDTOCALL",
|
||||
"YIELDTOMAT",
|
||||
}
|
||||
206
vendor/github.com/aclindsa/ofxgo/profile.go
generated
vendored
206
vendor/github.com/aclindsa/ofxgo/profile.go
generated
vendored
@@ -1,206 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/aclindsa/xml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
XMLName xml.Name `xml:"PROFTRNRQ"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
ClientRouting String `xml:"PROFRQ>CLIENTROUTING"` // Forced to NONE
|
||||
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 {
|
||||
return "PROFTRNRQ"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||
// into XML/SGML
|
||||
func (r *ProfileRequest) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := r.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
// TODO implement
|
||||
r.ClientRouting = "NONE"
|
||||
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 {
|
||||
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 {
|
||||
XMLName xml.Name `xml:"SIGNONINFO"`
|
||||
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
|
||||
Max Int `xml:"MAX"` // Maximum number of password characters
|
||||
CharType charType `xml:"CHARTYPE"` // One of ALPHAONLY, NUMERICONLY, ALPHAORNUMERIC, ALPHAANDNUMERIC
|
||||
CaseSen Boolean `xml:"CASESEN"` // Password is case-sensitive?
|
||||
Special Boolean `xml:"SPECIAL"` // Special characters allowed?
|
||||
Spaces Boolean `xml:"SPACES"` // Spaces allowed?
|
||||
PinCh Boolean `xml:"PINCH"` // Pin change <PINCHRQ> requests allowed
|
||||
ChgPinFirst Boolean `xml:"CHGPINFIRST"` // Server requires user to change password at first signon
|
||||
UserCred1Label String `xml:"USERCRED1LABEL,omitempty"` // Prompt for USERCRED1 (if this field is present, USERCRED1 is required)
|
||||
UserCred2Label String `xml:"USERCRED2LABEL,omitempty"` // Prompt for USERCRED2 (if this field is present, USERCRED2 is required)
|
||||
ClientUIDReq Boolean `xml:"CLIENTUIDREQ,omitempty"` // CLIENTUID required?
|
||||
AuthTokenFirst Boolean `xml:"AUTHTOKENFIRST,omitempty"` // Server requires AUTHTOKEN as part of first signon
|
||||
AuthTokenLabel String `xml:"AUTHTOKENLABEL,omitempty"`
|
||||
AuthTokenInfoURL String `xml:"AUTHTOKENINFOURL,omitempty"`
|
||||
MFAChallengeSupt Boolean `xml:"MFACHALLENGESUPT,omitempty"` // Server supports MFACHALLENGE
|
||||
MFAChallengeFIRST Boolean `xml:"MFACHALLENGEFIRST,omitempty"` // Server requires MFACHALLENGE to be sent with first signon
|
||||
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 {
|
||||
XMLName xml.Name // <xxxMSGSETVn>
|
||||
Name string // <xxxMSGSETVn> (copy of XMLName.Local)
|
||||
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
|
||||
OfxSec ofxSec `xml:"MSGSETCORE>OFXSEC"` // NONE or 'TYPE 1'
|
||||
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
|
||||
Language []String `xml:"MSGSETCORE>LANGUAGE"` // List of supported languages
|
||||
SyncMode syncMode `xml:"MSGSETCORE>SYNCMODE"` // One of FULL, LITE
|
||||
RefreshSupt Boolean `xml:"MSGSETCORE>REFRESHSUPT,omitempty"` // Y if server supports <REFRESH>Y within synchronizations. This option is irrelevant for full synchronization servers. Clients must ignore <REFRESHSUPT> (or its absence) if the profile also specifies <SYNCMODE>FULL. For lite synchronization, the default is N. Without <REFRESHSUPT>Y, lite synchronization servers are not required to support <REFRESH>Y requests
|
||||
RespFileER Boolean `xml:"MSGSETCORE>RESPFILEER"` // server supports file-based error recovery
|
||||
SpName String `xml:"MSGSETCORE>SPNAME"` // Name of service provider
|
||||
// TODO MessageSet-specific stuff?
|
||||
}
|
||||
|
||||
// MessageSetList is a list of MessageSets (necessary because they must be
|
||||
// manually parsed)
|
||||
type MessageSetList []MessageSet
|
||||
|
||||
// UnmarshalXML handles unmarshalling a MessageSetList element from an XML string
|
||||
func (msl *MessageSetList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
for {
|
||||
var msgset MessageSet
|
||||
tok, err := nextNonWhitespaceToken(d)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local {
|
||||
// If we found the end of our starting element, we're done parsing
|
||||
return nil
|
||||
} else if _, ok := tok.(xml.StartElement); ok {
|
||||
// Found starting tag for <xxxMSGSET>. Get the next one (xxxMSGSETVn) and decode that struct
|
||||
tok, err := nextNonWhitespaceToken(d)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if versionStart, ok := tok.(xml.StartElement); ok {
|
||||
if err := d.DecodeElement(&msgset, &versionStart); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("Invalid MSGSETLIST formatting")
|
||||
}
|
||||
msgset.Name = msgset.XMLName.Local
|
||||
|
||||
// Eat ending tags for <xxxMSGSET>
|
||||
tok, err = nextNonWhitespaceToken(d)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if _, ok := tok.(xml.EndElement); !ok {
|
||||
return errors.New("Invalid MSGSETLIST formatting")
|
||||
}
|
||||
} else {
|
||||
return errors.New("MSGSETLIST didn't find an opening xxxMSGSETVn element")
|
||||
}
|
||||
*msl = MessageSetList(append(*(*[]MessageSet)(msl), msgset))
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalXML handles marshalling a MessageSetList element to an XML string
|
||||
func (msl *MessageSetList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
messageSetListElement := xml.StartElement{Name: xml.Name{Local: "MSGSETLIST"}}
|
||||
if err := e.EncodeToken(messageSetListElement); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, messageset := range *msl {
|
||||
if !strings.HasSuffix(messageset.Name, "V1") {
|
||||
return errors.New("Expected MessageSet.Name to end with \"V1\"")
|
||||
}
|
||||
messageSetName := strings.TrimSuffix(messageset.Name, "V1")
|
||||
messageSetElement := xml.StartElement{Name: xml.Name{Local: messageSetName}}
|
||||
if err := e.EncodeToken(messageSetElement); err != nil {
|
||||
return err
|
||||
}
|
||||
start := xml.StartElement{Name: xml.Name{Local: messageset.Name}}
|
||||
if err := e.EncodeElement(&messageset, start); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.EncodeToken(messageSetElement.End()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := e.EncodeToken(messageSetListElement.End()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
XMLName xml.Name `xml:"PROFTRNRS"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
Status Status `xml:"STATUS"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
MessageSetList MessageSetList `xml:"PROFRS>MSGSETLIST"`
|
||||
SignonInfoList []SignonInfo `xml:"PROFRS>SIGNONINFOLIST>SIGNONINFO"`
|
||||
DtProfUp Date `xml:"PROFRS>DTPROFUP"`
|
||||
FiName String `xml:"PROFRS>FINAME"`
|
||||
Addr1 String `xml:"PROFRS>ADDR1"`
|
||||
Addr2 String `xml:"PROFRS>ADDR2,omitempty"`
|
||||
Addr3 String `xml:"PROFRS>ADDR3,omitempty"`
|
||||
City String `xml:"PROFRS>CITY"`
|
||||
State String `xml:"PROFRS>STATE"`
|
||||
PostalCode String `xml:"PROFRS>POSTALCODE"`
|
||||
Country String `xml:"PROFRS>COUNTRY"`
|
||||
CsPhone String `xml:"PROFRS>CSPHONE,omitempty"`
|
||||
TsPhone String `xml:"PROFRS>TSPHONE,omitempty"`
|
||||
FaxPhone String `xml:"PROFRS>FAXPHONE,omitempty"`
|
||||
URL String `xml:"PROFRS>URL,omitempty"`
|
||||
Email String `xml:"PROFRS>EMAIL,omitempty"`
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (pr *ProfileResponse) Name() string {
|
||||
return "PROFTRNRS"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||
func (pr *ProfileResponse) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := pr.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
//TODO implement
|
||||
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 {
|
||||
return ProfRs
|
||||
}
|
||||
151
vendor/github.com/aclindsa/ofxgo/request.go
generated
vendored
151
vendor/github.com/aclindsa/ofxgo/request.go
generated
vendored
@@ -1,151 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/aclindsa/xml"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Request is the top-level object marshalled and sent to OFX servers. It is
|
||||
// constructed by appending one or more request objects to the message set they
|
||||
// correspond to (i.e. appending StatementRequest to Request.Bank to get a bank
|
||||
// statemement). If a *Request object is appended to the wrong message set, an
|
||||
// error will be returned when Marshal() is called on this Request.
|
||||
type Request struct {
|
||||
URL string
|
||||
Version ofxVersion // OFX version, overwritten in Client.Request()
|
||||
Signon SignonRequest //<SIGNONMSGSETV1>
|
||||
Signup []Message //<SIGNUPMSGSETV1>
|
||||
Bank []Message //<BANKMSGSETV1>
|
||||
CreditCard []Message //<CREDITCARDMSGSETV1>
|
||||
Loan []Message //<LOANMSGSETV1>
|
||||
InvStmt []Message //<INVSTMTMSGSETV1>
|
||||
InterXfer []Message //<INTERXFERMSGSETV1>
|
||||
WireXfer []Message //<WIREXFERMSGSETV1>
|
||||
Billpay []Message //<BILLPAYMSGSETV1>
|
||||
Email []Message //<EMAILMSGSETV1>
|
||||
SecList []Message //<SECLISTMSGSETV1>
|
||||
PresDir []Message //<PRESDIRMSGSETV1>
|
||||
PresDlv []Message //<PRESDLVMSGSETV1>
|
||||
Prof []Message //<PROFMSGSETV1>
|
||||
Image []Message //<IMAGEMSGSETV1>
|
||||
|
||||
indent bool // Whether to indent the marshaled XML
|
||||
carriageReturn bool // Whether to user carriage returns in new lines for marshaled XML
|
||||
}
|
||||
|
||||
func encodeMessageSet(e *xml.Encoder, requests []Message, set messageType, version ofxVersion) error {
|
||||
if len(requests) > 0 {
|
||||
messageSetElement := xml.StartElement{Name: xml.Name{Local: set.String()}}
|
||||
if err := e.EncodeToken(messageSetElement); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, request := range requests {
|
||||
if request.Type() != set {
|
||||
return errors.New("Expected " + set.String() + " message , found " + request.Type().String())
|
||||
}
|
||||
if ok, err := request.Valid(version); !ok {
|
||||
return err
|
||||
}
|
||||
if err := e.Encode(request); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.EncodeToken(messageSetElement.End()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetClientFields overwrites the fields in this Request object controlled by
|
||||
// the Client
|
||||
func (oq *Request) SetClientFields(c Client) {
|
||||
oq.Signon.DtClient.Time = time.Now()
|
||||
|
||||
// Overwrite fields that the client controls
|
||||
oq.Version = c.OfxVersion()
|
||||
oq.Signon.AppID = c.ID()
|
||||
oq.Signon.AppVer = c.Version()
|
||||
oq.indent = c.IndentRequests()
|
||||
oq.carriageReturn = c.CarriageReturnNewLines()
|
||||
}
|
||||
|
||||
// Marshal this Request into its SGML/XML representation held in a bytes.Buffer
|
||||
//
|
||||
// If error is non-nil, this bytes.Buffer is ready to be sent to an OFX server
|
||||
func (oq *Request) Marshal() (*bytes.Buffer, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
// Write the header appropriate to our version
|
||||
writeHeader(&b, oq.Version, oq.carriageReturn)
|
||||
|
||||
encoder := xml.NewEncoder(&b)
|
||||
if oq.indent {
|
||||
encoder.Indent("", " ")
|
||||
}
|
||||
if oq.carriageReturn {
|
||||
encoder.CarriageReturn(true)
|
||||
}
|
||||
if oq.Version < OfxVersion200 {
|
||||
// OFX 100 series versions should avoid element close tags for compatibility
|
||||
encoder.SetDisableAutoClose(ofxLeafElements...)
|
||||
}
|
||||
|
||||
ofxElement := xml.StartElement{Name: xml.Name{Local: "OFX"}}
|
||||
|
||||
if err := encoder.EncodeToken(ofxElement); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok, err := oq.Signon.Valid(oq.Version); !ok {
|
||||
return nil, err
|
||||
}
|
||||
signonMsgSet := xml.StartElement{Name: xml.Name{Local: SignonRq.String()}}
|
||||
if err := encoder.EncodeToken(signonMsgSet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := encoder.Encode(&oq.Signon); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := encoder.EncodeToken(signonMsgSet.End()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messageSets := []struct {
|
||||
Messages []Message
|
||||
Type messageType
|
||||
}{
|
||||
{oq.Signup, SignupRq},
|
||||
{oq.Bank, BankRq},
|
||||
{oq.CreditCard, CreditCardRq},
|
||||
{oq.Loan, LoanRq},
|
||||
{oq.InvStmt, InvStmtRq},
|
||||
{oq.InterXfer, InterXferRq},
|
||||
{oq.WireXfer, WireXferRq},
|
||||
{oq.Billpay, BillpayRq},
|
||||
{oq.Email, EmailRq},
|
||||
{oq.SecList, SecListRq},
|
||||
{oq.PresDir, PresDirRq},
|
||||
{oq.PresDlv, PresDlvRq},
|
||||
{oq.Prof, ProfRq},
|
||||
{oq.Image, ImageRq},
|
||||
}
|
||||
for _, set := range messageSets {
|
||||
if err := encodeMessageSet(encoder, set.Messages, set.Type, oq.Version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := encoder.EncodeToken(ofxElement.End()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := encoder.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &b, nil
|
||||
}
|
||||
505
vendor/github.com/aclindsa/ofxgo/response.go
generated
vendored
505
vendor/github.com/aclindsa/ofxgo/response.go
generated
vendored
@@ -1,505 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
|
||||
// Response is the top-level object returned from a parsed OFX response file.
|
||||
// It can be inspected by using type assertions or switches on the message set
|
||||
// you're interested in.
|
||||
type Response struct {
|
||||
Version ofxVersion // OFX header version
|
||||
Signon SignonResponse //<SIGNONMSGSETV1>
|
||||
Signup []Message //<SIGNUPMSGSETV1>
|
||||
Bank []Message //<BANKMSGSETV1>
|
||||
CreditCard []Message //<CREDITCARDMSGSETV1>
|
||||
Loan []Message //<LOANMSGSETV1>
|
||||
InvStmt []Message //<INVSTMTMSGSETV1>
|
||||
InterXfer []Message //<INTERXFERMSGSETV1>
|
||||
WireXfer []Message //<WIREXFERMSGSETV1>
|
||||
Billpay []Message //<BILLPAYMSGSETV1>
|
||||
Email []Message //<EMAILMSGSETV1>
|
||||
SecList []Message //<SECLISTMSGSETV1>
|
||||
PresDir []Message //<PRESDIRMSGSETV1>
|
||||
PresDlv []Message //<PRESDLVMSGSETV1>
|
||||
Prof []Message //<PROFMSGSETV1>
|
||||
Image []Message //<IMAGEMSGSETV1>
|
||||
}
|
||||
|
||||
func (or *Response) readSGMLHeaders(r *bufio.Reader) error {
|
||||
b, err := r.ReadSlice('<')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s := string(b)
|
||||
err = r.UnreadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
for i, name := range headerExp.SubexpNames() {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
headerValue := matches[i]
|
||||
switch name {
|
||||
case "OFXHEADER":
|
||||
if headerValue != "100" {
|
||||
return errors.New("OFXHEADER is not 100")
|
||||
}
|
||||
case "DATA":
|
||||
if headerValue != "OFXSGML" {
|
||||
return errors.New("OFX DATA header does not contain OFXSGML")
|
||||
}
|
||||
case "VERSION":
|
||||
err := or.Version.FromString(headerValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if or.Version > OfxVersion160 {
|
||||
return errors.New("OFX VERSION > 160 in SGML header")
|
||||
}
|
||||
case "SECURITY":
|
||||
if !(headerValue == "NONE" || headerValue == "TYPE1") {
|
||||
return errors.New("OFX SECURITY header must be NONE or TYPE1")
|
||||
}
|
||||
case "COMPRESSION":
|
||||
if headerValue != "NONE" {
|
||||
return errors.New("OFX COMPRESSION header not NONE")
|
||||
}
|
||||
case "ENCODING", "CHARSET", "OLDFILEUID", "NEWFILEUID":
|
||||
// TODO: check/handle these headers?
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (or *Response) readXMLHeaders(decoder *xml.Decoder) error {
|
||||
var tok xml.Token
|
||||
tok, err := nextNonWhitespaceToken(decoder)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if xmlElem, ok := tok.(xml.ProcInst); !ok || xmlElem.Target != "xml" {
|
||||
return errors.New("Missing xml processing instruction")
|
||||
}
|
||||
|
||||
// parse the OFX header
|
||||
tok, err = nextNonWhitespaceToken(decoder)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ofxElem, ok := tok.(xml.ProcInst); ok && ofxElem.Target == "OFX" {
|
||||
var seenHeader, seenVersion bool = false, false
|
||||
|
||||
headers := bytes.TrimSpace(ofxElem.Inst)
|
||||
for len(headers) > 0 {
|
||||
tmp := bytes.SplitN(headers, []byte("=\""), 2)
|
||||
if len(tmp) != 2 {
|
||||
return errors.New("Malformed OFX header")
|
||||
}
|
||||
header := string(tmp[0])
|
||||
headers = tmp[1]
|
||||
tmp = bytes.SplitN(headers, []byte("\""), 2)
|
||||
if len(tmp) != 2 {
|
||||
return errors.New("Malformed OFX header")
|
||||
}
|
||||
value := string(tmp[0])
|
||||
headers = bytes.TrimSpace(tmp[1])
|
||||
|
||||
switch header {
|
||||
case "OFXHEADER":
|
||||
if value != "200" {
|
||||
return errors.New("OFXHEADER is not 200")
|
||||
}
|
||||
seenHeader = true
|
||||
case "VERSION":
|
||||
err := or.Version.FromString(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seenVersion = true
|
||||
|
||||
if or.Version < OfxVersion200 {
|
||||
return errors.New("OFX VERSION < 200 in XML header")
|
||||
}
|
||||
case "SECURITY":
|
||||
if value != "NONE" {
|
||||
return errors.New("OFX SECURITY header not NONE")
|
||||
}
|
||||
case "OLDFILEUID", "NEWFILEUID":
|
||||
// TODO check/handle these headers?
|
||||
default:
|
||||
return errors.New("Invalid OFX header: " + header)
|
||||
}
|
||||
}
|
||||
|
||||
if !seenHeader {
|
||||
return errors.New("OFXHEADER version missing")
|
||||
}
|
||||
if !seenVersion {
|
||||
return errors.New("OFX VERSION header missing")
|
||||
}
|
||||
|
||||
} else {
|
||||
return errors.New("Missing xml 'OFX' processing instruction")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Number of bytes of response to read when attempting to figure out whether
|
||||
// we're using OFX or SGML
|
||||
const guessVersionCheckBytes = 1024
|
||||
|
||||
// Defaults to XML if it can't determine the version or if there is any
|
||||
// ambiguity
|
||||
// Returns false for SGML, true (for XML) otherwise.
|
||||
func guessVersion(r *bufio.Reader) (bool, error) {
|
||||
b, _ := r.Peek(guessVersionCheckBytes)
|
||||
if b == nil {
|
||||
return false, errors.New("Failed to read OFX header")
|
||||
}
|
||||
sgmlIndex := bytes.Index(b, []byte("OFXHEADER:"))
|
||||
xmlIndex := bytes.Index(b, []byte("OFXHEADER="))
|
||||
if sgmlIndex < 0 {
|
||||
return true, nil
|
||||
} else if xmlIndex < 0 {
|
||||
return false, nil
|
||||
} else {
|
||||
return xmlIndex <= sgmlIndex, nil
|
||||
}
|
||||
}
|
||||
|
||||
// A map of message set tags to a map of transaction wrapper tags to the
|
||||
// reflect.Type of the struct for that transaction type. Used when decoding
|
||||
// Responses. Newly-implemented response transaction types *must* be added to
|
||||
// this map in order to be unmarshalled.
|
||||
var responseTypes = map[string]map[string]reflect.Type{
|
||||
SignupRs.String(): {
|
||||
(&AcctInfoResponse{}).Name(): reflect.TypeOf(AcctInfoResponse{})},
|
||||
BankRs.String(): {
|
||||
(&StatementResponse{}).Name(): reflect.TypeOf(StatementResponse{})},
|
||||
CreditCardRs.String(): {
|
||||
(&CCStatementResponse{}).Name(): reflect.TypeOf(CCStatementResponse{})},
|
||||
LoanRs.String(): {},
|
||||
InvStmtRs.String(): {
|
||||
(&InvStatementResponse{}).Name(): reflect.TypeOf(InvStatementResponse{})},
|
||||
InterXferRs.String(): {},
|
||||
WireXferRs.String(): {},
|
||||
BillpayRs.String(): {},
|
||||
EmailRs.String(): {},
|
||||
SecListRs.String(): {
|
||||
(&SecListResponse{}).Name(): reflect.TypeOf(SecListResponse{}),
|
||||
(&SecurityList{}).Name(): reflect.TypeOf(SecurityList{})},
|
||||
PresDirRs.String(): {},
|
||||
PresDlvRs.String(): {},
|
||||
ProfRs.String(): {
|
||||
(&ProfileResponse{}).Name(): reflect.TypeOf(ProfileResponse{})},
|
||||
ImageRs.String(): {},
|
||||
}
|
||||
|
||||
func decodeMessageSet(d *xml.Decoder, start xml.StartElement, msgs *[]Message, version ofxVersion) error {
|
||||
setTypes, ok := responseTypes[start.Name.Local]
|
||||
if !ok {
|
||||
return errors.New("Invalid message set: " + start.Name.Local)
|
||||
}
|
||||
for {
|
||||
tok, err := nextNonWhitespaceToken(d)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local {
|
||||
// If we found the end of our starting element, we're done parsing
|
||||
return nil
|
||||
} else if startElement, ok := tok.(xml.StartElement); ok {
|
||||
responseType, ok := setTypes[startElement.Name.Local]
|
||||
if !ok {
|
||||
// If you are a developer and received this message after you
|
||||
// thought you added a new transaction type, make sure you
|
||||
// added it to the responseTypes map above
|
||||
return errors.New("Unsupported response transaction for " +
|
||||
start.Name.Local + ": " + startElement.Name.Local)
|
||||
}
|
||||
response := reflect.New(responseType).Interface()
|
||||
responseMessage := response.(Message)
|
||||
if err := d.DecodeElement(responseMessage, &startElement); err != nil {
|
||||
return err
|
||||
}
|
||||
*msgs = append(*msgs, responseMessage)
|
||||
} else {
|
||||
return errors.New("Didn't find an opening element")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ParseResponse parses and validates an OFX response in SGML or XML into a
|
||||
// Response object from the given io.Reader
|
||||
//
|
||||
// It is commonly used as part of Client.Request(), but may be used on its own
|
||||
// to parse already-downloaded OFX files (such as those from 'Web Connect'). It
|
||||
// performs version autodetection if it can and attempts to be as forgiving as
|
||||
// possible about the input format.
|
||||
func ParseResponse(reader io.Reader) (*Response, error) {
|
||||
resp, err := DecodeResponse(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = resp.Valid()
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DecodeResponse parses an OFX response in SGML or XML into a Response object
|
||||
// from the given io.Reader
|
||||
func DecodeResponse(reader io.Reader) (*Response, error) {
|
||||
var or Response
|
||||
|
||||
r := bufio.NewReaderSize(reader, guessVersionCheckBytes)
|
||||
xmlVersion, err := guessVersion(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parse SGML headers before creating XML decoder
|
||||
if !xmlVersion {
|
||||
if err := or.readSGMLHeaders(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
decoder := xml.NewDecoder(r)
|
||||
if !xmlVersion {
|
||||
decoder.Strict = false
|
||||
decoder.AutoCloseAfterCharData = ofxLeafElements
|
||||
}
|
||||
decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
if xmlVersion {
|
||||
// parse the xml header
|
||||
if err := or.readXMLHeaders(decoder); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
tok, err := nextNonWhitespaceToken(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if ofxStart, ok := tok.(xml.StartElement); !ok || ofxStart.Name.Local != "OFX" {
|
||||
return nil, errors.New("Missing opening OFX xml element")
|
||||
}
|
||||
|
||||
// Unmarshal the signon message
|
||||
tok, err = nextNonWhitespaceToken(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if signonStart, ok := tok.(xml.StartElement); ok && signonStart.Name.Local == SignonRs.String() {
|
||||
if err := decoder.Decode(&or.Signon); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("Missing opening SIGNONMSGSRSV1 xml element")
|
||||
}
|
||||
|
||||
tok, err = nextNonWhitespaceToken(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if signonEnd, ok := tok.(xml.EndElement); !ok || signonEnd.Name.Local != SignonRs.String() {
|
||||
return nil, errors.New("Missing closing SIGNONMSGSRSV1 xml element")
|
||||
}
|
||||
|
||||
var messageSlices = map[string]*[]Message{
|
||||
SignupRs.String(): &or.Signup,
|
||||
BankRs.String(): &or.Bank,
|
||||
CreditCardRs.String(): &or.CreditCard,
|
||||
LoanRs.String(): &or.Loan,
|
||||
InvStmtRs.String(): &or.InvStmt,
|
||||
InterXferRs.String(): &or.InterXfer,
|
||||
WireXferRs.String(): &or.WireXfer,
|
||||
BillpayRs.String(): &or.Billpay,
|
||||
EmailRs.String(): &or.Email,
|
||||
SecListRs.String(): &or.SecList,
|
||||
PresDirRs.String(): &or.PresDir,
|
||||
PresDlvRs.String(): &or.PresDlv,
|
||||
ProfRs.String(): &or.Prof,
|
||||
ImageRs.String(): &or.Image,
|
||||
}
|
||||
|
||||
for {
|
||||
tok, err = nextNonWhitespaceToken(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if ofxEnd, ok := tok.(xml.EndElement); ok && ofxEnd.Name.Local == "OFX" {
|
||||
return &or, nil // found closing XML element, so we're done
|
||||
} else if start, ok := tok.(xml.StartElement); ok {
|
||||
slice, ok := messageSlices[start.Name.Local]
|
||||
if !ok {
|
||||
return nil, errors.New("Invalid message set: " + start.Name.Local)
|
||||
}
|
||||
if err := decodeMessageSet(decoder, start, slice, or.Version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("Found unexpected token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Valid returns whether the Response is valid according to the OFX spec
|
||||
func (or *Response) Valid() (bool, error) {
|
||||
var errs errInvalid
|
||||
if ok, err := or.Signon.Valid(or.Version); !ok {
|
||||
errs.AddErr(err)
|
||||
}
|
||||
for _, messageSet := range [][]Message{
|
||||
or.Signup,
|
||||
or.Bank,
|
||||
or.CreditCard,
|
||||
or.Loan,
|
||||
or.InvStmt,
|
||||
or.InterXfer,
|
||||
or.WireXfer,
|
||||
or.Billpay,
|
||||
or.Email,
|
||||
or.SecList,
|
||||
or.PresDir,
|
||||
or.PresDlv,
|
||||
or.Prof,
|
||||
or.Image,
|
||||
} {
|
||||
for _, message := range messageSet {
|
||||
if ok, err := message.Valid(or.Version); !ok {
|
||||
errs.AddErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
err := errs.ErrOrNil()
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
// Marshal this Response into its SGML/XML representation held in a bytes.Buffer
|
||||
//
|
||||
// If error is non-nil, this bytes.Buffer is ready to be sent to an OFX client
|
||||
func (or *Response) Marshal() (*bytes.Buffer, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
// Write the header appropriate to our version
|
||||
writeHeader(&b, or.Version, false)
|
||||
|
||||
encoder := xml.NewEncoder(&b)
|
||||
encoder.Indent("", " ")
|
||||
|
||||
ofxElement := xml.StartElement{Name: xml.Name{Local: "OFX"}}
|
||||
|
||||
if err := encoder.EncodeToken(ofxElement); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok, err := or.Signon.Valid(or.Version); !ok {
|
||||
return nil, err
|
||||
}
|
||||
signonMsgSet := xml.StartElement{Name: xml.Name{Local: SignonRs.String()}}
|
||||
if err := encoder.EncodeToken(signonMsgSet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := encoder.Encode(&or.Signon); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := encoder.EncodeToken(signonMsgSet.End()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messageSets := []struct {
|
||||
Messages []Message
|
||||
Type messageType
|
||||
}{
|
||||
{or.Signup, SignupRs},
|
||||
{or.Bank, BankRs},
|
||||
{or.CreditCard, CreditCardRs},
|
||||
{or.Loan, LoanRs},
|
||||
{or.InvStmt, InvStmtRs},
|
||||
{or.InterXfer, InterXferRs},
|
||||
{or.WireXfer, WireXferRs},
|
||||
{or.Billpay, BillpayRs},
|
||||
{or.Email, EmailRs},
|
||||
{or.SecList, SecListRs},
|
||||
{or.PresDir, PresDirRs},
|
||||
{or.PresDlv, PresDlvRs},
|
||||
{or.Prof, ProfRs},
|
||||
{or.Image, ImageRs},
|
||||
}
|
||||
for _, set := range messageSets {
|
||||
if err := encodeMessageSet(encoder, set.Messages, set.Type, or.Version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := encoder.EncodeToken(ofxElement.End()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := encoder.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
// errInvalid represents validation failures while parsing an OFX response
|
||||
// If an institution returns slightly malformed data, ParseResponse will return a best-effort parsed response and a validation error.
|
||||
type errInvalid []error
|
||||
|
||||
func (e errInvalid) Error() string {
|
||||
var errStrings []string
|
||||
for _, err := range e {
|
||||
errStrings = append(errStrings, err.Error())
|
||||
}
|
||||
return fmt.Sprintf("Validation failed: %s", strings.Join(errStrings, "; "))
|
||||
}
|
||||
|
||||
func (e *errInvalid) AddErr(err error) {
|
||||
if err != nil {
|
||||
if errs, ok := err.(errInvalid); ok {
|
||||
*e = append(*e, errs...)
|
||||
} else {
|
||||
*e = append(*e, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e errInvalid) ErrOrNil() error {
|
||||
if len(e) > 0 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
332
vendor/github.com/aclindsa/ofxgo/seclist.go
generated
vendored
332
vendor/github.com/aclindsa/ofxgo/seclist.go
generated
vendored
@@ -1,332 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
|
||||
// SecurityID identifies a security by its CUSIP (for US-based FI's, others may
|
||||
// use UniqueID types other than CUSIP)
|
||||
type SecurityID struct {
|
||||
XMLName xml.Name `xml:"SECID"`
|
||||
UniqueID String `xml:"UNIQUEID"` // 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 {
|
||||
XMLName xml.Name `xml:"SECRQ"`
|
||||
// Only one of the next three should be present
|
||||
SecID *SecurityID `xml:"SECID,omitempty"`
|
||||
Ticker String `xml:"TICKER,omitempty"`
|
||||
FiID String `xml:"FIID,omitempty"`
|
||||
}
|
||||
|
||||
// SecListRequest represents a request for information (namely price) about one
|
||||
// or more securities
|
||||
type SecListRequest struct {
|
||||
XMLName xml.Name `xml:"SECLISTTRNRQ"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
Securities []SecurityRequest `xml:"SECLISTRQ>SECRQ,omitempty"`
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (r *SecListRequest) Name() string {
|
||||
return "SECLISTTRNRQ"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||
// into XML/SGML
|
||||
func (r *SecListRequest) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := r.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
// TODO implement
|
||||
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 {
|
||||
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 {
|
||||
XMLName xml.Name `xml:"SECLISTTRNRS"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
Status Status `xml:"STATUS"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
// 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 {
|
||||
return "SECLISTTRNRS"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||
func (r *SecListResponse) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := r.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
// TODO implement
|
||||
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 {
|
||||
return SecListRs
|
||||
}
|
||||
|
||||
// Security is satisfied by all *Info elements providing information about
|
||||
// securities for SecurityList
|
||||
type Security interface {
|
||||
SecurityType() string
|
||||
}
|
||||
|
||||
// SecInfo represents the generic information about a security. It is included
|
||||
// in most other *Info elements.
|
||||
type SecInfo struct {
|
||||
XMLName xml.Name `xml:"SECINFO"`
|
||||
SecID SecurityID `xml:"SECID"`
|
||||
SecName String `xml:"SECNAME"` // Full name of security
|
||||
Ticker String `xml:"TICKER,omitempty"` // Ticker symbol
|
||||
FiID String `xml:"FIID,omitempty"`
|
||||
Rating String `xml:"RATING,omitempty"`
|
||||
UnitPrice Amount `xml:"UNITPRICE,omitempty"` // Current price, as of DTASOF
|
||||
DtAsOf *Date `xml:"DTASOF,omitempty"` // Date UNITPRICE was for
|
||||
Currency *Currency `xml:"CURRENCY,omitempty"` // Overriding currency for UNITPRICE
|
||||
Memo String `xml:"MEMO,omitempty"`
|
||||
}
|
||||
|
||||
// DebtInfo provides information about a debt security
|
||||
type DebtInfo struct {
|
||||
XMLName xml.Name `xml:"DEBTINFO"`
|
||||
SecInfo SecInfo `xml:"SECINFO"`
|
||||
ParValue Amount `xml:"PARVALUE"`
|
||||
DebtType debtType `xml:"DEBTTYPE"` // One of COUPON, ZERO (zero coupon)
|
||||
DebtClass debtClass `xml:"DEBTCLASS,omitempty"` // One of TREASURY, MUNICIPAL, CORPORATE, OTHER
|
||||
CouponRate Amount `xml:"COUPONRT,omitempty"` // Bond coupon rate for next closest call date
|
||||
DtCoupon *Date `xml:"DTCOUPON,omitempty"` // Maturity date for next coupon
|
||||
CouponFreq couponFreq `xml:"COUPONFREQ,omitempty"` // When coupons mature - one of MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL, or OTHER
|
||||
CallPrice Amount `xml:"CALLPRICE,omitempty"` // Bond call price
|
||||
YieldToCall Amount `xml:"YIELDTOCALL,omitempty"` // Yield to next call
|
||||
DtCall *Date `xml:"DTCALL,omitempty"` // Next call date
|
||||
CallType callType `xml:"CALLTYPE,omitempt"` // Type of next call. One of CALL, PUT, PREFUND, MATURITY
|
||||
YieldToMat Amount `xml:"YIELDTOMAT,omitempty"` // Yield to maturity
|
||||
DtMat *Date `xml:"DTMAT,omitempty"` // Debt maturity date
|
||||
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
|
||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||
}
|
||||
|
||||
// SecurityType returns a string representation of this security's type
|
||||
func (i DebtInfo) SecurityType() string {
|
||||
return "DEBTINFO"
|
||||
}
|
||||
|
||||
// AssetPortion represents the percentage of a mutual fund with the given asset
|
||||
// classification
|
||||
type AssetPortion struct {
|
||||
XMLName xml.Name `xml:"PORTION"`
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
XMLName xml.Name `xml:"FIPORTION"`
|
||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined 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 {
|
||||
XMLName xml.Name `xml:"MFINFO"`
|
||||
SecInfo SecInfo `xml:"SECINFO"`
|
||||
MfType mfType `xml:"MFTYPE"` // One of OPEN, END, CLOSEEND, OTHER
|
||||
Yield Amount `xml:"YIELD,omitempty"` // Current yield reported as the dividend expressed as a portion of the current stock price
|
||||
DtYieldAsOf *Date `xml:"DTYIELDASOF,omitempty"` // Date YIELD is valid for
|
||||
AssetClasses []AssetPortion `xml:"MFASSETCLASS>PORTION"`
|
||||
FiAssetClasses []FiAssetPortion `xml:"FIMFASSETCLASS>FIPORTION"`
|
||||
}
|
||||
|
||||
// SecurityType returns a string representation of this security's type
|
||||
func (i MFInfo) SecurityType() string {
|
||||
return "MFINFO"
|
||||
}
|
||||
|
||||
// OptInfo provides information about an option
|
||||
type OptInfo struct {
|
||||
XMLName xml.Name `xml:"OPTINFO"`
|
||||
SecInfo SecInfo `xml:"SECINFO"`
|
||||
OptType optType `xml:"OPTTYPE"` // One of PUT, CALL
|
||||
StrikePrice Amount `xml:"STRIKEPRICE"`
|
||||
DtExpire Date `xml:"DTEXPIRE"` // Expiration date
|
||||
ShPerCtrct Int `xml:"SHPERCTRCT"` // Shares per contract
|
||||
SecID *SecurityID `xml:"SECID,omitempty"` // Security ID of the underlying security
|
||||
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
|
||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||
}
|
||||
|
||||
// SecurityType returns a string representation of this security's type
|
||||
func (i OptInfo) SecurityType() string {
|
||||
return "OPTINFO"
|
||||
}
|
||||
|
||||
// OtherInfo provides information about a security type not covered by the
|
||||
// other *Info elements
|
||||
type OtherInfo struct {
|
||||
XMLName xml.Name `xml:"OTHERINFO"`
|
||||
SecInfo SecInfo `xml:"SECINFO"`
|
||||
TypeDesc String `xml:"TYPEDESC,omitempty"` // Description of security type
|
||||
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
|
||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||
}
|
||||
|
||||
// SecurityType returns a string representation of this security's type
|
||||
func (i OtherInfo) SecurityType() string {
|
||||
return "OTHERINFO"
|
||||
}
|
||||
|
||||
// StockInfo provides information about a security type
|
||||
type StockInfo struct {
|
||||
XMLName xml.Name `xml:"STOCKINFO"`
|
||||
SecInfo SecInfo `xml:"SECINFO"`
|
||||
StockType stockType `xml:"STOCKTYPE,omitempty"` // One of COMMON, PREFERRED, CONVERTIBLE, OTHER
|
||||
Yield Amount `xml:"YIELD,omitempty"` // Current yield reported as the dividend expressed as a portion of the current stock price
|
||||
DtYieldAsOf *Date `xml:"DTYIELDASOF,omitempty"` // Date YIELD is valid for
|
||||
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
|
||||
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
|
||||
}
|
||||
|
||||
// SecurityType returns a string representation of this security's type
|
||||
func (i StockInfo) SecurityType() string {
|
||||
return "STOCKINFO"
|
||||
}
|
||||
|
||||
// SecurityList is a container for Security objects containaing information
|
||||
// about securities
|
||||
type SecurityList struct {
|
||||
XMLName xml.Name `xml:"SECLIST"`
|
||||
Securities []Security
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (r *SecurityList) Name() string {
|
||||
return "SECLIST"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||
func (r *SecurityList) Valid(version ofxVersion) (bool, error) {
|
||||
// TODO implement
|
||||
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 {
|
||||
return SecListRs
|
||||
}
|
||||
|
||||
// UnmarshalXML handles unmarshalling a SecurityList from an SGML/XML string
|
||||
func (r *SecurityList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
for {
|
||||
tok, err := nextNonWhitespaceToken(d)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local {
|
||||
// If we found the end of our starting element, we're done parsing
|
||||
return nil
|
||||
} else if startElement, ok := tok.(xml.StartElement); ok {
|
||||
switch startElement.Name.Local {
|
||||
case "DEBTINFO":
|
||||
var info DebtInfo
|
||||
if err := d.DecodeElement(&info, &startElement); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Securities = append(r.Securities, Security(info))
|
||||
case "MFINFO":
|
||||
var info MFInfo
|
||||
if err := d.DecodeElement(&info, &startElement); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Securities = append(r.Securities, Security(info))
|
||||
case "OPTINFO":
|
||||
var info OptInfo
|
||||
if err := d.DecodeElement(&info, &startElement); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Securities = append(r.Securities, Security(info))
|
||||
case "OTHERINFO":
|
||||
var info OtherInfo
|
||||
if err := d.DecodeElement(&info, &startElement); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Securities = append(r.Securities, Security(info))
|
||||
case "STOCKINFO":
|
||||
var info StockInfo
|
||||
if err := d.DecodeElement(&info, &startElement); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Securities = append(r.Securities, Security(info))
|
||||
default:
|
||||
return errors.New("Invalid SECLIST child tag: " + startElement.Name.Local)
|
||||
}
|
||||
} else {
|
||||
return errors.New("Didn't find an opening element")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalXML handles marshalling a SecurityList to an SGML/XML string
|
||||
func (r *SecurityList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
secListElement := xml.StartElement{Name: xml.Name{Local: "SECLIST"}}
|
||||
if err := e.EncodeToken(secListElement); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range r.Securities {
|
||||
start := xml.StartElement{Name: xml.Name{Local: s.SecurityType()}}
|
||||
switch sec := s.(type) {
|
||||
case DebtInfo:
|
||||
if err := e.EncodeElement(&sec, start); err != nil {
|
||||
return err
|
||||
}
|
||||
case MFInfo:
|
||||
if err := e.EncodeElement(&sec, start); err != nil {
|
||||
return err
|
||||
}
|
||||
case OptInfo:
|
||||
if err := e.EncodeElement(&sec, start); err != nil {
|
||||
return err
|
||||
}
|
||||
case OtherInfo:
|
||||
if err := e.EncodeElement(&sec, start); err != nil {
|
||||
return err
|
||||
}
|
||||
case StockInfo:
|
||||
if err := e.EncodeElement(&sec, start); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("Invalid SECLIST child type: " + sec.SecurityType())
|
||||
}
|
||||
}
|
||||
if err := e.EncodeToken(secListElement.End()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
88
vendor/github.com/aclindsa/ofxgo/signon.go
generated
vendored
88
vendor/github.com/aclindsa/ofxgo/signon.go
generated
vendored
@@ -1,88 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
|
||||
// SignonRequest identifies and authenticates a user to their FI and is
|
||||
// provided with every Request
|
||||
type SignonRequest struct {
|
||||
XMLName xml.Name `xml:"SONRQ"`
|
||||
DtClient Date `xml:"DTCLIENT"` // Current time on client, overwritten in Client.Request()
|
||||
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"`
|
||||
AppID String `xml:"APPID"` // Overwritten in Client.Request()
|
||||
AppVer String `xml:"APPVER"` // Overwritten in Client.Request()
|
||||
ClientUID UID `xml:"CLIENTUID,omitempty"`
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (r *SignonRequest) Name() string {
|
||||
return "SONRQ"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||
// into XML/SGML
|
||||
func (r *SignonRequest) Valid(version ofxVersion) (bool, error) {
|
||||
if len(r.UserID) < 1 || len(r.UserID) > 32 {
|
||||
return false, errors.New("SONRQ>USERID invalid length")
|
||||
}
|
||||
if (len(r.UserPass) == 0) == (len(r.UserKey) == 0) {
|
||||
return false, errors.New("One and only one of SONRQ>USERPASS and USERKEY must be supplied")
|
||||
}
|
||||
if len(r.UserPass) > 32 {
|
||||
return false, errors.New("SONRQ>USERPASS invalid length")
|
||||
}
|
||||
if len(r.UserKey) > 64 {
|
||||
return false, errors.New("SONRQ>USERKEY invalid length")
|
||||
}
|
||||
if len(r.Language) == 0 {
|
||||
r.Language = "ENG"
|
||||
} else if len(r.Language) != 3 {
|
||||
return false, fmt.Errorf("SONRQ>LANGUAGE invalid length: \"%s\"", r.Language)
|
||||
}
|
||||
if len(r.AppID) < 1 || len(r.AppID) > 5 {
|
||||
return false, errors.New("SONRQ>APPID invalid length")
|
||||
}
|
||||
if len(r.AppVer) < 1 || len(r.AppVer) > 4 {
|
||||
return false, errors.New("SONRQ>APPVER invalid length")
|
||||
}
|
||||
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 {
|
||||
XMLName xml.Name `xml:"SONRS"`
|
||||
Status Status `xml:"STATUS"`
|
||||
DtServer Date `xml:"DTSERVER"`
|
||||
UserKey String `xml:"USERKEY,omitempty"`
|
||||
TsKeyExpire *Date `xml:"TSKEYEXPIRE,omitempty"`
|
||||
Language String `xml:"LANGUAGE"`
|
||||
DtProfUp *Date `xml:"DTPROFUP,omitempty"`
|
||||
DtAcctUp *Date `xml:"DTACCTUP,omitempty"`
|
||||
Org String `xml:"FI>ORG"`
|
||||
Fid String `xml:"FI>FID"`
|
||||
SessCookie String `xml:"SESSCOOKIE,omitempty"`
|
||||
AccessKey String `xml:"ACCESSKEY,omitempty"`
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (r *SignonResponse) Name() string {
|
||||
return "SONRS"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||
func (r *SignonResponse) Valid(version ofxVersion) (bool, error) {
|
||||
if len(r.Language) != 3 {
|
||||
return false, fmt.Errorf("SONRS>LANGUAGE invalid length: \"%s\"", r.Language)
|
||||
}
|
||||
return r.Status.Valid()
|
||||
}
|
||||
166
vendor/github.com/aclindsa/ofxgo/signup.go
generated
vendored
166
vendor/github.com/aclindsa/ofxgo/signup.go
generated
vendored
@@ -1,166 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/aclindsa/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 {
|
||||
XMLName xml.Name `xml:"ACCTINFOTRNRQ"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
DtAcctUp Date `xml:"ACCTINFORQ>DTACCTUP"`
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (r *AcctInfoRequest) Name() string {
|
||||
return "ACCTINFOTRNRQ"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
|
||||
// into XML/SGML
|
||||
func (r *AcctInfoRequest) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := r.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
// TODO implement
|
||||
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 {
|
||||
return SignupRq
|
||||
}
|
||||
|
||||
// HolderInfo contains the information a FI has about an account-holder
|
||||
type HolderInfo struct {
|
||||
XMLName xml.Name
|
||||
FirstName String `xml:"FIRSTNAME"`
|
||||
MiddleName String `xml:"MIDDLENAME,omitempty"`
|
||||
LastName String `xml:"LASTNAME"`
|
||||
Addr1 String `xml:"ADDR1"`
|
||||
Addr2 String `xml:"ADDR2,omitempty"`
|
||||
Addr3 String `xml:"ADDR3,omitempty"`
|
||||
City String `xml:"CITY"`
|
||||
State String `xml:"STATE"`
|
||||
PostalCode String `xml:"POSTALCODE"`
|
||||
Country String `xml:"COUNTRY,omitempty"`
|
||||
DayPhone String `xml:"DAYPHONE,omitempty"`
|
||||
EvePhone String `xml:"EVEPHONE,omitempty"`
|
||||
Email String `xml:"EMAIL,omitempty"`
|
||||
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 {
|
||||
XMLName xml.Name `xml:"BANKACCTINFO"`
|
||||
BankAcctFrom BankAcct `xml:"BANKACCTFROM"`
|
||||
SupTxDl Boolean `xml:"SUPTXDL"` // Supports downloading transactions (as opposed to balance only)
|
||||
XferSrc Boolean `xml:"XFERSRC"` // Enabled as source for intra/interbank transfer
|
||||
XferDest Boolean `xml:"XFERDEST"` // Enabled as destination for intra/interbank transfer
|
||||
MaturityDate Date `xml:"MATURITYDATE,omitempty"` // Maturity date for CD, if CD
|
||||
MaturityAmt Amount `xml:"MATURITYAMOUNT,omitempty"` // Maturity amount for CD, if CD
|
||||
MinBalReq Amount `xml:"MINBALREQ,omitempty"` // Minimum balance required to avoid service fees
|
||||
AcctClassification acctClassification `xml:"ACCTCLASSIFICATION,omitempty"` // One of PERSONAL, BUSINESS, CORPORATE, OTHER
|
||||
OverdraftLimit Amount `xml:"OVERDRAFTLIMIT,omitempty"`
|
||||
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
|
||||
}
|
||||
|
||||
// String makes pointers to BankAcctInfo structs print nicely
|
||||
func (bai *BankAcctInfo) String() string {
|
||||
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 {
|
||||
XMLName xml.Name `xml:"CCACCTINFO"`
|
||||
CCAcctFrom CCAcct `xml:"CCACCTFROM"`
|
||||
SupTxDl Boolean `xml:"SUPTXDL"` // Supports downloading transactions (as opposed to balance only)
|
||||
XferSrc Boolean `xml:"XFERSRC"` // Enabled as source for intra/interbank transfer
|
||||
XferDest Boolean `xml:"XFERDEST"` // Enabled as destination for intra/interbank transfer
|
||||
AcctClassification acctClassification `xml:"ACCTCLASSIFICATION,omitempty"` // One of PERSONAL, BUSINESS, CORPORATE, OTHER
|
||||
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
|
||||
}
|
||||
|
||||
// String makes pointers to CCAcctInfo structs print nicely
|
||||
func (ci *CCAcctInfo) String() string {
|
||||
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 {
|
||||
XMLName xml.Name `xml:"INVACCTINFO"`
|
||||
InvAcctFrom InvAcct `xml:"INVACCTFROM"`
|
||||
UsProductType usProductType `xml:"USPRODUCTTYPE"` // One of 401K, 403B, IRA, KEOGH, OTHER, SARSEP, SIMPLE, NORMAL, TDA, TRUST, UGMA
|
||||
Checking Boolean `xml:"CHECKING"` // Has check-writing privileges
|
||||
SvcStatus svcStatus `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
|
||||
InvAcctType holderType `xml:"INVACCTTYPE,omitempty"` // One of INDIVIDUAL, JOINT, TRUST, CORPORATE
|
||||
OptionLevel String `xml:"OPTIONLEVEL,omitempty"` // Text desribing option trading privileges
|
||||
}
|
||||
|
||||
// String makes pointers to InvAcctInfo structs print nicely
|
||||
func (iai *InvAcctInfo) String() string {
|
||||
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 {
|
||||
XMLName xml.Name `xml:"ACCTINFO"`
|
||||
Name String `xml:"NAME,omitempty"`
|
||||
Desc String `xml:"DESC,omitempty"`
|
||||
Phone String `xml:"PHONE,omitempty"`
|
||||
PrimaryHolder HolderInfo `xml:"HOLDERINFO>PRIMARYHOLDER,omitempty"`
|
||||
SecondaryHolder HolderInfo `xml:"HOLDERINFO>SECONDARYHOLDER,omitempty"`
|
||||
|
||||
// Only one of the rest of the fields will be valid for any given AcctInfo
|
||||
BankAcctInfo *BankAcctInfo `xml:"BANKACCTINFO,omitempty"`
|
||||
CCAcctInfo *CCAcctInfo `xml:"CCACCTINFO,omitempty"`
|
||||
InvAcctInfo *InvAcctInfo `xml:"INVACCTINFO,omitempty"`
|
||||
// TODO LOANACCTINFO
|
||||
// TODO BPACCTINFO?
|
||||
}
|
||||
|
||||
// AcctInfoResponse contains the information about all a user's accounts
|
||||
// accessible from this FI
|
||||
type AcctInfoResponse struct {
|
||||
XMLName xml.Name `xml:"ACCTINFOTRNRS"`
|
||||
TrnUID UID `xml:"TRNUID"`
|
||||
Status Status `xml:"STATUS"`
|
||||
CltCookie String `xml:"CLTCOOKIE,omitempty"`
|
||||
// TODO `xml:"OFXEXTENSION,omitempty"`
|
||||
DtAcctUp Date `xml:"ACCTINFORS>DTACCTUP"`
|
||||
AcctInfo []AcctInfo `xml:"ACCTINFORS>ACCTINFO,omitempty"`
|
||||
}
|
||||
|
||||
// Name returns the name of the top-level transaction XML/SGML element
|
||||
func (air *AcctInfoResponse) Name() string {
|
||||
return "ACCTINFOTRNRS"
|
||||
}
|
||||
|
||||
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
|
||||
func (air *AcctInfoResponse) Valid(version ofxVersion) (bool, error) {
|
||||
if ok, err := air.TrnUID.Valid(); !ok {
|
||||
return false, err
|
||||
}
|
||||
//TODO implement
|
||||
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 {
|
||||
return SignupRs
|
||||
}
|
||||
386
vendor/github.com/aclindsa/ofxgo/types.go
generated
vendored
386
vendor/github.com/aclindsa/ofxgo/types.go
generated
vendored
@@ -1,386 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/aclindsa/xml"
|
||||
"golang.org/x/text/currency"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Int provides helper methods to unmarshal int64 values from SGML/XML
|
||||
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 {
|
||||
var value string
|
||||
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
i2, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*i = Int(i2)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal returns true if the two Ints are equal in value
|
||||
func (i Int) Equal(o Int) bool {
|
||||
return i == o
|
||||
}
|
||||
|
||||
// Amount represents non-integer values (or at least values for fields that may
|
||||
// not necessarily be integers)
|
||||
type Amount struct {
|
||||
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 {
|
||||
var value string
|
||||
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
// The OFX spec allows the start of the fractional amount to be delineated
|
||||
// by a comma, so fix that up before attempting to parse it into big.Rat
|
||||
value = strings.Replace(value, ",", ".", 1)
|
||||
|
||||
if _, ok := a.SetString(value); !ok {
|
||||
return errors.New("Failed to parse OFX amount")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String prints a string representation of an Amount
|
||||
func (a Amount) String() string {
|
||||
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 {
|
||||
return e.EncodeElement(a.String(), start)
|
||||
}
|
||||
|
||||
// Equal returns true if two Amounts are equal in value
|
||||
func (a Amount) Equal(o Amount) bool {
|
||||
return (&a).Cmp(&o.Rat) == 0
|
||||
}
|
||||
|
||||
// Date represents OFX date/time values
|
||||
type Date struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
var ofxDateFormats = []string{
|
||||
"20060102150405.000",
|
||||
"20060102150405",
|
||||
"200601021504",
|
||||
"2006010215",
|
||||
"20060102",
|
||||
}
|
||||
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 {
|
||||
var value, zone, zoneFormat string
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = strings.SplitN(value, "]", 2)[0]
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
// Split the time zone off, if any
|
||||
split := strings.SplitN(value, "[", 2)
|
||||
if len(split) == 2 {
|
||||
value = split[0]
|
||||
zoneFormat = " -0700"
|
||||
zone = strings.TrimRight(split[1], "]")
|
||||
|
||||
matches := ofxDateZoneRegex.FindStringSubmatch(zone)
|
||||
if matches == nil {
|
||||
return errors.New("Invalid OFX Date timezone format: " + zone)
|
||||
}
|
||||
var err error
|
||||
var zonehours, zoneminutes int
|
||||
zonehours, err = strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(matches[3]) > 0 {
|
||||
zoneminutes, err = strconv.Atoi(matches[3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zoneminutes = zoneminutes * 60 / 100
|
||||
}
|
||||
zone = fmt.Sprintf(" %+03d%02d", zonehours, zoneminutes)
|
||||
|
||||
// Get the time zone name if it's there, default to GMT if the offset
|
||||
// is 0 and a name isn't supplied
|
||||
if len(matches[5]) > 0 {
|
||||
zone = zone + " " + matches[5]
|
||||
zoneFormat = zoneFormat + " MST"
|
||||
} else if zonehours == 0 && zoneminutes == 0 {
|
||||
zone = zone + " GMT"
|
||||
zoneFormat = zoneFormat + " MST"
|
||||
}
|
||||
} else {
|
||||
// Default to GMT if no time zone was specified
|
||||
zone = " +0000 GMT"
|
||||
zoneFormat = " -0700 MST"
|
||||
}
|
||||
|
||||
// Try all the date formats, from longest to shortest
|
||||
for _, format := range ofxDateFormats {
|
||||
t, err := time.Parse(format+zoneFormat, value+zone)
|
||||
if err == nil {
|
||||
od.Time = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
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 {
|
||||
format := od.Format(ofxDateFormats[0])
|
||||
zonename, zoneoffset := od.Zone()
|
||||
if zoneoffset < 0 {
|
||||
format += "[" + fmt.Sprintf("%+d", zoneoffset/3600)
|
||||
} else {
|
||||
format += "[" + fmt.Sprintf("%d", zoneoffset/3600)
|
||||
}
|
||||
fractionaloffset := (zoneoffset % 3600) / 36
|
||||
if fractionaloffset > 0 {
|
||||
format += "." + fmt.Sprintf("%02d", fractionaloffset)
|
||||
} else if fractionaloffset < 0 {
|
||||
format += "." + fmt.Sprintf("%02d", -fractionaloffset)
|
||||
}
|
||||
|
||||
if len(zonename) > 0 {
|
||||
return format + ":" + zonename + "]"
|
||||
}
|
||||
return format + "]"
|
||||
}
|
||||
|
||||
// MarshalXML marshals a Date to XML
|
||||
func (od *Date) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
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 {
|
||||
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 {
|
||||
return &Date{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)}
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
|
||||
// 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 {
|
||||
var value string
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*os = String(strings.TrimSpace(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string
|
||||
func (os *String) String() string {
|
||||
return string(*os)
|
||||
}
|
||||
|
||||
// Equal returns true if the two Strings are equal in value
|
||||
func (os String) Equal(o String) bool {
|
||||
return os == o
|
||||
}
|
||||
|
||||
// Boolean provides helper methods to unmarshal bool values from OFX SGML/XML
|
||||
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 {
|
||||
var value string
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpob := strings.TrimSpace(value)
|
||||
switch tmpob {
|
||||
case "Y":
|
||||
*ob = Boolean(true)
|
||||
case "N":
|
||||
*ob = Boolean(false)
|
||||
default:
|
||||
return errors.New("Invalid OFX Boolean")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML marshals a Boolean to XML
|
||||
func (ob *Boolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
if *ob {
|
||||
return e.EncodeElement("Y", start)
|
||||
}
|
||||
return e.EncodeElement("N", start)
|
||||
}
|
||||
|
||||
// String returns a string representation of a Boolean value
|
||||
func (ob *Boolean) String() string {
|
||||
return fmt.Sprintf("%v", *ob)
|
||||
}
|
||||
|
||||
// Equal returns true if the two Booleans are the same
|
||||
func (ob Boolean) Equal(o Boolean) bool {
|
||||
return ob == o
|
||||
}
|
||||
|
||||
// UID represents an UID according to the OFX spec
|
||||
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 {
|
||||
var value string
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*ou = UID(strings.TrimSpace(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecommendedFormat returns true iff this UID meets the OFX specification's
|
||||
// recommendation that UIDs follow the standard UUID 36-character format
|
||||
func (ou UID) RecommendedFormat() (bool, error) {
|
||||
if len(ou) != 36 {
|
||||
return false, errors.New("UID not 36 characters long")
|
||||
}
|
||||
if ou[8] != '-' || ou[13] != '-' || ou[18] != '-' || ou[23] != '-' {
|
||||
return false, errors.New("UID missing hyphens at the appropriate places")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Valid returns true, nil if the UID is valid. This is less strict than
|
||||
// RecommendedFormat, and will always return true, nil if it does.
|
||||
func (ou UID) Valid() (bool, error) {
|
||||
if len(ou) == 0 || len(ou) > 36 {
|
||||
return false, errors.New("UID invalid length")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Equal returns true if the two UIDs are the same
|
||||
func (ou UID) Equal(o UID) bool {
|
||||
return ou == o
|
||||
}
|
||||
|
||||
// RandomUID creates a new randomly-generated UID
|
||||
func RandomUID() (*UID, error) {
|
||||
uidbytes := make([]byte, 16)
|
||||
n, err := rand.Read(uidbytes[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != 16 {
|
||||
return nil, errors.New("RandomUID failed to read 16 random bytes")
|
||||
}
|
||||
uid := UID(fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", uidbytes[:4], uidbytes[4:6], uidbytes[6:8], uidbytes[8:10], uidbytes[10:]))
|
||||
return &uid, nil
|
||||
}
|
||||
|
||||
// CurrSymbol represents an ISO-4217 currency
|
||||
type CurrSymbol struct {
|
||||
currency.Unit
|
||||
}
|
||||
|
||||
// UnmarshalXML handles unmarshalling a CurrSymbol from an SGML/XML string.
|
||||
// Leading and trailing whitespace is ignored.
|
||||
func (c *CurrSymbol) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var value string
|
||||
|
||||
err := d.DecodeElement(&value, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
unit, err := currency.ParseISO(value)
|
||||
if err != nil {
|
||||
return errors.New("Error parsing CurrSymbol:" + err.Error())
|
||||
}
|
||||
c.Unit = unit
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML marshals a CurrSymbol to SGML/XML
|
||||
func (c *CurrSymbol) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return e.EncodeElement(c.String(), start)
|
||||
}
|
||||
|
||||
// Equal returns true if the two Currencies are the same
|
||||
func (c CurrSymbol) Equal(o CurrSymbol) bool {
|
||||
return c.String() == o.String()
|
||||
}
|
||||
|
||||
// Valid returns true, nil if the CurrSymbol is valid.
|
||||
func (c CurrSymbol) Valid() (bool, error) {
|
||||
if c.String() == "XXX" {
|
||||
return false, fmt.Errorf("Invalid CurrSymbol: %s", c.Unit)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// NewCurrSymbol returns a new CurrSymbol given a three-letter ISO-4217
|
||||
// currency symbol as a string
|
||||
func NewCurrSymbol(s string) (*CurrSymbol, error) {
|
||||
unit, err := currency.ParseISO(s)
|
||||
if err != nil {
|
||||
return nil, errors.New("Error parsing string to create new CurrSymbol:" + err.Error())
|
||||
}
|
||||
return &CurrSymbol{unit}, nil
|
||||
}
|
||||
25
vendor/github.com/aclindsa/ofxgo/util.go
generated
vendored
25
vendor/github.com/aclindsa/ofxgo/util.go
generated
vendored
@@ -1,25 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/aclindsa/xml"
|
||||
)
|
||||
|
||||
// Returns the next available Token from the xml.Decoder that is not CharData
|
||||
// made up entirely of whitespace. This is useful to skip whitespace when
|
||||
// manually unmarshaling XML.
|
||||
func nextNonWhitespaceToken(decoder *xml.Decoder) (xml.Token, error) {
|
||||
for {
|
||||
tok, err := decoder.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if chars, ok := tok.(xml.CharData); ok {
|
||||
strippedBytes := bytes.TrimSpace(chars)
|
||||
if len(strippedBytes) != 0 {
|
||||
return tok, nil
|
||||
}
|
||||
} else {
|
||||
return tok, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
83
vendor/github.com/aclindsa/ofxgo/vanguard_client.go
generated
vendored
83
vendor/github.com/aclindsa/ofxgo/vanguard_client.go
generated
vendored
@@ -1,83 +0,0 @@
|
||||
package ofxgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VanguardClient provides a Client implementation which handles Vanguard's
|
||||
// cookie-passing requirements. VanguardClient uses default, non-zero settings,
|
||||
// if its fields are not initialized.
|
||||
type VanguardClient struct {
|
||||
*BasicClient
|
||||
}
|
||||
|
||||
// NewVanguardClient returns a Client interface configured to handle Vanguard's
|
||||
// brand of idiosyncrasy
|
||||
func NewVanguardClient(bc *BasicClient) Client {
|
||||
return &VanguardClient{bc}
|
||||
}
|
||||
|
||||
// rawRequestCookies is RawRequest with the added feature of sending cookies
|
||||
func rawRequestCookies(URL string, r io.Reader, cookies []*http.Cookie) (*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")
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("POST", URL, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/x-ofx")
|
||||
for _, cookie := range cookies {
|
||||
request.AddCookie(cookie)
|
||||
}
|
||||
|
||||
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, 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)
|
||||
|
||||
b, err := r.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := c.RawRequest(r.URL, b)
|
||||
|
||||
// Some financial institutions (cough, Vanguard, cough), require a cookie
|
||||
// to be set on the http request, or they return empty responses.
|
||||
// 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 response != nil && response.ContentLength <= 0 && len(response.Cookies()) > 0 {
|
||||
b, err = r.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawRequestCookies(r.URL, b, response.Cookies())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
1107
vendor/github.com/aclindsa/xml/AUTHORS
generated
vendored
1107
vendor/github.com/aclindsa/xml/AUTHORS
generated
vendored
File diff suppressed because it is too large
Load Diff
1445
vendor/github.com/aclindsa/xml/CONTRIBUTORS
generated
vendored
1445
vendor/github.com/aclindsa/xml/CONTRIBUTORS
generated
vendored
File diff suppressed because it is too large
Load Diff
27
vendor/github.com/aclindsa/xml/LICENSE
generated
vendored
27
vendor/github.com/aclindsa/xml/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
22
vendor/github.com/aclindsa/xml/PATENTS
generated
vendored
22
vendor/github.com/aclindsa/xml/PATENTS
generated
vendored
@@ -1,22 +0,0 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
1075
vendor/github.com/aclindsa/xml/marshal.go
generated
vendored
1075
vendor/github.com/aclindsa/xml/marshal.go
generated
vendored
File diff suppressed because it is too large
Load Diff
749
vendor/github.com/aclindsa/xml/read.go
generated
vendored
749
vendor/github.com/aclindsa/xml/read.go
generated
vendored
@@ -1,749 +0,0 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
|
||||
// an XML element is an order-dependent collection of anonymous
|
||||
// values, while a data structure is an order-independent collection
|
||||
// of named values.
|
||||
// See package json for a textual representation more suitable
|
||||
// to data structures.
|
||||
|
||||
// Unmarshal parses the XML-encoded data and stores the result in
|
||||
// the value pointed to by v, which must be an arbitrary struct,
|
||||
// slice, or string. Well-formed data that does not fit into v is
|
||||
// discarded.
|
||||
//
|
||||
// Because Unmarshal uses the reflect package, it can only assign
|
||||
// to exported (upper case) fields. Unmarshal uses a case-sensitive
|
||||
// comparison to match XML element names to tag values and struct
|
||||
// field names.
|
||||
//
|
||||
// Unmarshal maps an XML element to a struct using the following rules.
|
||||
// In the rules, the tag of a field refers to the value associated with the
|
||||
// key 'xml' in the struct field's tag (see the example above).
|
||||
//
|
||||
// * If the struct has a field of type []byte or string with tag
|
||||
// ",innerxml", Unmarshal accumulates the raw XML nested inside the
|
||||
// element in that field. The rest of the rules still apply.
|
||||
//
|
||||
// * If the struct has a field named XMLName of type Name,
|
||||
// Unmarshal records the element name in that field.
|
||||
//
|
||||
// * If the XMLName field has an associated tag of the form
|
||||
// "name" or "namespace-URL name", the XML element must have
|
||||
// the given name (and, optionally, name space) or else Unmarshal
|
||||
// returns an error.
|
||||
//
|
||||
// * If the XML element has an attribute whose name matches a
|
||||
// struct field name with an associated tag containing ",attr" or
|
||||
// the explicit name in a struct field tag of the form "name,attr",
|
||||
// Unmarshal records the attribute value in that field.
|
||||
//
|
||||
// * If the XML element has an attribute not handled by the previous
|
||||
// rule and the struct has a field with an associated tag containing
|
||||
// ",any,attr", Unmarshal records the attribute value in the first
|
||||
// such field.
|
||||
//
|
||||
// * If the XML element contains character data, that data is
|
||||
// accumulated in the first struct field that has tag ",chardata".
|
||||
// The struct field may have type []byte or string.
|
||||
// If there is no such field, the character data is discarded.
|
||||
//
|
||||
// * If the XML element contains comments, they are accumulated in
|
||||
// the first struct field that has tag ",comment". The struct
|
||||
// field may have type []byte or string. If there is no such
|
||||
// field, the comments are discarded.
|
||||
//
|
||||
// * If the XML element contains a sub-element whose name matches
|
||||
// the prefix of a tag formatted as "a" or "a>b>c", unmarshal
|
||||
// will descend into the XML structure looking for elements with the
|
||||
// given names, and will map the innermost elements to that struct
|
||||
// field. A tag starting with ">" is equivalent to one starting
|
||||
// with the field name followed by ">".
|
||||
//
|
||||
// * If the XML element contains a sub-element whose name matches
|
||||
// a struct field's XMLName tag and the struct field has no
|
||||
// explicit name tag as per the previous rule, unmarshal maps
|
||||
// the sub-element to that struct field.
|
||||
//
|
||||
// * If the XML element contains a sub-element whose name matches a
|
||||
// field without any mode flags (",attr", ",chardata", etc), Unmarshal
|
||||
// maps the sub-element to that struct field.
|
||||
//
|
||||
// * If the XML element contains a sub-element that hasn't matched any
|
||||
// of the above rules and the struct has a field with tag ",any",
|
||||
// unmarshal maps the sub-element to that struct field.
|
||||
//
|
||||
// * An anonymous struct field is handled as if the fields of its
|
||||
// value were part of the outer struct.
|
||||
//
|
||||
// * A struct field with tag "-" is never unmarshaled into.
|
||||
//
|
||||
// Unmarshal maps an XML element to a string or []byte by saving the
|
||||
// concatenation of that element's character data in the string or
|
||||
// []byte. The saved []byte is never nil.
|
||||
//
|
||||
// Unmarshal maps an attribute value to a string or []byte by saving
|
||||
// the value in the string or slice.
|
||||
//
|
||||
// Unmarshal maps an attribute value to an Attr by saving the attribute,
|
||||
// including its name, in the Attr.
|
||||
//
|
||||
// Unmarshal maps an XML element or attribute value to a slice by
|
||||
// extending the length of the slice and mapping the element or attribute
|
||||
// to the newly created value.
|
||||
//
|
||||
// Unmarshal maps an XML element or attribute value to a bool by
|
||||
// setting it to the boolean value represented by the string.
|
||||
//
|
||||
// Unmarshal maps an XML element or attribute value to an integer or
|
||||
// floating-point field by setting the field to the result of
|
||||
// interpreting the string value in decimal. There is no check for
|
||||
// overflow.
|
||||
//
|
||||
// Unmarshal maps an XML element to a Name by recording the element
|
||||
// name.
|
||||
//
|
||||
// Unmarshal maps an XML element to a pointer by setting the pointer
|
||||
// to a freshly allocated value and then mapping the element to that value.
|
||||
//
|
||||
// A missing element or empty attribute value will be unmarshaled as a zero value.
|
||||
// If the field is a slice, a zero value will be appended to the field. Otherwise, the
|
||||
// field will be set to its zero value.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
return NewDecoder(bytes.NewReader(data)).Decode(v)
|
||||
}
|
||||
|
||||
// Decode works like Unmarshal, except it reads the decoder
|
||||
// stream to find the start element.
|
||||
func (d *Decoder) Decode(v interface{}) error {
|
||||
return d.DecodeElement(v, nil)
|
||||
}
|
||||
|
||||
// DecodeElement works like Unmarshal except that it takes
|
||||
// a pointer to the start XML element to decode into v.
|
||||
// It is useful when a client reads some raw XML tokens itself
|
||||
// but also wants to defer to Unmarshal for some elements.
|
||||
func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error {
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return errors.New("non-pointer passed to Unmarshal")
|
||||
}
|
||||
return d.unmarshal(val.Elem(), start)
|
||||
}
|
||||
|
||||
// An UnmarshalError represents an error in the unmarshaling process.
|
||||
type UnmarshalError string
|
||||
|
||||
func (e UnmarshalError) Error() string { return string(e) }
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal
|
||||
// an XML element description of themselves.
|
||||
//
|
||||
// UnmarshalXML decodes a single XML element
|
||||
// beginning with the given start element.
|
||||
// If it returns an error, the outer call to Unmarshal stops and
|
||||
// returns that error.
|
||||
// UnmarshalXML must consume exactly one XML element.
|
||||
// One common implementation strategy is to unmarshal into
|
||||
// a separate value with a layout matching the expected XML
|
||||
// using d.DecodeElement, and then to copy the data from
|
||||
// that value into the receiver.
|
||||
// Another common strategy is to use d.Token to process the
|
||||
// XML object one token at a time.
|
||||
// UnmarshalXML may not use d.RawToken.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalXML(d *Decoder, start StartElement) error
|
||||
}
|
||||
|
||||
// UnmarshalerAttr is the interface implemented by objects that can unmarshal
|
||||
// an XML attribute description of themselves.
|
||||
//
|
||||
// UnmarshalXMLAttr decodes a single XML attribute.
|
||||
// If it returns an error, the outer call to Unmarshal stops and
|
||||
// returns that error.
|
||||
// UnmarshalXMLAttr is used only for struct fields with the
|
||||
// "attr" option in the field tag.
|
||||
type UnmarshalerAttr interface {
|
||||
UnmarshalXMLAttr(attr Attr) error
|
||||
}
|
||||
|
||||
// receiverType returns the receiver type to use in an expression like "%s.MethodName".
|
||||
func receiverType(val interface{}) string {
|
||||
t := reflect.TypeOf(val)
|
||||
if t.Name() != "" {
|
||||
return t.String()
|
||||
}
|
||||
return "(" + t.String() + ")"
|
||||
}
|
||||
|
||||
// unmarshalInterface unmarshals a single XML element into val.
|
||||
// start is the opening tag of the element.
|
||||
func (d *Decoder) unmarshalInterface(val Unmarshaler, start *StartElement) error {
|
||||
// Record that decoder must stop at end tag corresponding to start.
|
||||
d.pushEOF()
|
||||
|
||||
d.unmarshalDepth++
|
||||
err := val.UnmarshalXML(d, *start)
|
||||
d.unmarshalDepth--
|
||||
if err != nil {
|
||||
d.popEOF()
|
||||
return err
|
||||
}
|
||||
|
||||
if !d.popEOF() {
|
||||
return fmt.Errorf("xml: %s.UnmarshalXML did not consume entire <%s> element", receiverType(val), start.Name.Local)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmarshalTextInterface unmarshals a single XML element into val.
|
||||
// The chardata contained in the element (but not its children)
|
||||
// is passed to the text unmarshaler.
|
||||
func (d *Decoder) unmarshalTextInterface(val encoding.TextUnmarshaler) error {
|
||||
var buf []byte
|
||||
depth := 1
|
||||
for depth > 0 {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case CharData:
|
||||
if depth == 1 {
|
||||
buf = append(buf, t...)
|
||||
}
|
||||
case StartElement:
|
||||
depth++
|
||||
case EndElement:
|
||||
depth--
|
||||
}
|
||||
}
|
||||
return val.UnmarshalText(buf)
|
||||
}
|
||||
|
||||
// unmarshalAttr unmarshals a single XML attribute into val.
|
||||
func (d *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error {
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
if val.CanInterface() && val.Type().Implements(unmarshalerAttrType) {
|
||||
// This is an unmarshaler with a non-pointer receiver,
|
||||
// so it's likely to be incorrect, but we do what we're told.
|
||||
return val.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr)
|
||||
}
|
||||
if val.CanAddr() {
|
||||
pv := val.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(unmarshalerAttrType) {
|
||||
return pv.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr)
|
||||
}
|
||||
}
|
||||
|
||||
// Not an UnmarshalerAttr; try encoding.TextUnmarshaler.
|
||||
if val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
|
||||
// This is an unmarshaler with a non-pointer receiver,
|
||||
// so it's likely to be incorrect, but we do what we're told.
|
||||
return val.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))
|
||||
}
|
||||
if val.CanAddr() {
|
||||
pv := val.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
|
||||
return pv.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))
|
||||
}
|
||||
}
|
||||
|
||||
if val.Type().Kind() == reflect.Slice && val.Type().Elem().Kind() != reflect.Uint8 {
|
||||
// Slice of element values.
|
||||
// Grow slice.
|
||||
n := val.Len()
|
||||
val.Set(reflect.Append(val, reflect.Zero(val.Type().Elem())))
|
||||
|
||||
// Recur to read element into slice.
|
||||
if err := d.unmarshalAttr(val.Index(n), attr); err != nil {
|
||||
val.SetLen(n)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if val.Type() == attrType {
|
||||
val.Set(reflect.ValueOf(attr))
|
||||
return nil
|
||||
}
|
||||
|
||||
return copyValue(val, []byte(attr.Value))
|
||||
}
|
||||
|
||||
var (
|
||||
attrType = reflect.TypeOf(Attr{})
|
||||
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
unmarshalerAttrType = reflect.TypeOf((*UnmarshalerAttr)(nil)).Elem()
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Unmarshal a single XML element into val.
|
||||
func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error {
|
||||
// Find start element if we need it.
|
||||
if start == nil {
|
||||
for {
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t, ok := tok.(StartElement); ok {
|
||||
start = &t
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load value from interface, but only if the result will be
|
||||
// usefully addressable.
|
||||
if val.Kind() == reflect.Interface && !val.IsNil() {
|
||||
e := val.Elem()
|
||||
if e.Kind() == reflect.Ptr && !e.IsNil() {
|
||||
val = e
|
||||
}
|
||||
}
|
||||
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
if val.CanInterface() && val.Type().Implements(unmarshalerType) {
|
||||
// This is an unmarshaler with a non-pointer receiver,
|
||||
// so it's likely to be incorrect, but we do what we're told.
|
||||
return d.unmarshalInterface(val.Interface().(Unmarshaler), start)
|
||||
}
|
||||
|
||||
if val.CanAddr() {
|
||||
pv := val.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(unmarshalerType) {
|
||||
return d.unmarshalInterface(pv.Interface().(Unmarshaler), start)
|
||||
}
|
||||
}
|
||||
|
||||
if val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
|
||||
return d.unmarshalTextInterface(val.Interface().(encoding.TextUnmarshaler))
|
||||
}
|
||||
|
||||
if val.CanAddr() {
|
||||
pv := val.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
|
||||
return d.unmarshalTextInterface(pv.Interface().(encoding.TextUnmarshaler))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
data []byte
|
||||
saveData reflect.Value
|
||||
comment []byte
|
||||
saveComment reflect.Value
|
||||
saveXML reflect.Value
|
||||
saveXMLIndex int
|
||||
saveXMLData []byte
|
||||
saveAny reflect.Value
|
||||
sv reflect.Value
|
||||
tinfo *typeInfo
|
||||
err error
|
||||
)
|
||||
|
||||
switch v := val; v.Kind() {
|
||||
default:
|
||||
return errors.New("unknown type " + v.Type().String())
|
||||
|
||||
case reflect.Interface:
|
||||
// TODO: For now, simply ignore the field. In the near
|
||||
// future we may choose to unmarshal the start
|
||||
// element on it, if not nil.
|
||||
return d.Skip()
|
||||
|
||||
case reflect.Slice:
|
||||
typ := v.Type()
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
// []byte
|
||||
saveData = v
|
||||
break
|
||||
}
|
||||
|
||||
// Slice of element values.
|
||||
// Grow slice.
|
||||
n := v.Len()
|
||||
v.Set(reflect.Append(val, reflect.Zero(v.Type().Elem())))
|
||||
|
||||
// Recur to read element into slice.
|
||||
if err := d.unmarshal(v.Index(n), start); err != nil {
|
||||
v.SetLen(n)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.String:
|
||||
saveData = v
|
||||
|
||||
case reflect.Struct:
|
||||
typ := v.Type()
|
||||
if typ == nameType {
|
||||
v.Set(reflect.ValueOf(start.Name))
|
||||
break
|
||||
}
|
||||
|
||||
sv = v
|
||||
tinfo, err = getTypeInfo(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate and assign element name.
|
||||
if tinfo.xmlname != nil {
|
||||
finfo := tinfo.xmlname
|
||||
if finfo.name != "" && finfo.name != start.Name.Local {
|
||||
return UnmarshalError("expected element type <" + finfo.name + "> but have <" + start.Name.Local + ">")
|
||||
}
|
||||
if finfo.xmlns != "" && finfo.xmlns != start.Name.Space {
|
||||
e := "expected element <" + finfo.name + "> in name space " + finfo.xmlns + " but have "
|
||||
if start.Name.Space == "" {
|
||||
e += "no name space"
|
||||
} else {
|
||||
e += start.Name.Space
|
||||
}
|
||||
return UnmarshalError(e)
|
||||
}
|
||||
fv := finfo.value(sv)
|
||||
if _, ok := fv.Interface().(Name); ok {
|
||||
fv.Set(reflect.ValueOf(start.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// Assign attributes.
|
||||
for _, a := range start.Attr {
|
||||
handled := false
|
||||
any := -1
|
||||
for i := range tinfo.fields {
|
||||
finfo := &tinfo.fields[i]
|
||||
switch finfo.flags & fMode {
|
||||
case fAttr:
|
||||
strv := finfo.value(sv)
|
||||
if a.Name.Local == finfo.name && (finfo.xmlns == "" || finfo.xmlns == a.Name.Space) {
|
||||
if err := d.unmarshalAttr(strv, a); err != nil {
|
||||
return err
|
||||
}
|
||||
handled = true
|
||||
}
|
||||
|
||||
case fAny | fAttr:
|
||||
if any == -1 {
|
||||
any = i
|
||||
}
|
||||
}
|
||||
}
|
||||
if !handled && any >= 0 {
|
||||
finfo := &tinfo.fields[any]
|
||||
strv := finfo.value(sv)
|
||||
if err := d.unmarshalAttr(strv, a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether we need to save character data or comments.
|
||||
for i := range tinfo.fields {
|
||||
finfo := &tinfo.fields[i]
|
||||
switch finfo.flags & fMode {
|
||||
case fCDATA, fCharData:
|
||||
if !saveData.IsValid() {
|
||||
saveData = finfo.value(sv)
|
||||
}
|
||||
|
||||
case fComment:
|
||||
if !saveComment.IsValid() {
|
||||
saveComment = finfo.value(sv)
|
||||
}
|
||||
|
||||
case fAny, fAny | fElement:
|
||||
if !saveAny.IsValid() {
|
||||
saveAny = finfo.value(sv)
|
||||
}
|
||||
|
||||
case fInnerXml:
|
||||
if !saveXML.IsValid() {
|
||||
saveXML = finfo.value(sv)
|
||||
if d.saved == nil {
|
||||
saveXMLIndex = 0
|
||||
d.saved = new(bytes.Buffer)
|
||||
} else {
|
||||
saveXMLIndex = d.savedOffset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find end element.
|
||||
// Process sub-elements along the way.
|
||||
Loop:
|
||||
for {
|
||||
var savedOffset int
|
||||
if saveXML.IsValid() {
|
||||
savedOffset = d.savedOffset()
|
||||
}
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case StartElement:
|
||||
consumed := false
|
||||
if sv.IsValid() {
|
||||
consumed, err = d.unmarshalPath(tinfo, sv, nil, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !consumed && saveAny.IsValid() {
|
||||
consumed = true
|
||||
if err := d.unmarshal(saveAny, &t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !consumed {
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case EndElement:
|
||||
if saveXML.IsValid() {
|
||||
saveXMLData = d.saved.Bytes()[saveXMLIndex:savedOffset]
|
||||
if saveXMLIndex == 0 {
|
||||
d.saved = nil
|
||||
}
|
||||
}
|
||||
break Loop
|
||||
|
||||
case CharData:
|
||||
if saveData.IsValid() {
|
||||
data = append(data, t...)
|
||||
}
|
||||
|
||||
case Comment:
|
||||
if saveComment.IsValid() {
|
||||
comment = append(comment, t...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if saveData.IsValid() && saveData.CanInterface() && saveData.Type().Implements(textUnmarshalerType) {
|
||||
if err := saveData.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
|
||||
return err
|
||||
}
|
||||
saveData = reflect.Value{}
|
||||
}
|
||||
|
||||
if saveData.IsValid() && saveData.CanAddr() {
|
||||
pv := saveData.Addr()
|
||||
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
|
||||
if err := pv.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
|
||||
return err
|
||||
}
|
||||
saveData = reflect.Value{}
|
||||
}
|
||||
}
|
||||
|
||||
if err := copyValue(saveData, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch t := saveComment; t.Kind() {
|
||||
case reflect.String:
|
||||
t.SetString(string(comment))
|
||||
case reflect.Slice:
|
||||
t.Set(reflect.ValueOf(comment))
|
||||
}
|
||||
|
||||
switch t := saveXML; t.Kind() {
|
||||
case reflect.String:
|
||||
t.SetString(string(saveXMLData))
|
||||
case reflect.Slice:
|
||||
if t.Type().Elem().Kind() == reflect.Uint8 {
|
||||
t.Set(reflect.ValueOf(saveXMLData))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyValue(dst reflect.Value, src []byte) (err error) {
|
||||
dst0 := dst
|
||||
|
||||
if dst.Kind() == reflect.Ptr {
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.New(dst.Type().Elem()))
|
||||
}
|
||||
dst = dst.Elem()
|
||||
}
|
||||
|
||||
// Save accumulated data.
|
||||
switch dst.Kind() {
|
||||
case reflect.Invalid:
|
||||
// Probably a comment.
|
||||
default:
|
||||
return errors.New("cannot unmarshal into " + dst0.Type().String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if len(src) == 0 {
|
||||
dst.SetInt(0)
|
||||
return nil
|
||||
}
|
||||
itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dst.SetInt(itmp)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
if len(src) == 0 {
|
||||
dst.SetUint(0)
|
||||
return nil
|
||||
}
|
||||
utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dst.SetUint(utmp)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if len(src) == 0 {
|
||||
dst.SetFloat(0)
|
||||
return nil
|
||||
}
|
||||
ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dst.SetFloat(ftmp)
|
||||
case reflect.Bool:
|
||||
if len(src) == 0 {
|
||||
dst.SetBool(false)
|
||||
return nil
|
||||
}
|
||||
value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dst.SetBool(value)
|
||||
case reflect.String:
|
||||
dst.SetString(string(src))
|
||||
case reflect.Slice:
|
||||
if len(src) == 0 {
|
||||
// non-nil to flag presence
|
||||
src = []byte{}
|
||||
}
|
||||
dst.SetBytes(src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmarshalPath walks down an XML structure looking for wanted
|
||||
// paths, and calls unmarshal on them.
|
||||
// The consumed result tells whether XML elements have been consumed
|
||||
// from the Decoder until start's matching end element, or if it's
|
||||
// still untouched because start is uninteresting for sv's fields.
|
||||
func (d *Decoder) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement) (consumed bool, err error) {
|
||||
recurse := false
|
||||
Loop:
|
||||
for i := range tinfo.fields {
|
||||
finfo := &tinfo.fields[i]
|
||||
if finfo.flags&fElement == 0 || len(finfo.parents) < len(parents) || finfo.xmlns != "" && finfo.xmlns != start.Name.Space {
|
||||
continue
|
||||
}
|
||||
for j := range parents {
|
||||
if parents[j] != finfo.parents[j] {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local {
|
||||
// It's a perfect match, unmarshal the field.
|
||||
return true, d.unmarshal(finfo.value(sv), start)
|
||||
}
|
||||
if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local {
|
||||
// It's a prefix for the field. Break and recurse
|
||||
// since it's not ok for one field path to be itself
|
||||
// the prefix for another field path.
|
||||
recurse = true
|
||||
|
||||
// We can reuse the same slice as long as we
|
||||
// don't try to append to it.
|
||||
parents = finfo.parents[:len(parents)+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if !recurse {
|
||||
// We have no business with this element.
|
||||
return false, nil
|
||||
}
|
||||
// The element is not a perfect match for any field, but one
|
||||
// or more fields have the path to this element as a parent
|
||||
// prefix. Recurse and attempt to match these.
|
||||
for {
|
||||
var tok Token
|
||||
tok, err = d.Token()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case StartElement:
|
||||
consumed2, err := d.unmarshalPath(tinfo, sv, parents, &t)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if !consumed2 {
|
||||
if err := d.Skip(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
case EndElement:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip reads tokens until it has consumed the end element
|
||||
// matching the most recent start element already consumed.
|
||||
// It recurs if it encounters a start element, so it can be used to
|
||||
// skip nested structures.
|
||||
// It returns nil if it finds an end element matching the start
|
||||
// element; otherwise it returns an error describing the problem.
|
||||
func (d *Decoder) Skip() error {
|
||||
for {
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tok.(type) {
|
||||
case StartElement:
|
||||
if err := d.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
case EndElement:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
364
vendor/github.com/aclindsa/xml/typeinfo.go
generated
vendored
364
vendor/github.com/aclindsa/xml/typeinfo.go
generated
vendored
@@ -1,364 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// typeInfo holds details for the xml representation of a type.
|
||||
type typeInfo struct {
|
||||
xmlname *fieldInfo
|
||||
fields []fieldInfo
|
||||
}
|
||||
|
||||
// fieldInfo holds details for the xml representation of a single field.
|
||||
type fieldInfo struct {
|
||||
idx []int
|
||||
name string
|
||||
xmlns string
|
||||
flags fieldFlags
|
||||
parents []string
|
||||
}
|
||||
|
||||
type fieldFlags int
|
||||
|
||||
const (
|
||||
fElement fieldFlags = 1 << iota
|
||||
fAttr
|
||||
fCDATA
|
||||
fCharData
|
||||
fInnerXml
|
||||
fComment
|
||||
fAny
|
||||
|
||||
fOmitEmpty
|
||||
|
||||
fMode = fElement | fAttr | fCDATA | fCharData | fInnerXml | fComment | fAny
|
||||
|
||||
xmlName = "XMLName"
|
||||
)
|
||||
|
||||
var tinfoMap sync.Map // map[reflect.Type]*typeInfo
|
||||
|
||||
var nameType = reflect.TypeOf(Name{})
|
||||
|
||||
// getTypeInfo returns the typeInfo structure with details necessary
|
||||
// for marshaling and unmarshaling typ.
|
||||
func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
|
||||
if ti, ok := tinfoMap.Load(typ); ok {
|
||||
return ti.(*typeInfo), nil
|
||||
}
|
||||
|
||||
tinfo := &typeInfo{}
|
||||
if typ.Kind() == reflect.Struct && typ != nameType {
|
||||
n := typ.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
if (f.PkgPath != "" && !f.Anonymous) || f.Tag.Get("xml") == "-" {
|
||||
continue // Private field
|
||||
}
|
||||
|
||||
// For embedded structs, embed its fields.
|
||||
if f.Anonymous {
|
||||
t := f.Type
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() == reflect.Struct {
|
||||
inner, err := getTypeInfo(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tinfo.xmlname == nil {
|
||||
tinfo.xmlname = inner.xmlname
|
||||
}
|
||||
for _, finfo := range inner.fields {
|
||||
finfo.idx = append([]int{i}, finfo.idx...)
|
||||
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
finfo, err := structFieldInfo(typ, &f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Name == xmlName {
|
||||
tinfo.xmlname = finfo
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the field if it doesn't conflict with other fields.
|
||||
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ti, _ := tinfoMap.LoadOrStore(typ, tinfo)
|
||||
return ti.(*typeInfo), nil
|
||||
}
|
||||
|
||||
// structFieldInfo builds and returns a fieldInfo for f.
|
||||
func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
|
||||
finfo := &fieldInfo{idx: f.Index}
|
||||
|
||||
// Split the tag from the xml namespace if necessary.
|
||||
tag := f.Tag.Get("xml")
|
||||
if i := strings.IndexByte(tag, ' '); i >= 0 {
|
||||
finfo.xmlns, tag = tag[:i], tag[i+1:]
|
||||
}
|
||||
|
||||
// Parse flags.
|
||||
tokens := strings.Split(tag, ",")
|
||||
if len(tokens) == 1 {
|
||||
finfo.flags = fElement
|
||||
} else {
|
||||
tag = tokens[0]
|
||||
for _, flag := range tokens[1:] {
|
||||
switch flag {
|
||||
case "attr":
|
||||
finfo.flags |= fAttr
|
||||
case "cdata":
|
||||
finfo.flags |= fCDATA
|
||||
case "chardata":
|
||||
finfo.flags |= fCharData
|
||||
case "innerxml":
|
||||
finfo.flags |= fInnerXml
|
||||
case "comment":
|
||||
finfo.flags |= fComment
|
||||
case "any":
|
||||
finfo.flags |= fAny
|
||||
case "omitempty":
|
||||
finfo.flags |= fOmitEmpty
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the flags used.
|
||||
valid := true
|
||||
switch mode := finfo.flags & fMode; mode {
|
||||
case 0:
|
||||
finfo.flags |= fElement
|
||||
case fAttr, fCDATA, fCharData, fInnerXml, fComment, fAny, fAny | fAttr:
|
||||
if f.Name == xmlName || tag != "" && mode != fAttr {
|
||||
valid = false
|
||||
}
|
||||
default:
|
||||
// This will also catch multiple modes in a single field.
|
||||
valid = false
|
||||
}
|
||||
if finfo.flags&fMode == fAny {
|
||||
finfo.flags |= fElement
|
||||
}
|
||||
if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
|
||||
valid = false
|
||||
}
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q",
|
||||
f.Name, typ, f.Tag.Get("xml"))
|
||||
}
|
||||
}
|
||||
|
||||
// Use of xmlns without a name is not allowed.
|
||||
if finfo.xmlns != "" && tag == "" {
|
||||
return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q",
|
||||
f.Name, typ, f.Tag.Get("xml"))
|
||||
}
|
||||
|
||||
if f.Name == xmlName {
|
||||
// The XMLName field records the XML element name. Don't
|
||||
// process it as usual because its name should default to
|
||||
// empty rather than to the field name.
|
||||
finfo.name = tag
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
if tag == "" {
|
||||
// If the name part of the tag is completely empty, get
|
||||
// default from XMLName of underlying struct if feasible,
|
||||
// or field name otherwise.
|
||||
if xmlname := lookupXMLName(f.Type); xmlname != nil {
|
||||
finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
|
||||
} else {
|
||||
finfo.name = f.Name
|
||||
}
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
// Prepare field name and parents.
|
||||
parents := strings.Split(tag, ">")
|
||||
if parents[0] == "" {
|
||||
parents[0] = f.Name
|
||||
}
|
||||
if parents[len(parents)-1] == "" {
|
||||
return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)
|
||||
}
|
||||
finfo.name = parents[len(parents)-1]
|
||||
if len(parents) > 1 {
|
||||
if (finfo.flags & fElement) == 0 {
|
||||
return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
|
||||
}
|
||||
finfo.parents = parents[:len(parents)-1]
|
||||
}
|
||||
|
||||
// If the field type has an XMLName field, the names must match
|
||||
// so that the behavior of both marshaling and unmarshaling
|
||||
// is straightforward and unambiguous.
|
||||
if finfo.flags&fElement != 0 {
|
||||
ftyp := f.Type
|
||||
xmlname := lookupXMLName(ftyp)
|
||||
if xmlname != nil && xmlname.name != finfo.name {
|
||||
return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName",
|
||||
finfo.name, typ, f.Name, xmlname.name, ftyp)
|
||||
}
|
||||
}
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
// lookupXMLName returns the fieldInfo for typ's XMLName field
|
||||
// in case it exists and has a valid xml field tag, otherwise
|
||||
// it returns nil.
|
||||
func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
for i, n := 0, typ.NumField(); i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.Name != xmlName {
|
||||
continue
|
||||
}
|
||||
finfo, err := structFieldInfo(typ, &f)
|
||||
if finfo.name != "" && err == nil {
|
||||
return finfo
|
||||
}
|
||||
// Also consider errors as a non-existent field tag
|
||||
// and let getTypeInfo itself report the error.
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// addFieldInfo adds finfo to tinfo.fields if there are no
|
||||
// conflicts, or if conflicts arise from previous fields that were
|
||||
// obtained from deeper embedded structures than finfo. In the latter
|
||||
// case, the conflicting entries are dropped.
|
||||
// A conflict occurs when the path (parent + name) to a field is
|
||||
// itself a prefix of another path, or when two paths match exactly.
|
||||
// It is okay for field paths to share a common, shorter prefix.
|
||||
func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
|
||||
var conflicts []int
|
||||
Loop:
|
||||
// First, figure all conflicts. Most working code will have none.
|
||||
for i := range tinfo.fields {
|
||||
oldf := &tinfo.fields[i]
|
||||
if oldf.flags&fMode != newf.flags&fMode {
|
||||
continue
|
||||
}
|
||||
if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns {
|
||||
continue
|
||||
}
|
||||
minl := min(len(newf.parents), len(oldf.parents))
|
||||
for p := 0; p < minl; p++ {
|
||||
if oldf.parents[p] != newf.parents[p] {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
if len(oldf.parents) > len(newf.parents) {
|
||||
if oldf.parents[len(newf.parents)] == newf.name {
|
||||
conflicts = append(conflicts, i)
|
||||
}
|
||||
} else if len(oldf.parents) < len(newf.parents) {
|
||||
if newf.parents[len(oldf.parents)] == oldf.name {
|
||||
conflicts = append(conflicts, i)
|
||||
}
|
||||
} else {
|
||||
if newf.name == oldf.name {
|
||||
conflicts = append(conflicts, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Without conflicts, add the new field and return.
|
||||
if conflicts == nil {
|
||||
tinfo.fields = append(tinfo.fields, *newf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If any conflict is shallower, ignore the new field.
|
||||
// This matches the Go field resolution on embedding.
|
||||
for _, i := range conflicts {
|
||||
if len(tinfo.fields[i].idx) < len(newf.idx) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, if any of them is at the same depth level, it's an error.
|
||||
for _, i := range conflicts {
|
||||
oldf := &tinfo.fields[i]
|
||||
if len(oldf.idx) == len(newf.idx) {
|
||||
f1 := typ.FieldByIndex(oldf.idx)
|
||||
f2 := typ.FieldByIndex(newf.idx)
|
||||
return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the new field is shallower, and thus takes precedence,
|
||||
// so drop the conflicting fields from tinfo and append the new one.
|
||||
for c := len(conflicts) - 1; c >= 0; c-- {
|
||||
i := conflicts[c]
|
||||
copy(tinfo.fields[i:], tinfo.fields[i+1:])
|
||||
tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
|
||||
}
|
||||
tinfo.fields = append(tinfo.fields, *newf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// A TagPathError represents an error in the unmarshaling process
|
||||
// caused by the use of field tags with conflicting paths.
|
||||
type TagPathError struct {
|
||||
Struct reflect.Type
|
||||
Field1, Tag1 string
|
||||
Field2, Tag2 string
|
||||
}
|
||||
|
||||
func (e *TagPathError) Error() string {
|
||||
return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
|
||||
}
|
||||
|
||||
// value returns v's field value corresponding to finfo.
|
||||
// It's equivalent to v.FieldByIndex(finfo.idx), but initializes
|
||||
// and dereferences pointers as necessary.
|
||||
func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
|
||||
for i, x := range finfo.idx {
|
||||
if i > 0 {
|
||||
t := v.Type()
|
||||
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
v = v.Field(x)
|
||||
}
|
||||
return v
|
||||
}
|
||||
2075
vendor/github.com/aclindsa/xml/xml.go
generated
vendored
2075
vendor/github.com/aclindsa/xml/xml.go
generated
vendored
File diff suppressed because it is too large
Load Diff
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
@@ -1,41 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
|
||||
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
|
||||
|
||||
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
|
||||
|
||||
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
|
||||
|
||||
### Fixes
|
||||
|
||||
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
|
||||
|
||||
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
|
||||
|
||||
## Changelog
|
||||
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
@@ -1,26 +0,0 @@
|
||||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Tips
|
||||
|
||||
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
|
||||
|
||||
Always try to include a test case! If it is not possible or not necessary,
|
||||
please explain why in the pull request description.
|
||||
|
||||
### Releasing
|
||||
|
||||
Commits that would precipitate a SemVer change, as described in the Conventional
|
||||
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
|
||||
to create a release candidate pull request. Once submitted, `release-please`
|
||||
will create a release.
|
||||
|
||||
For tips on how to work with `release-please`, see its documentation.
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
||||
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
@@ -1,9 +0,0 @@
|
||||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
||||
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
21
vendor/github.com/google/uuid/README.md
generated
vendored
21
vendor/github.com/google/uuid/README.md
generated
vendored
@@ -1,21 +0,0 @@
|
||||
# uuid
|
||||
The uuid package generates and inspects UUIDs based on
|
||||
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
|
||||
and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
This package is based on the github.com/pborman/uuid package (previously named
|
||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||
|
||||
###### Install
|
||||
```sh
|
||||
go get github.com/google/uuid
|
||||
```
|
||||
|
||||
###### Documentation
|
||||
[](https://pkg.go.dev/github.com/google/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://pkg.go.dev/github.com/google/uuid
|
||||
80
vendor/github.com/google/uuid/dce.go
generated
vendored
80
vendor/github.com/google/uuid/dce.go
generated
vendored
@@ -1,80 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err == nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() (UUID, error) {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() (UUID, error) {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||
// for Version 2 UUIDs.
|
||||
func (uuid UUID) Domain() Domain {
|
||||
return Domain(uuid[9])
|
||||
}
|
||||
|
||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||
// UUIDs.
|
||||
func (uuid UUID) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(uuid[0:4])
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
||||
12
vendor/github.com/google/uuid/doc.go
generated
vendored
12
vendor/github.com/google/uuid/doc.go
generated
vendored
@@ -1,12 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package uuid generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||
// Services.
|
||||
//
|
||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||
// maps or compared directly.
|
||||
package uuid
|
||||
59
vendor/github.com/google/uuid/hash.go
generated
vendored
59
vendor/github.com/google/uuid/hash.go
generated
vendored
@@ -1,59 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known namespace IDs and UUIDs
|
||||
var (
|
||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
Nil UUID // empty UUID, all zeros
|
||||
|
||||
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
|
||||
Max = UUID{
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
}
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:]) //nolint:errcheck
|
||||
h.Write(data) //nolint:errcheck
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
||||
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||
var js [36]byte
|
||||
encodeHex(js[:], uuid)
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*uuid = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||
return uuid[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(uuid[:], data)
|
||||
return nil
|
||||
}
|
||||
90
vendor/github.com/google/uuid/node.go
generated
vendored
90
vendor/github.com/google/uuid/node.go
generated
vendored
@@ -1,90 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
ifname string // name of interface being used
|
||||
nodeID [6]byte // hardware for version 1 UUIDs
|
||||
zeroID [6]byte // nodeID with only 0's
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||
if iname != "" && addr != nil {
|
||||
ifname = iname
|
||||
copy(nodeID[:], addr)
|
||||
return true
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
ifname = "random"
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := nodeID
|
||||
return nid[:]
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
copy(nodeID[:], id)
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
var node [6]byte
|
||||
copy(node[:], uuid[10:])
|
||||
return node[:]
|
||||
}
|
||||
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
@@ -1,12 +0,0 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build js
|
||||
|
||||
package uuid
|
||||
|
||||
// getHardwareInterface returns nil values for the JS version of the code.
|
||||
// This removes the "net" dependency, because it is not used in the browser.
|
||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
||||
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
@@ -1,33 +0,0 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !js
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var interfaces []net.Interface // cached list of interfaces
|
||||
|
||||
// getHardwareInterface returns the name and hardware address of interface name.
|
||||
// If name is "" then the name and hardware address of one of the system's
|
||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||
// there are no interfaces) then "", nil is returned.
|
||||
//
|
||||
// Only addresses of at least 6 bytes are returned.
|
||||
func getHardwareInterface(name string) (string, []byte) {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
return ifs.Name, ifs.HardwareAddr
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
118
vendor/github.com/google/uuid/null.go
generated
vendored
118
vendor/github.com/google/uuid/null.go
generated
vendored
@@ -1,118 +0,0 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
// NullUUID represents a UUID that may be null.
|
||||
// NullUUID implements the SQL driver.Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var u uuid.NullUUID
|
||||
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
||||
// ...
|
||||
// if u.Valid {
|
||||
// // use u.UUID
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool // Valid is true if UUID is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the SQL driver.Scanner interface.
|
||||
func (nu *NullUUID) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
nu.UUID, nu.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
err := nu.UUID.Scan(value)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nu NullUUID) Value() (driver.Value, error) {
|
||||
if !nu.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return nu.UUID.Value()
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID[:], nil
|
||||
}
|
||||
|
||||
return []byte(nil), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(nu.UUID[:], data)
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (nu NullUUID) MarshalText() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID.MarshalText()
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
nu.UUID = id
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return json.Marshal(nu.UUID)
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, jsonNull) {
|
||||
*nu = NullUUID{}
|
||||
return nil // valid null UUID
|
||||
}
|
||||
err := json.Unmarshal(data, &nu.UUID)
|
||||
nu.Valid = err == nil
|
||||
return err
|
||||
}
|
||||
59
vendor/github.com/google/uuid/sql.go
generated
vendored
59
vendor/github.com/google/uuid/sql.go
generated
vendored
@@ -1,59 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see Parse for required string format
|
||||
u, err := Parse(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Scan: %v", err)
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
|
||||
case []byte:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(src) != 16 {
|
||||
return uuid.Scan(string(src))
|
||||
}
|
||||
copy((*uuid)[:], src)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
||||
134
vendor/github.com/google/uuid/time.go
generated
vendored
134
vendor/github.com/google/uuid/time.go
generated
vendored
@@ -1,134 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clockSeq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clockSeq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||
// random clock sequence is generated the first time a clock sequence is
|
||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clockSeq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
oldSeq := clockSeq
|
||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if oldSeq != clockSeq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
|
||||
func (uuid UUID) Time() Time {
|
||||
var t Time
|
||||
switch uuid.Version() {
|
||||
case 6:
|
||||
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
|
||||
t = Time(time)
|
||||
case 7:
|
||||
time := binary.BigEndian.Uint64(uuid[:8])
|
||||
t = Time((time>>16)*10000 + g1582ns100)
|
||||
default: // forward compatible
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
t = Time(time)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid.
|
||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() int {
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||
}
|
||||
43
vendor/github.com/google/uuid/util.go
generated
vendored
43
vendor/github.com/google/uuid/util.go
generated
vendored
@@ -1,43 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts hex characters x1 and x2 into a byte.
|
||||
func xtob(x1, x2 byte) (byte, bool) {
|
||||
b1 := xvalues[x1]
|
||||
b2 := xvalues[x2]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
||||
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
@@ -1,365 +0,0 @@
|
||||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID [16]byte
|
||||
|
||||
// A Version represents a UUID's version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUID's variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
const randPoolSize = 16 * 16
|
||||
|
||||
var (
|
||||
rander = rand.Reader // random function
|
||||
poolEnabled = false
|
||||
poolMu sync.Mutex
|
||||
poolPos = randPoolSize // protected with poolMu
|
||||
pool [randPoolSize]byte // protected with poolMu
|
||||
)
|
||||
|
||||
type invalidLengthError struct{ len int }
|
||||
|
||||
func (err invalidLengthError) Error() string {
|
||||
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
||||
}
|
||||
|
||||
// IsInvalidLengthError is matcher function for custom error invalidLengthError
|
||||
func IsInvalidLengthError(err error) bool {
|
||||
_, ok := err.(invalidLengthError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
|
||||
// the standard UUID forms defined in RFC 4122
|
||||
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
|
||||
// Parse accepts non-standard strings such as the raw hex encoding
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
|
||||
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
|
||||
// examined in the latter case. Parse should not be used to validate strings as
|
||||
// it parses non-standard encodings as indicated above.
|
||||
func Parse(s string) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(s) {
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36:
|
||||
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
case 36 + 2:
|
||||
s = s[1:]
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
case 32:
|
||||
var ok bool
|
||||
for i := range uuid {
|
||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(s)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(s[x], s[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(b) {
|
||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||
}
|
||||
b = b[9:]
|
||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
b = b[1:]
|
||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
var ok bool
|
||||
for i := 0; i < 32; i += 2 {
|
||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(b)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(b[x], b[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||
func MustParse(s string) UUID {
|
||||
uuid, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||
// does not have a length of 16. The bytes are copied from the slice.
|
||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||
err = uuid.UnmarshalBinary(b)
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// Must returns uuid if err is nil and panics otherwise.
|
||||
func Must(uuid UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
// It returns an error if the format is invalid, otherwise nil.
|
||||
func Validate(s string) error {
|
||||
switch len(s) {
|
||||
// Standard UUID format
|
||||
case 36:
|
||||
|
||||
// UUID with "urn:uuid:" prefix
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// UUID enclosed in braces
|
||||
case 36 + 2:
|
||||
if s[0] != '{' || s[len(s)-1] != '}' {
|
||||
return fmt.Errorf("invalid bracketed UUID format")
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
|
||||
// UUID without hyphens
|
||||
case 32:
|
||||
for i := 0; i < len(s); i += 2 {
|
||||
_, ok := xtob(s[i], s[i+1])
|
||||
if !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return invalidLengthError{len(s)}
|
||||
}
|
||||
|
||||
// Check for standard UUID format
|
||||
if len(s) == 36 {
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
|
||||
if _, ok := xtob(s[x], s[x+1]); !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
var buf [36]byte
|
||||
encodeHex(buf[:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
var buf [36 + 9]byte
|
||||
copy(buf[:], "urn:uuid:")
|
||||
encodeHex(buf[9:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst, uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], uuid[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], uuid[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:], uuid[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the version of uuid.
|
||||
func (uuid UUID) Version() Version {
|
||||
return Version(uuid[6] >> 4)
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
||||
|
||||
// EnableRandPool enables internal randomness pool used for Random
|
||||
// (Version 4) UUID generation. The pool contains random bytes read from
|
||||
// the random number generator on demand in batches. Enabling the pool
|
||||
// may improve the UUID generation throughput significantly.
|
||||
//
|
||||
// Since the pool is stored on the Go heap, this feature may be a bad fit
|
||||
// for security sensitive applications.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func EnableRandPool() {
|
||||
poolEnabled = true
|
||||
}
|
||||
|
||||
// DisableRandPool disables the randomness pool if it was previously
|
||||
// enabled with EnableRandPool.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func DisableRandPool() {
|
||||
poolEnabled = false
|
||||
defer poolMu.Unlock()
|
||||
poolMu.Lock()
|
||||
poolPos = randPoolSize
|
||||
}
|
||||
|
||||
// UUIDs is a slice of UUID types.
|
||||
type UUIDs []UUID
|
||||
|
||||
// Strings returns a string slice containing the string form of each UUID in uuids.
|
||||
func (uuids UUIDs) Strings() []string {
|
||||
var uuidStrs = make([]string, len(uuids))
|
||||
for i, uuid := range uuids {
|
||||
uuidStrs[i] = uuid.String()
|
||||
}
|
||||
return uuidStrs
|
||||
}
|
||||
44
vendor/github.com/google/uuid/version1.go
generated
vendored
44
vendor/github.com/google/uuid/version1.go
generated
vendored
@@ -1,44 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil and an error.
|
||||
//
|
||||
// In most cases, New should be used.
|
||||
func NewUUID() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
timeLow := uint32(now & 0xffffffff)
|
||||
timeMid := uint16((now >> 32) & 0xffff)
|
||||
timeHi := uint16((now >> 48) & 0x0fff)
|
||||
timeHi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
76
vendor/github.com/google/uuid/version4.go
generated
vendored
76
vendor/github.com/google/uuid/version4.go
generated
vendored
@@ -1,76 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "io"
|
||||
|
||||
// New creates a new random UUID or panics. New is equivalent to
|
||||
// the expression
|
||||
//
|
||||
// uuid.Must(uuid.NewRandom())
|
||||
func New() UUID {
|
||||
return Must(NewRandom())
|
||||
}
|
||||
|
||||
// NewString creates a new random UUID and returns it as a string or panics.
|
||||
// NewString is equivalent to the expression
|
||||
//
|
||||
// uuid.New().String()
|
||||
func NewString() string {
|
||||
return Must(NewRandom()).String()
|
||||
}
|
||||
|
||||
// NewRandom returns a Random (Version 4) UUID.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
//
|
||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() (UUID, error) {
|
||||
if !poolEnabled {
|
||||
return NewRandomFromReader(rander)
|
||||
}
|
||||
return newRandomFromPool()
|
||||
}
|
||||
|
||||
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
||||
func NewRandomFromReader(r io.Reader) (UUID, error) {
|
||||
var uuid UUID
|
||||
_, err := io.ReadFull(r, uuid[:])
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func newRandomFromPool() (UUID, error) {
|
||||
var uuid UUID
|
||||
poolMu.Lock()
|
||||
if poolPos == randPoolSize {
|
||||
_, err := io.ReadFull(rander, pool[:])
|
||||
if err != nil {
|
||||
poolMu.Unlock()
|
||||
return Nil, err
|
||||
}
|
||||
poolPos = 0
|
||||
}
|
||||
copy(uuid[:], pool[poolPos:(poolPos+16)])
|
||||
poolPos += 16
|
||||
poolMu.Unlock()
|
||||
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
56
vendor/github.com/google/uuid/version6.go
generated
vendored
56
vendor/github.com/google/uuid/version6.go
generated
vendored
@@ -1,56 +0,0 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
|
||||
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
|
||||
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
|
||||
//
|
||||
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewV6 returns Nil and an error.
|
||||
func NewV6() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_high |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_mid | time_low_and_version |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|clk_seq_hi_res | clk_seq_low | node (0-1) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| node (2-5) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
uuid[6] = 0x60 | (uuid[6] & 0x0F)
|
||||
uuid[8] = 0x80 | (uuid[8] & 0x3F)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
104
vendor/github.com/google/uuid/version7.go
generated
vendored
104
vendor/github.com/google/uuid/version7.go
generated
vendored
@@ -1,104 +0,0 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// UUID version 7 features a time-ordered value field derived from the widely
|
||||
// implemented and well known Unix Epoch timestamp source,
|
||||
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
|
||||
// As well as improved entropy characteristics over versions 1 or 6.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
|
||||
//
|
||||
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
|
||||
//
|
||||
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
// On error, NewV7 returns Nil and an error
|
||||
func NewV7() (UUID, error) {
|
||||
uuid, err := NewRandom()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// it use NewRandomFromReader fill random bits.
|
||||
// On error, NewV7FromReader returns Nil and an error.
|
||||
func NewV7FromReader(r io.Reader) (UUID, error) {
|
||||
uuid, err := NewRandomFromReader(r)
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
|
||||
// uuid[8] already has the right version number (Variant is 10)
|
||||
// see function NewV7 and NewV7FromReader
|
||||
func makeV7(uuid []byte) {
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms | ver | rand_a (12 bit seq) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|var| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
_ = uuid[15] // bounds check
|
||||
|
||||
t, s := getV7Time()
|
||||
|
||||
uuid[0] = byte(t >> 40)
|
||||
uuid[1] = byte(t >> 32)
|
||||
uuid[2] = byte(t >> 24)
|
||||
uuid[3] = byte(t >> 16)
|
||||
uuid[4] = byte(t >> 8)
|
||||
uuid[5] = byte(t)
|
||||
|
||||
uuid[6] = 0x70 | (0x0F & byte(s>>8))
|
||||
uuid[7] = byte(s)
|
||||
}
|
||||
|
||||
// lastV7time is the last time we returned stored as:
|
||||
//
|
||||
// 52 bits of time in milliseconds since epoch
|
||||
// 12 bits of (fractional nanoseconds) >> 8
|
||||
var lastV7time int64
|
||||
|
||||
const nanoPerMilli = 1000000
|
||||
|
||||
// getV7Time returns the time in milliseconds and nanoseconds / 256.
|
||||
// The returned (milli << 12 + seq) is guarenteed to be greater than
|
||||
// (milli << 12 + seq) returned by any previous call to getV7Time.
|
||||
func getV7Time() (milli, seq int64) {
|
||||
timeMu.Lock()
|
||||
defer timeMu.Unlock()
|
||||
|
||||
nano := timeNow().UnixNano()
|
||||
milli = nano / nanoPerMilli
|
||||
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
|
||||
seq = (nano - milli*nanoPerMilli) >> 8
|
||||
now := milli<<12 + seq
|
||||
if now <= lastV7time {
|
||||
now = lastV7time + 1
|
||||
milli = now >> 12
|
||||
seq = now & 0xfff
|
||||
}
|
||||
lastV7time = now
|
||||
return milli, seq
|
||||
}
|
||||
Reference in New Issue
Block a user