Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
14a62aa3f7 | ||
![]() |
9e62844f82 | ||
![]() |
aa374638bf | ||
![]() |
d9deca7e18 | ||
![]() |
417d6d51e6 | ||
![]() |
54c186c94e | ||
![]() |
b61a3c137a | ||
![]() |
e287c7d9b3 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
examples/
|
@@ -1,3 +1,3 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- "1.12.5"
|
- "1.14"
|
||||||
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Artem Piskun
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
17
README.md
17
README.md
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
go-discogs is a Go client library for the [Discogs API](https://www.discogs.com/developers/). Check the usage section to see how to access the Discogs API.
|
go-discogs is a Go client library for the [Discogs API](https://www.discogs.com/developers/). Check the usage section to see how to access the Discogs API.
|
||||||
|
|
||||||
|
The lib is under MIT but be sure you are familiar with [Discogs API Terms of Use](https://support.discogs.com/hc/en-us/articles/360009334593-API-Terms-of-Use).
|
||||||
|
|
||||||
### Feauteres
|
### Feauteres
|
||||||
* Database
|
* Database
|
||||||
* [Releases](#releases)
|
* [Releases](#releases)
|
||||||
@@ -18,7 +20,7 @@ go-discogs is a Go client library for the [Discogs API](https://www.discogs.com/
|
|||||||
|
|
||||||
Install
|
Install
|
||||||
--------
|
--------
|
||||||
go get -u github.com/irlndts/go-discogs
|
go get github.com/irlndts/go-discogs
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
---------
|
---------
|
||||||
@@ -29,18 +31,19 @@ import "github.com/irlndts/go-discogs"
|
|||||||
```
|
```
|
||||||
|
|
||||||
Some requests require authentification (as any user). According to [Discogs](https://www.discogs.com/developers/#page:authentication,header:authentication-discogs-auth-flow), to send requests with Discogs Auth, you have two options: sending your credentials in the query string with key and secret parameters or a [token parameter](https://www.discogs.com/settings/developers).
|
Some requests require authentification (as any user). According to [Discogs](https://www.discogs.com/developers/#page:authentication,header:authentication-discogs-auth-flow), to send requests with Discogs Auth, you have two options: sending your credentials in the query string with key and secret parameters or a [token parameter](https://www.discogs.com/settings/developers).
|
||||||
This is token way example:
|
|
||||||
```go
|
```go
|
||||||
client, err := discogs.NewClient(&discogs.Options{
|
client, err := discogs.New(&discogs.Options{
|
||||||
UserAgent: "Some Name",
|
UserAgent: "Some Name",
|
||||||
Currency: "EUR", // optional, "USD" (default), "GBP", "EUR", "CAD", "AUD", "JPY", "CHF", "MXN", "BRL", "NZD", "SEK", "ZAR" are allowed
|
Currency: "EUR", // optional, "USD" (default), "GBP", "EUR", "CAD", "AUD", "JPY", "CHF", "MXN", "BRL", "NZD", "SEK", "ZAR" are allowed
|
||||||
Token: "Some Token", // optional
|
Token: "Some Token", // optional
|
||||||
|
URL: "https://api.discogs.com", // optional
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Releases
|
#### Releases
|
||||||
```go
|
```go
|
||||||
release, _ := client.Database.Release(9893847)
|
release, _ := client.Release(9893847)
|
||||||
fmt.Println(release.Artists[0].Name, " - ", release.Title)
|
fmt.Println(release.Artists[0].Name, " - ", release.Title)
|
||||||
// St. Petersburg Ska-Jazz Review - Elephant Riddim
|
// St. Petersburg Ska-Jazz Review - Elephant Riddim
|
||||||
```
|
```
|
||||||
@@ -72,18 +75,16 @@ type SearchRequest struct {
|
|||||||
Contributer string // search contributor usernames (optional)
|
Contributer string // search contributor usernames (optional)
|
||||||
|
|
||||||
Page int // optional
|
Page int // optional
|
||||||
PerPage int // optional
|
PerPage int // optional
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Example
|
Example
|
||||||
```go
|
```go
|
||||||
request := discogs.SearchRequest{Artist: "reggaenauts", ReleaseTitle: "river rock", Page: 0, PerPage: 1}
|
request := discogs.SearchRequest{Artist: "reggaenauts", ReleaseTitle: "river rock", Page: 0, PerPage: 1}
|
||||||
search, _ := client.Search.Search(request)
|
search, _ := client.Search(request)
|
||||||
|
|
||||||
for _, r := range search.Results {
|
for _, r := range search.Results {
|
||||||
fmt.Println(r.Title)
|
fmt.Println(r.Title)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
etc.
|
|
||||||
|
57
database.go
57
database.go
@@ -12,20 +12,39 @@ const (
|
|||||||
mastersURI = "/masters/"
|
mastersURI = "/masters/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DatabaseService ...
|
// DatabaseService is an interface to work with database.
|
||||||
type DatabaseService struct {
|
type DatabaseService interface {
|
||||||
|
// Artist represents a person in the discogs database.
|
||||||
|
Artist(artistID int) (*Artist, error)
|
||||||
|
// ArtistReleases returns a list of releases and masters associated with the artist.
|
||||||
|
ArtistReleases(artistID int, pagination *Pagination) (*ArtistReleases, error)
|
||||||
|
// Label returns a label.
|
||||||
|
Label(labelID int) (*Label, error)
|
||||||
|
// LabelReleases returns a list of Releases associated with the label.
|
||||||
|
LabelReleases(labelID int, pagination *Pagination) (*LabelReleases, error)
|
||||||
|
// Master returns a master release.
|
||||||
|
Master(masterID int) (*Master, error)
|
||||||
|
// MasterVersions retrieves a list of all Releases that are versions of this master.
|
||||||
|
MasterVersions(masterID int, pagination *Pagination) (*MasterVersions, error)
|
||||||
|
// Release returns release by release's ID.
|
||||||
|
Release(releaseID int) (*Release, error)
|
||||||
|
// ReleaseRating retruns community release rating.
|
||||||
|
ReleaseRating(releaseID int) (*ReleaseRating, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type databaseService struct {
|
||||||
url string
|
url string
|
||||||
currency string
|
currency string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDatabaseService(url string, currency string) *DatabaseService {
|
func newDatabaseService(url string, currency string) DatabaseService {
|
||||||
return &DatabaseService{
|
return &databaseService{
|
||||||
url: url,
|
url: url,
|
||||||
currency: currency,
|
currency: currency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release serves relesase response from discogs
|
// Release serves relesase response from discogs.
|
||||||
type Release struct {
|
type Release struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
@@ -54,7 +73,7 @@ type Release struct {
|
|||||||
Released string `json:"released"`
|
Released string `json:"released"`
|
||||||
ReleasedFormatted string `json:"released_formatted"`
|
ReleasedFormatted string `json:"released_formatted"`
|
||||||
ResourceURL string `json:"resource_url"`
|
ResourceURL string `json:"resource_url"`
|
||||||
Series []string `json:"series"`
|
Series []Series `json:"series"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Styles []string `json:"styles"`
|
Styles []string `json:"styles"`
|
||||||
Tracklist []Track `json:"tracklist"`
|
Tracklist []Track `json:"tracklist"`
|
||||||
@@ -63,8 +82,7 @@ type Release struct {
|
|||||||
Year int `json:"year"`
|
Year int `json:"year"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release returns release by release's ID
|
func (s *databaseService) Release(releaseID int) (*Release, error) {
|
||||||
func (s *DatabaseService) Release(releaseID int) (*Release, error) {
|
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("curr_abbr", s.currency)
|
params.Set("curr_abbr", s.currency)
|
||||||
|
|
||||||
@@ -73,14 +91,13 @@ func (s *DatabaseService) Release(releaseID int) (*Release, error) {
|
|||||||
return release, err
|
return release, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseRating serves response for community release rating request
|
// ReleaseRating serves response for community release rating request.
|
||||||
type ReleaseRating struct {
|
type ReleaseRating struct {
|
||||||
ID int `json:"release_id"`
|
ID int `json:"release_id"`
|
||||||
Rating Rating `json:"rating"`
|
Rating Rating `json:"rating"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseRating retruns community release rating
|
func (s *databaseService) ReleaseRating(releaseID int) (*ReleaseRating, error) {
|
||||||
func (s *DatabaseService) ReleaseRating(releaseID int) (*ReleaseRating, error) {
|
|
||||||
var rating *ReleaseRating
|
var rating *ReleaseRating
|
||||||
err := request(s.url+releasesURI+strconv.Itoa(releaseID)+"/rating", nil, &rating)
|
err := request(s.url+releasesURI+strconv.Itoa(releaseID)+"/rating", nil, &rating)
|
||||||
return rating, err
|
return rating, err
|
||||||
@@ -105,8 +122,7 @@ type Artist struct {
|
|||||||
DataQuality string `json:"data_quality"`
|
DataQuality string `json:"data_quality"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Artist represents a person in the discogs database
|
func (s *databaseService) Artist(artistID int) (*Artist, error) {
|
||||||
func (s *DatabaseService) Artist(artistID int) (*Artist, error) {
|
|
||||||
var artist *Artist
|
var artist *Artist
|
||||||
err := request(s.url+artistsURI+strconv.Itoa(artistID), nil, &artist)
|
err := request(s.url+artistsURI+strconv.Itoa(artistID), nil, &artist)
|
||||||
return artist, err
|
return artist, err
|
||||||
@@ -118,8 +134,7 @@ type ArtistReleases struct {
|
|||||||
Releases []ReleaseSource `json:"releases"`
|
Releases []ReleaseSource `json:"releases"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArtistReleases returns a list of releases and masters associated with the artist.
|
func (s *databaseService) ArtistReleases(artistID int, pagination *Pagination) (*ArtistReleases, error) {
|
||||||
func (s *DatabaseService) ArtistReleases(artistID int, pagination *Pagination) (*ArtistReleases, error) {
|
|
||||||
var releases *ArtistReleases
|
var releases *ArtistReleases
|
||||||
err := request(s.url+artistsURI+strconv.Itoa(artistID)+"/releases", pagination.params(), &releases)
|
err := request(s.url+artistsURI+strconv.Itoa(artistID)+"/releases", pagination.params(), &releases)
|
||||||
return releases, err
|
return releases, err
|
||||||
@@ -141,8 +156,7 @@ type Label struct {
|
|||||||
DataQuality string `json:"data_quality"`
|
DataQuality string `json:"data_quality"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label returns a label.
|
func (s *databaseService) Label(labelID int) (*Label, error) {
|
||||||
func (s *DatabaseService) Label(labelID int) (*Label, error) {
|
|
||||||
var label *Label
|
var label *Label
|
||||||
err := request(s.url+labelsURI+strconv.Itoa(labelID), nil, &label)
|
err := request(s.url+labelsURI+strconv.Itoa(labelID), nil, &label)
|
||||||
return label, err
|
return label, err
|
||||||
@@ -154,8 +168,7 @@ type LabelReleases struct {
|
|||||||
Releases []ReleaseSource `json:"releases"`
|
Releases []ReleaseSource `json:"releases"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelReleases returns a list of Releases associated with the label.
|
func (s *databaseService) LabelReleases(labelID int, pagination *Pagination) (*LabelReleases, error) {
|
||||||
func (s *DatabaseService) LabelReleases(labelID int, pagination *Pagination) (*LabelReleases, error) {
|
|
||||||
var releases *LabelReleases
|
var releases *LabelReleases
|
||||||
err := request(s.url+labelsURI+strconv.Itoa(labelID)+"/releases", pagination.params(), &releases)
|
err := request(s.url+labelsURI+strconv.Itoa(labelID)+"/releases", pagination.params(), &releases)
|
||||||
return releases, err
|
return releases, err
|
||||||
@@ -187,8 +200,7 @@ type Master struct {
|
|||||||
DataQuality string `json:"data_quality"`
|
DataQuality string `json:"data_quality"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Master returns a master release
|
func (s *databaseService) Master(masterID int) (*Master, error) {
|
||||||
func (s *DatabaseService) Master(masterID int) (*Master, error) {
|
|
||||||
var master *Master
|
var master *Master
|
||||||
err := request(s.url+mastersURI+strconv.Itoa(masterID), nil, &master)
|
err := request(s.url+mastersURI+strconv.Itoa(masterID), nil, &master)
|
||||||
return master, err
|
return master, err
|
||||||
@@ -200,8 +212,7 @@ type MasterVersions struct {
|
|||||||
Versions []Version `json:"versions"`
|
Versions []Version `json:"versions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MasterVersions retrieves a list of all Releases that are versions of this master
|
func (s *databaseService) MasterVersions(masterID int, pagination *Pagination) (*MasterVersions, error) {
|
||||||
func (s *DatabaseService) MasterVersions(masterID int, pagination *Pagination) (*MasterVersions, error) {
|
|
||||||
var versions *MasterVersions
|
var versions *MasterVersions
|
||||||
err := request(s.url+mastersURI+strconv.Itoa(masterID)+"/versions", pagination.params(), &versions)
|
err := request(s.url+mastersURI+strconv.Itoa(masterID)+"/versions", pagination.params(), &versions)
|
||||||
return versions, err
|
return versions, err
|
||||||
|
@@ -60,7 +60,7 @@ func TestDatabaseServiceRelease(t *testing.T) {
|
|||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
d := initDiscogsClient(t, &Options{URL: ts.URL})
|
d := initDiscogsClient(t, &Options{URL: ts.URL})
|
||||||
release, err := d.Database.Release(8138518)
|
release, err := d.Release(8138518)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get release: %s", err)
|
t.Fatalf("failed to get release: %s", err)
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ func TestDatabaseServiceMaster(t *testing.T) {
|
|||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
d := initDiscogsClient(t, &Options{URL: ts.URL})
|
d := initDiscogsClient(t, &Options{URL: ts.URL})
|
||||||
master, err := d.Database.Master(718441)
|
master, err := d.Master(718441)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get master: %s", err)
|
t.Fatalf("failed to get master: %s", err)
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ func TestDatabaseServiceArtist(t *testing.T) {
|
|||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
d := initDiscogsClient(t, &Options{URL: ts.URL})
|
d := initDiscogsClient(t, &Options{URL: ts.URL})
|
||||||
artist, err := d.Database.Artist(38661)
|
artist, err := d.Artist(38661)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to get master: %s", err)
|
t.Fatalf("failed to get master: %s", err)
|
||||||
}
|
}
|
||||||
|
33
discogs.go
33
discogs.go
@@ -14,22 +14,31 @@ const (
|
|||||||
|
|
||||||
// Options is a set of options to use discogs API client
|
// Options is a set of options to use discogs API client
|
||||||
type Options struct {
|
type Options struct {
|
||||||
URL string
|
// Discogs API endpoint (optional).
|
||||||
Currency string
|
URL string
|
||||||
|
// Currency to use (optional, default is USD).
|
||||||
|
Currency string
|
||||||
|
// UserAgent to to call discogs api with.
|
||||||
UserAgent string
|
UserAgent string
|
||||||
Token string
|
// Token provided by discogs (optional).
|
||||||
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a Discogs client for making Discogs API requests.
|
// Discogs is an interface for making Discogs API requests.
|
||||||
type Client struct {
|
type Discogs interface {
|
||||||
Database *DatabaseService
|
DatabaseService
|
||||||
Search *SearchService
|
SearchService
|
||||||
|
}
|
||||||
|
|
||||||
|
type discogs struct {
|
||||||
|
DatabaseService
|
||||||
|
SearchService
|
||||||
}
|
}
|
||||||
|
|
||||||
var header *http.Header
|
var header *http.Header
|
||||||
|
|
||||||
// NewClient returns a new Client.
|
// New returns a new discogs API client.
|
||||||
func NewClient(o *Options) (*Client, error) {
|
func New(o *Options) (Discogs, error) {
|
||||||
header = &http.Header{}
|
header = &http.Header{}
|
||||||
|
|
||||||
if o == nil || o.UserAgent == "" {
|
if o == nil || o.UserAgent == "" {
|
||||||
@@ -52,9 +61,9 @@ func NewClient(o *Options) (*Client, error) {
|
|||||||
o.URL = discogsAPI
|
o.URL = discogsAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return discogs{
|
||||||
Database: newDatabaseService(o.URL, cur),
|
newDatabaseService(o.URL, cur),
|
||||||
Search: newSearchService(o.URL + "/database/search"),
|
newSearchService(o.URL + "/database/search"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,11 +5,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testUserAgent = "UnitTestClient/0.0.2 +https://github.com/irlndts/go-discogs"
|
testUserAgent = "UnitTestClient/0.0.2"
|
||||||
testToken = ""
|
testToken = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
func initDiscogsClient(t *testing.T, options *Options) *Client {
|
func initDiscogsClient(t *testing.T, options *Options) Discogs {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = &Options{
|
options = &Options{
|
||||||
UserAgent: testUserAgent,
|
UserAgent: testUserAgent,
|
||||||
@@ -22,7 +22,7 @@ func initDiscogsClient(t *testing.T, options *Options) *Client {
|
|||||||
options.UserAgent = testUserAgent
|
options.UserAgent = testUserAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := NewClient(options)
|
client, err := New(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create client: %s", err)
|
t.Fatalf("failed to create client: %s", err)
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ func initDiscogsClient(t *testing.T, options *Options) *Client {
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
options *Options
|
options *Options
|
||||||
err error
|
err error
|
||||||
@@ -53,7 +53,7 @@ func TestNewClient(t *testing.T) {
|
|||||||
for name := range tests {
|
for name := range tests {
|
||||||
tt := tests[name]
|
tt := tests[name]
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
if _, err := NewClient(tt.options); err != tt.err {
|
if _, err := New(tt.options); err != tt.err {
|
||||||
t.Errorf("err got=%s; want=%s", err, tt.err)
|
t.Errorf("err got=%s; want=%s", err, tt.err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -66,19 +66,19 @@ func TestCurrency(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{currency: "", want: "USD", err: nil},
|
{currency: "", want: "USD"},
|
||||||
{currency: "USD", want: "USD", err: nil},
|
{currency: "USD", want: "USD"},
|
||||||
{currency: "GBP", want: "GBP", err: nil},
|
{currency: "GBP", want: "GBP"},
|
||||||
{currency: "EUR", want: "EUR", err: nil},
|
{currency: "EUR", want: "EUR"},
|
||||||
{currency: "CAD", want: "CAD", err: nil},
|
{currency: "CAD", want: "CAD"},
|
||||||
{currency: "AUD", want: "AUD", err: nil},
|
{currency: "AUD", want: "AUD"},
|
||||||
{currency: "JPY", want: "JPY", err: nil},
|
{currency: "JPY", want: "JPY"},
|
||||||
{currency: "CHF", want: "CHF", err: nil},
|
{currency: "CHF", want: "CHF"},
|
||||||
{currency: "MXN", want: "MXN", err: nil},
|
{currency: "MXN", want: "MXN"},
|
||||||
{currency: "BRL", want: "BRL", err: nil},
|
{currency: "BRL", want: "BRL"},
|
||||||
{currency: "NZD", want: "NZD", err: nil},
|
{currency: "NZD", want: "NZD"},
|
||||||
{currency: "SEK", want: "SEK", err: nil},
|
{currency: "SEK", want: "SEK"},
|
||||||
{currency: "ZAR", want: "ZAR", err: nil},
|
{currency: "ZAR", want: "ZAR"},
|
||||||
{currency: "RUR", want: "", err: ErrCurrencyNotSupported},
|
{currency: "RUR", want: "", err: ErrCurrencyNotSupported},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
21
doc.go
Normal file
21
doc.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Package discogs is a Go client library for the Discogs API.
|
||||||
|
|
||||||
|
The discogs package provides a client for accessing the Discogs API.
|
||||||
|
First of all import library and init client variable.
|
||||||
|
According to discogs api documentation you must provide your user-agent.
|
||||||
|
|
||||||
|
Some requests require authentification (as any user).
|
||||||
|
According to Discogs, to send requests with Discogs Auth, you have two options:
|
||||||
|
sending your credentials in the query string with key and secret parameters or
|
||||||
|
a token parameter. This is token way example:
|
||||||
|
|
||||||
|
client, err := discogs.New(&discogs.Options{
|
||||||
|
UserAgent: "Some Name",
|
||||||
|
Currency: "EUR", // optional, "USD" (default), "GBP", "EUR", "CAD", "AUD", "JPY", "CHF", "MXN", "BRL", "NZD", "SEK", "ZAR" are allowed
|
||||||
|
Token: "Some Token", // optional
|
||||||
|
URL: "https://api.discogs.com", // optional
|
||||||
|
})
|
||||||
|
|
||||||
|
*/
|
||||||
|
package discogs
|
@@ -1,26 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/irlndts/go-discogs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
d, err := discogs.NewClient(&discogs.Options{
|
|
||||||
UserAgent: "TestDiscogsClient/0.0.1 +http://example.com",
|
|
||||||
Currency: "USD",
|
|
||||||
Token: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
master, err := d.Database.Master(718441)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("%+v\n", master)
|
|
||||||
}
|
|
4
go.mod
4
go.mod
@@ -1,5 +1,5 @@
|
|||||||
module github.com/irlndts/go-discogs
|
module github.com/irlndts/go-discogs
|
||||||
|
|
||||||
go 1.13.4
|
go 1.14
|
||||||
|
|
||||||
require github.com/google/go-cmp v0.3.1
|
require github.com/google/go-cmp v0.4.1
|
||||||
|
6
go.sum
6
go.sum
@@ -1,2 +1,4 @@
|
|||||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
16
models.go
16
models.go
@@ -14,6 +14,17 @@ type Video struct {
|
|||||||
URI string `json:"uri"`
|
URI string `json:"uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Series ...
|
||||||
|
type Series struct {
|
||||||
|
Catno string `json:"catno"`
|
||||||
|
EntityType string `json:"entity_type"`
|
||||||
|
EntityTypeName string `json:"entity_type_name"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ResourceURL string `json:"resource_url"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// ArtistSource ...
|
// ArtistSource ...
|
||||||
type ArtistSource struct {
|
type ArtistSource struct {
|
||||||
Anv string `json:"anv"`
|
Anv string `json:"anv"`
|
||||||
@@ -57,8 +68,9 @@ type LabelSource struct {
|
|||||||
|
|
||||||
// Identifier ...
|
// Identifier ...
|
||||||
type Identifier struct {
|
type Identifier struct {
|
||||||
Type string `json:"type"`
|
Description string `json:"description,omitempty"`
|
||||||
Value string `json:"value"`
|
Type string `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format ...
|
// Format ...
|
||||||
|
23
search.go
23
search.go
@@ -5,13 +5,22 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SearchService ...
|
// SearchService is an interface to work with search.
|
||||||
type SearchService struct {
|
type SearchService interface {
|
||||||
|
// Search makes search request to discogs.
|
||||||
|
// Issue a search query to database. This endpoint accepts pagination parameters.
|
||||||
|
// Authentication (as any user) is required.
|
||||||
|
// https://www.discogs.com/developers/#page:database,header:database-search
|
||||||
|
Search(req SearchRequest) (*Search, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchService ...
|
||||||
|
type searchService struct {
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSearchService(url string) *SearchService {
|
func newSearchService(url string) SearchService {
|
||||||
return &SearchService{
|
return &searchService{
|
||||||
url: url,
|
url: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,11 +145,7 @@ type Result struct {
|
|||||||
MasterID int `json:"master_id,omitempty"`
|
MasterID int `json:"master_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search makes search request to discogs.
|
func (s *searchService) Search(req SearchRequest) (*Search, error) {
|
||||||
// Issue a search query to our database. This endpoint accepts pagination parameters.
|
|
||||||
// Authentication (as any user) is required.
|
|
||||||
// https://www.discogs.com/developers/#page:database,header:database-search
|
|
||||||
func (s *SearchService) Search(req SearchRequest) (*Search, error) {
|
|
||||||
var search *Search
|
var search *Search
|
||||||
err := request(s.url, req.params(), &search)
|
err := request(s.url, req.params(), &search)
|
||||||
return search, err
|
return search, err
|
||||||
|
File diff suppressed because one or more lines are too long
87
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
87
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
@@ -6,6 +6,10 @@
|
|||||||
//
|
//
|
||||||
// This package is intended to be a more powerful and safer alternative to
|
// This package is intended to be a more powerful and safer alternative to
|
||||||
// reflect.DeepEqual for comparing whether two values are semantically equal.
|
// reflect.DeepEqual for comparing whether two values are semantically equal.
|
||||||
|
// It is intended to only be used in tests, as performance is not a goal and
|
||||||
|
// it may panic if it cannot compare the values. Its propensity towards
|
||||||
|
// panicking means that its unsuitable for production environments where a
|
||||||
|
// spurious panic may be fatal.
|
||||||
//
|
//
|
||||||
// The primary features of cmp are:
|
// The primary features of cmp are:
|
||||||
//
|
//
|
||||||
@@ -22,8 +26,8 @@
|
|||||||
// equality is determined by recursively comparing the primitive kinds on both
|
// equality is determined by recursively comparing the primitive kinds on both
|
||||||
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
|
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
|
||||||
// fields are not compared by default; they result in panics unless suppressed
|
// fields are not compared by default; they result in panics unless suppressed
|
||||||
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
|
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly
|
||||||
// using the AllowUnexported option.
|
// compared using the Exporter option.
|
||||||
package cmp
|
package cmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -62,8 +66,8 @@ import (
|
|||||||
//
|
//
|
||||||
// Structs are equal if recursively calling Equal on all fields report equal.
|
// Structs are equal if recursively calling Equal on all fields report equal.
|
||||||
// If a struct contains unexported fields, Equal panics unless an Ignore option
|
// If a struct contains unexported fields, Equal panics unless an Ignore option
|
||||||
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the AllowUnexported
|
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option
|
||||||
// option explicitly permits comparing the unexported field.
|
// explicitly permits comparing the unexported field.
|
||||||
//
|
//
|
||||||
// Slices are equal if they are both nil or both non-nil, where recursively
|
// Slices are equal if they are both nil or both non-nil, where recursively
|
||||||
// calling Equal on all non-ignored slice or array elements report equal.
|
// calling Equal on all non-ignored slice or array elements report equal.
|
||||||
@@ -80,6 +84,11 @@ import (
|
|||||||
// Pointers and interfaces are equal if they are both nil or both non-nil,
|
// Pointers and interfaces are equal if they are both nil or both non-nil,
|
||||||
// where they have the same underlying concrete type and recursively
|
// where they have the same underlying concrete type and recursively
|
||||||
// calling Equal on the underlying values reports equal.
|
// calling Equal on the underlying values reports equal.
|
||||||
|
//
|
||||||
|
// Before recursing into a pointer, slice element, or map, the current path
|
||||||
|
// is checked to detect whether the address has already been visited.
|
||||||
|
// If there is a cycle, then the pointed at values are considered equal
|
||||||
|
// only if both addresses were previously visited in the same path step.
|
||||||
func Equal(x, y interface{}, opts ...Option) bool {
|
func Equal(x, y interface{}, opts ...Option) bool {
|
||||||
vx := reflect.ValueOf(x)
|
vx := reflect.ValueOf(x)
|
||||||
vy := reflect.ValueOf(y)
|
vy := reflect.ValueOf(y)
|
||||||
@@ -137,6 +146,7 @@ type state struct {
|
|||||||
// Calling statelessCompare must not result in observable changes to these.
|
// Calling statelessCompare must not result in observable changes to these.
|
||||||
result diff.Result // The current result of comparison
|
result diff.Result // The current result of comparison
|
||||||
curPath Path // The current path in the value tree
|
curPath Path // The current path in the value tree
|
||||||
|
curPtrs pointerPath // The current set of visited pointers
|
||||||
reporters []reporter // Optional reporters
|
reporters []reporter // Optional reporters
|
||||||
|
|
||||||
// recChecker checks for infinite cycles applying the same set of
|
// recChecker checks for infinite cycles applying the same set of
|
||||||
@@ -148,13 +158,14 @@ type state struct {
|
|||||||
dynChecker dynChecker
|
dynChecker dynChecker
|
||||||
|
|
||||||
// These fields, once set by processOption, will not change.
|
// These fields, once set by processOption, will not change.
|
||||||
exporters map[reflect.Type]bool // Set of structs with unexported field visibility
|
exporters []exporter // List of exporters for structs with unexported fields
|
||||||
opts Options // List of all fundamental and filter options
|
opts Options // List of all fundamental and filter options
|
||||||
}
|
}
|
||||||
|
|
||||||
func newState(opts []Option) *state {
|
func newState(opts []Option) *state {
|
||||||
// Always ensure a validator option exists to validate the inputs.
|
// Always ensure a validator option exists to validate the inputs.
|
||||||
s := &state{opts: Options{validator{}}}
|
s := &state{opts: Options{validator{}}}
|
||||||
|
s.curPtrs.Init()
|
||||||
s.processOption(Options(opts))
|
s.processOption(Options(opts))
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -174,13 +185,8 @@ func (s *state) processOption(opt Option) {
|
|||||||
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
||||||
}
|
}
|
||||||
s.opts = append(s.opts, opt)
|
s.opts = append(s.opts, opt)
|
||||||
case visibleStructs:
|
case exporter:
|
||||||
if s.exporters == nil {
|
s.exporters = append(s.exporters, opt)
|
||||||
s.exporters = make(map[reflect.Type]bool)
|
|
||||||
}
|
|
||||||
for t := range opt {
|
|
||||||
s.exporters[t] = true
|
|
||||||
}
|
|
||||||
case reporter:
|
case reporter:
|
||||||
s.reporters = append(s.reporters, opt)
|
s.reporters = append(s.reporters, opt)
|
||||||
default:
|
default:
|
||||||
@@ -192,9 +198,9 @@ func (s *state) processOption(opt Option) {
|
|||||||
// This function is stateless in that it does not alter the current result,
|
// This function is stateless in that it does not alter the current result,
|
||||||
// or output to any registered reporters.
|
// or output to any registered reporters.
|
||||||
func (s *state) statelessCompare(step PathStep) diff.Result {
|
func (s *state) statelessCompare(step PathStep) diff.Result {
|
||||||
// We do not save and restore the curPath because all of the compareX
|
// We do not save and restore curPath and curPtrs because all of the
|
||||||
// methods should properly push and pop from the path.
|
// compareX methods should properly push and pop from them.
|
||||||
// It is an implementation bug if the contents of curPath differs from
|
// It is an implementation bug if the contents of the paths differ from
|
||||||
// when calling this function to when returning from it.
|
// when calling this function to when returning from it.
|
||||||
|
|
||||||
oldResult, oldReporters := s.result, s.reporters
|
oldResult, oldReporters := s.result, s.reporters
|
||||||
@@ -216,9 +222,17 @@ func (s *state) compareAny(step PathStep) {
|
|||||||
}
|
}
|
||||||
s.recChecker.Check(s.curPath)
|
s.recChecker.Check(s.curPath)
|
||||||
|
|
||||||
// Obtain the current type and values.
|
// Cycle-detection for slice elements (see NOTE in compareSlice).
|
||||||
t := step.Type()
|
t := step.Type()
|
||||||
vx, vy := step.Values()
|
vx, vy := step.Values()
|
||||||
|
if si, ok := step.(SliceIndex); ok && si.isSlice && vx.IsValid() && vy.IsValid() {
|
||||||
|
px, py := vx.Addr(), vy.Addr()
|
||||||
|
if eq, visited := s.curPtrs.Push(px, py); visited {
|
||||||
|
s.report(eq, reportByCycle)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.curPtrs.Pop(px, py)
|
||||||
|
}
|
||||||
|
|
||||||
// Rule 1: Check whether an option applies on this node in the value tree.
|
// Rule 1: Check whether an option applies on this node in the value tree.
|
||||||
if s.tryOptions(t, vx, vy) {
|
if s.tryOptions(t, vx, vy) {
|
||||||
@@ -354,6 +368,7 @@ func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
|
|||||||
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
||||||
var vax, vay reflect.Value // Addressable versions of vx and vy
|
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||||
|
|
||||||
|
var mayForce, mayForceInit bool
|
||||||
step := StructField{&structField{}}
|
step := StructField{&structField{}}
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
step.typ = t.Field(i).Type
|
step.typ = t.Field(i).Type
|
||||||
@@ -375,7 +390,13 @@ func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
|||||||
vax = makeAddressable(vx)
|
vax = makeAddressable(vx)
|
||||||
vay = makeAddressable(vy)
|
vay = makeAddressable(vy)
|
||||||
}
|
}
|
||||||
step.mayForce = s.exporters[t]
|
if !mayForceInit {
|
||||||
|
for _, xf := range s.exporters {
|
||||||
|
mayForce = mayForce || xf(t)
|
||||||
|
}
|
||||||
|
mayForceInit = true
|
||||||
|
}
|
||||||
|
step.mayForce = mayForce
|
||||||
step.pvx = vax
|
step.pvx = vax
|
||||||
step.pvy = vay
|
step.pvy = vay
|
||||||
step.field = t.Field(i)
|
step.field = t.Field(i)
|
||||||
@@ -391,9 +412,21 @@ func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support cyclic data structures.
|
// NOTE: It is incorrect to call curPtrs.Push on the slice header pointer
|
||||||
|
// since slices represents a list of pointers, rather than a single pointer.
|
||||||
|
// The pointer checking logic must be handled on a per-element basis
|
||||||
|
// in compareAny.
|
||||||
|
//
|
||||||
|
// A slice header (see reflect.SliceHeader) in Go is a tuple of a starting
|
||||||
|
// pointer P, a length N, and a capacity C. Supposing each slice element has
|
||||||
|
// a memory size of M, then the slice is equivalent to the list of pointers:
|
||||||
|
// [P+i*M for i in range(N)]
|
||||||
|
//
|
||||||
|
// For example, v[:0] and v[:1] are slices with the same starting pointer,
|
||||||
|
// but they are clearly different values. Using the slice pointer alone
|
||||||
|
// violates the assumption that equal pointers implies equal values.
|
||||||
|
|
||||||
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}}}
|
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}, isSlice: isSlice}}
|
||||||
withIndexes := func(ix, iy int) SliceIndex {
|
withIndexes := func(ix, iy int) SliceIndex {
|
||||||
if ix >= 0 {
|
if ix >= 0 {
|
||||||
step.vx, step.xkey = vx.Index(ix), ix
|
step.vx, step.xkey = vx.Index(ix), ix
|
||||||
@@ -470,7 +503,12 @@ func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support cyclic data structures.
|
// Cycle-detection for maps.
|
||||||
|
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||||
|
s.report(eq, reportByCycle)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.curPtrs.Pop(vx, vy)
|
||||||
|
|
||||||
// We combine and sort the two map keys so that we can perform the
|
// We combine and sort the two map keys so that we can perform the
|
||||||
// comparisons in a deterministic order.
|
// comparisons in a deterministic order.
|
||||||
@@ -507,7 +545,12 @@ func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support cyclic data structures.
|
// Cycle-detection for pointers.
|
||||||
|
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||||
|
s.report(eq, reportByCycle)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.curPtrs.Pop(vx, vy)
|
||||||
|
|
||||||
vx, vy = vx.Elem(), vy.Elem()
|
vx, vy = vx.Elem(), vy.Elem()
|
||||||
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
|
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
|
||||||
|
4
vendor/github.com/google/go-cmp/cmp/export_panic.go
generated
vendored
4
vendor/github.com/google/go-cmp/cmp/export_panic.go
generated
vendored
@@ -8,8 +8,8 @@ package cmp
|
|||||||
|
|
||||||
import "reflect"
|
import "reflect"
|
||||||
|
|
||||||
const supportAllowUnexported = false
|
const supportExporters = false
|
||||||
|
|
||||||
func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value {
|
func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value {
|
||||||
panic("retrieveUnexportedField is not implemented")
|
panic("no support for forcibly accessing unexported fields")
|
||||||
}
|
}
|
||||||
|
6
vendor/github.com/google/go-cmp/cmp/export_unsafe.go
generated
vendored
6
vendor/github.com/google/go-cmp/cmp/export_unsafe.go
generated
vendored
@@ -11,7 +11,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const supportAllowUnexported = true
|
const supportExporters = true
|
||||||
|
|
||||||
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
|
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
|
||||||
// a struct such that the value has read-write permissions.
|
// a struct such that the value has read-write permissions.
|
||||||
@@ -19,5 +19,7 @@ const supportAllowUnexported = true
|
|||||||
// The parent struct, v, must be addressable, while f must be a StructField
|
// The parent struct, v, must be addressable, while f must be a StructField
|
||||||
// describing the field to retrieve.
|
// describing the field to retrieve.
|
||||||
func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value {
|
func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value {
|
||||||
return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem()
|
// See https://github.com/google/go-cmp/issues/167 for discussion of the
|
||||||
|
// following expression.
|
||||||
|
return reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||||
}
|
}
|
||||||
|
55
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
55
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
@@ -225,8 +225,20 @@ func (validator) apply(s *state, vx, vy reflect.Value) {
|
|||||||
|
|
||||||
// Unable to Interface implies unexported field without visibility access.
|
// Unable to Interface implies unexported field without visibility access.
|
||||||
if !vx.CanInterface() || !vy.CanInterface() {
|
if !vx.CanInterface() || !vy.CanInterface() {
|
||||||
const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider AllowUnexported or cmpopts.IgnoreUnexported"
|
const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
|
||||||
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
|
var name string
|
||||||
|
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
|
||||||
|
// Named type with unexported fields.
|
||||||
|
name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
|
||||||
|
} else {
|
||||||
|
// Unnamed type with unexported fields. Derive PkgPath from field.
|
||||||
|
var pkgPath string
|
||||||
|
for i := 0; i < t.NumField() && pkgPath == ""; i++ {
|
||||||
|
pkgPath = t.Field(i).PkgPath
|
||||||
|
}
|
||||||
|
name = fmt.Sprintf("%q.(%v)", pkgPath, t.String()) // e.g., "path/to/package".(struct { a int })
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help))
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("not reachable")
|
panic("not reachable")
|
||||||
@@ -360,9 +372,8 @@ func (cm comparer) String() string {
|
|||||||
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
|
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowUnexported returns an Option that forcibly allows operations on
|
// Exporter returns an Option that specifies whether Equal is allowed to
|
||||||
// unexported fields in certain structs, which are specified by passing in a
|
// introspect into the unexported fields of certain struct types.
|
||||||
// value of each struct type.
|
|
||||||
//
|
//
|
||||||
// Users of this option must understand that comparing on unexported fields
|
// Users of this option must understand that comparing on unexported fields
|
||||||
// from external packages is not safe since changes in the internal
|
// from external packages is not safe since changes in the internal
|
||||||
@@ -386,10 +397,24 @@ func (cm comparer) String() string {
|
|||||||
//
|
//
|
||||||
// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
|
// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
|
||||||
// all unexported fields on specified struct types.
|
// all unexported fields on specified struct types.
|
||||||
func AllowUnexported(types ...interface{}) Option {
|
func Exporter(f func(reflect.Type) bool) Option {
|
||||||
if !supportAllowUnexported {
|
if !supportExporters {
|
||||||
panic("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS")
|
panic("Exporter is not supported on purego builds")
|
||||||
}
|
}
|
||||||
|
return exporter(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
type exporter func(reflect.Type) bool
|
||||||
|
|
||||||
|
func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowUnexported returns an Options that allows Equal to forcibly introspect
|
||||||
|
// unexported fields of the specified struct types.
|
||||||
|
//
|
||||||
|
// See Exporter for the proper use of this option.
|
||||||
|
func AllowUnexported(types ...interface{}) Option {
|
||||||
m := make(map[reflect.Type]bool)
|
m := make(map[reflect.Type]bool)
|
||||||
for _, typ := range types {
|
for _, typ := range types {
|
||||||
t := reflect.TypeOf(typ)
|
t := reflect.TypeOf(typ)
|
||||||
@@ -398,13 +423,7 @@ func AllowUnexported(types ...interface{}) Option {
|
|||||||
}
|
}
|
||||||
m[t] = true
|
m[t] = true
|
||||||
}
|
}
|
||||||
return visibleStructs(m)
|
return exporter(func(t reflect.Type) bool { return m[t] })
|
||||||
}
|
|
||||||
|
|
||||||
type visibleStructs map[reflect.Type]bool
|
|
||||||
|
|
||||||
func (visibleStructs) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result represents the comparison result for a single node and
|
// Result represents the comparison result for a single node and
|
||||||
@@ -436,6 +455,11 @@ func (r Result) ByFunc() bool {
|
|||||||
return r.flags&reportByFunc != 0
|
return r.flags&reportByFunc != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ByCycle reports whether a reference cycle was detected.
|
||||||
|
func (r Result) ByCycle() bool {
|
||||||
|
return r.flags&reportByCycle != 0
|
||||||
|
}
|
||||||
|
|
||||||
type resultFlags uint
|
type resultFlags uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -446,6 +470,7 @@ const (
|
|||||||
reportByIgnore
|
reportByIgnore
|
||||||
reportByMethod
|
reportByMethod
|
||||||
reportByFunc
|
reportByFunc
|
||||||
|
reportByCycle
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reporter is an Option that can be passed to Equal. When Equal traverses
|
// Reporter is an Option that can be passed to Equal. When Equal traverses
|
||||||
|
71
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
71
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
@@ -10,6 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Path is a list of PathSteps describing the sequence of operations to get
|
// Path is a list of PathSteps describing the sequence of operations to get
|
||||||
@@ -41,7 +43,7 @@ type PathStep interface {
|
|||||||
// In some cases, one or both may be invalid or have restrictions:
|
// In some cases, one or both may be invalid or have restrictions:
|
||||||
// • For StructField, both are not interface-able if the current field
|
// • For StructField, both are not interface-able if the current field
|
||||||
// is unexported and the struct type is not explicitly permitted by
|
// is unexported and the struct type is not explicitly permitted by
|
||||||
// AllowUnexported to traverse unexported fields.
|
// an Exporter to traverse unexported fields.
|
||||||
// • For SliceIndex, one may be invalid if an element is missing from
|
// • For SliceIndex, one may be invalid if an element is missing from
|
||||||
// either the x or y slice.
|
// either the x or y slice.
|
||||||
// • For MapIndex, one may be invalid if an entry is missing from
|
// • For MapIndex, one may be invalid if an entry is missing from
|
||||||
@@ -207,6 +209,7 @@ type SliceIndex struct{ *sliceIndex }
|
|||||||
type sliceIndex struct {
|
type sliceIndex struct {
|
||||||
pathStep
|
pathStep
|
||||||
xkey, ykey int
|
xkey, ykey int
|
||||||
|
isSlice bool // False for reflect.Array
|
||||||
}
|
}
|
||||||
|
|
||||||
func (si SliceIndex) Type() reflect.Type { return si.typ }
|
func (si SliceIndex) Type() reflect.Type { return si.typ }
|
||||||
@@ -301,6 +304,72 @@ func (tf Transform) Func() reflect.Value { return tf.trans.fnc }
|
|||||||
// The == operator can be used to detect the exact option used.
|
// The == operator can be used to detect the exact option used.
|
||||||
func (tf Transform) Option() Option { return tf.trans }
|
func (tf Transform) Option() Option { return tf.trans }
|
||||||
|
|
||||||
|
// pointerPath represents a dual-stack of pointers encountered when
|
||||||
|
// recursively traversing the x and y values. This data structure supports
|
||||||
|
// detection of cycles and determining whether the cycles are equal.
|
||||||
|
// In Go, cycles can occur via pointers, slices, and maps.
|
||||||
|
//
|
||||||
|
// The pointerPath uses a map to represent a stack; where descension into a
|
||||||
|
// pointer pushes the address onto the stack, and ascension from a pointer
|
||||||
|
// pops the address from the stack. Thus, when traversing into a pointer from
|
||||||
|
// reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
|
||||||
|
// by checking whether the pointer has already been visited. The cycle detection
|
||||||
|
// uses a seperate stack for the x and y values.
|
||||||
|
//
|
||||||
|
// If a cycle is detected we need to determine whether the two pointers
|
||||||
|
// should be considered equal. The definition of equality chosen by Equal
|
||||||
|
// requires two graphs to have the same structure. To determine this, both the
|
||||||
|
// x and y values must have a cycle where the previous pointers were also
|
||||||
|
// encountered together as a pair.
|
||||||
|
//
|
||||||
|
// Semantically, this is equivalent to augmenting Indirect, SliceIndex, and
|
||||||
|
// MapIndex with pointer information for the x and y values.
|
||||||
|
// Suppose px and py are two pointers to compare, we then search the
|
||||||
|
// Path for whether px was ever encountered in the Path history of x, and
|
||||||
|
// similarly so with py. If either side has a cycle, the comparison is only
|
||||||
|
// equal if both px and py have a cycle resulting from the same PathStep.
|
||||||
|
//
|
||||||
|
// Using a map as a stack is more performant as we can perform cycle detection
|
||||||
|
// in O(1) instead of O(N) where N is len(Path).
|
||||||
|
type pointerPath struct {
|
||||||
|
// mx is keyed by x pointers, where the value is the associated y pointer.
|
||||||
|
mx map[value.Pointer]value.Pointer
|
||||||
|
// my is keyed by y pointers, where the value is the associated x pointer.
|
||||||
|
my map[value.Pointer]value.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pointerPath) Init() {
|
||||||
|
p.mx = make(map[value.Pointer]value.Pointer)
|
||||||
|
p.my = make(map[value.Pointer]value.Pointer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push indicates intent to descend into pointers vx and vy where
|
||||||
|
// visited reports whether either has been seen before. If visited before,
|
||||||
|
// equal reports whether both pointers were encountered together.
|
||||||
|
// Pop must be called if and only if the pointers were never visited.
|
||||||
|
//
|
||||||
|
// The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map
|
||||||
|
// and be non-nil.
|
||||||
|
func (p pointerPath) Push(vx, vy reflect.Value) (equal, visited bool) {
|
||||||
|
px := value.PointerOf(vx)
|
||||||
|
py := value.PointerOf(vy)
|
||||||
|
_, ok1 := p.mx[px]
|
||||||
|
_, ok2 := p.my[py]
|
||||||
|
if ok1 || ok2 {
|
||||||
|
equal = p.mx[px] == py && p.my[py] == px // Pointers paired together
|
||||||
|
return equal, true
|
||||||
|
}
|
||||||
|
p.mx[px] = py
|
||||||
|
p.my[py] = px
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop ascends from pointers vx and vy.
|
||||||
|
func (p pointerPath) Pop(vx, vy reflect.Value) {
|
||||||
|
delete(p.mx, value.PointerOf(vx))
|
||||||
|
delete(p.my, value.PointerOf(vy))
|
||||||
|
}
|
||||||
|
|
||||||
// isExported reports whether the identifier is exported.
|
// isExported reports whether the identifier is exported.
|
||||||
func isExported(id string) bool {
|
func isExported(id string) bool {
|
||||||
r, _ := utf8.DecodeRuneInString(id)
|
r, _ := utf8.DecodeRuneInString(id)
|
||||||
|
17
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
17
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
@@ -81,14 +81,19 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
|
|||||||
return opts.FormatDiffSlice(v)
|
return opts.FormatDiffSlice(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var withinSlice bool
|
||||||
|
if v.parent != nil && (v.parent.Type.Kind() == reflect.Slice || v.parent.Type.Kind() == reflect.Array) {
|
||||||
|
withinSlice = true
|
||||||
|
}
|
||||||
|
|
||||||
// For leaf nodes, format the value based on the reflect.Values alone.
|
// For leaf nodes, format the value based on the reflect.Values alone.
|
||||||
if v.MaxDepth == 0 {
|
if v.MaxDepth == 0 {
|
||||||
switch opts.DiffMode {
|
switch opts.DiffMode {
|
||||||
case diffUnknown, diffIdentical:
|
case diffUnknown, diffIdentical:
|
||||||
// Format Equal.
|
// Format Equal.
|
||||||
if v.NumDiff == 0 {
|
if v.NumDiff == 0 {
|
||||||
outx := opts.FormatValue(v.ValueX, visitedPointers{})
|
outx := opts.FormatValue(v.ValueX, withinSlice, visitedPointers{})
|
||||||
outy := opts.FormatValue(v.ValueY, visitedPointers{})
|
outy := opts.FormatValue(v.ValueY, withinSlice, visitedPointers{})
|
||||||
if v.NumIgnored > 0 && v.NumSame == 0 {
|
if v.NumIgnored > 0 && v.NumSame == 0 {
|
||||||
return textEllipsis
|
return textEllipsis
|
||||||
} else if outx.Len() < outy.Len() {
|
} else if outx.Len() < outy.Len() {
|
||||||
@@ -101,8 +106,8 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
|
|||||||
// Format unequal.
|
// Format unequal.
|
||||||
assert(opts.DiffMode == diffUnknown)
|
assert(opts.DiffMode == diffUnknown)
|
||||||
var list textList
|
var list textList
|
||||||
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, visitedPointers{})
|
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, withinSlice, visitedPointers{})
|
||||||
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, visitedPointers{})
|
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, withinSlice, visitedPointers{})
|
||||||
if outx != nil {
|
if outx != nil {
|
||||||
list = append(list, textRecord{Diff: '-', Value: outx})
|
list = append(list, textRecord{Diff: '-', Value: outx})
|
||||||
}
|
}
|
||||||
@@ -111,9 +116,9 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
|
|||||||
}
|
}
|
||||||
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
||||||
case diffRemoved:
|
case diffRemoved:
|
||||||
return opts.FormatValue(v.ValueX, visitedPointers{})
|
return opts.FormatValue(v.ValueX, withinSlice, visitedPointers{})
|
||||||
case diffInserted:
|
case diffInserted:
|
||||||
return opts.FormatValue(v.ValueY, visitedPointers{})
|
return opts.FormatValue(v.ValueY, withinSlice, visitedPointers{})
|
||||||
default:
|
default:
|
||||||
panic("invalid diff mode")
|
panic("invalid diff mode")
|
||||||
}
|
}
|
||||||
|
23
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
23
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
@@ -74,7 +74,7 @@ func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
|
|||||||
|
|
||||||
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
||||||
// into pointers already in m. As pointers are visited, m is also updated.
|
// into pointers already in m. As pointers are visited, m is also updated.
|
||||||
func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) {
|
func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visitedPointers) (out textNode) {
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -108,12 +108,15 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||||||
return textLine(fmt.Sprint(v.Bool()))
|
return textLine(fmt.Sprint(v.Bool()))
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return textLine(fmt.Sprint(v.Int()))
|
return textLine(fmt.Sprint(v.Int()))
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
// Unnamed uints are usually bytes or words, so use hexadecimal.
|
return textLine(fmt.Sprint(v.Uint()))
|
||||||
if t.PkgPath() == "" || t.Kind() == reflect.Uintptr {
|
case reflect.Uint8:
|
||||||
|
if withinSlice {
|
||||||
return textLine(formatHex(v.Uint()))
|
return textLine(formatHex(v.Uint()))
|
||||||
}
|
}
|
||||||
return textLine(fmt.Sprint(v.Uint()))
|
return textLine(fmt.Sprint(v.Uint()))
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return textLine(formatHex(v.Uint()))
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return textLine(fmt.Sprint(v.Float()))
|
return textLine(fmt.Sprint(v.Float()))
|
||||||
case reflect.Complex64, reflect.Complex128:
|
case reflect.Complex64, reflect.Complex128:
|
||||||
@@ -129,7 +132,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||||||
if value.IsZero(vv) {
|
if value.IsZero(vv) {
|
||||||
continue // Elide fields with zero values
|
continue // Elide fields with zero values
|
||||||
}
|
}
|
||||||
s := opts.WithTypeMode(autoType).FormatValue(vv, m)
|
s := opts.WithTypeMode(autoType).FormatValue(vv, false, m)
|
||||||
list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
|
list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
|
||||||
}
|
}
|
||||||
return textWrap{"{", list, "}"}
|
return textWrap{"{", list, "}"}
|
||||||
@@ -156,7 +159,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s := opts.WithTypeMode(elideType).FormatValue(vi, m)
|
s := opts.WithTypeMode(elideType).FormatValue(vi, true, m)
|
||||||
list = append(list, textRecord{Value: s})
|
list = append(list, textRecord{Value: s})
|
||||||
}
|
}
|
||||||
return textWrap{ptr + "{", list, "}"}
|
return textWrap{ptr + "{", list, "}"}
|
||||||
@@ -171,7 +174,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||||||
var list textList
|
var list textList
|
||||||
for _, k := range value.SortKeys(v.MapKeys()) {
|
for _, k := range value.SortKeys(v.MapKeys()) {
|
||||||
sk := formatMapKey(k)
|
sk := formatMapKey(k)
|
||||||
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m)
|
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), false, m)
|
||||||
list = append(list, textRecord{Key: sk, Value: sv})
|
list = append(list, textRecord{Key: sk, Value: sv})
|
||||||
}
|
}
|
||||||
if opts.PrintAddresses {
|
if opts.PrintAddresses {
|
||||||
@@ -189,7 +192,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||||||
ptr = formatPointer(v)
|
ptr = formatPointer(v)
|
||||||
}
|
}
|
||||||
skipType = true // Let the underlying value print the type instead
|
skipType = true // Let the underlying value print the type instead
|
||||||
return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""}
|
return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), false, m), ""}
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return textNil
|
return textNil
|
||||||
@@ -197,7 +200,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t
|
|||||||
// Interfaces accept different concrete types,
|
// Interfaces accept different concrete types,
|
||||||
// so configure the underlying value to explicitly print the type.
|
// so configure the underlying value to explicitly print the type.
|
||||||
skipType = true // Print the concrete type instead
|
skipType = true // Print the concrete type instead
|
||||||
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m)
|
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), false, m)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||||
}
|
}
|
||||||
@@ -209,7 +212,7 @@ func formatMapKey(v reflect.Value) string {
|
|||||||
var opts formatOptions
|
var opts formatOptions
|
||||||
opts.TypeMode = elideType
|
opts.TypeMode = elideType
|
||||||
opts.ShallowPointers = true
|
opts.ShallowPointers = true
|
||||||
s := opts.FormatValue(v, visitedPointers{}).String()
|
s := opts.FormatValue(v, false, visitedPointers{}).String()
|
||||||
return strings.TrimSpace(s)
|
return strings.TrimSpace(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
4
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
@@ -172,7 +172,9 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
switch t.Elem().Kind() {
|
switch t.Elem().Kind() {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
ss = append(ss, fmt.Sprint(v.Index(i).Int()))
|
ss = append(ss, fmt.Sprint(v.Index(i).Int()))
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
ss = append(ss, fmt.Sprint(v.Index(i).Uint()))
|
||||||
|
case reflect.Uint8, reflect.Uintptr:
|
||||||
ss = append(ss, formatHex(v.Index(i).Uint()))
|
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||||
ss = append(ss, fmt.Sprint(v.Index(i).Interface()))
|
ss = append(ss, fmt.Sprint(v.Index(i).Interface()))
|
||||||
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -1,4 +1,5 @@
|
|||||||
# github.com/google/go-cmp v0.3.1
|
# github.com/google/go-cmp v0.4.1
|
||||||
|
## explicit
|
||||||
github.com/google/go-cmp/cmp
|
github.com/google/go-cmp/cmp
|
||||||
github.com/google/go-cmp/cmp/internal/diff
|
github.com/google/go-cmp/cmp/internal/diff
|
||||||
github.com/google/go-cmp/cmp/internal/flags
|
github.com/google/go-cmp/cmp/internal/flags
|
||||||
|
Reference in New Issue
Block a user