diff --git a/.travis.yml b/.travis.yml index 8b3da87..1ae07d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: go go: - - "1.10" + - "1.12.5" diff --git a/database.go b/database.go index 547e367..430a67d 100644 --- a/database.go +++ b/database.go @@ -68,11 +68,8 @@ func (s *DatabaseService) Release(releaseID int) (*Release, error) { params.Set("curr_abbr", s.currency) var release *Release - if err := request(s.url+releasesURI+strconv.Itoa(releaseID), params, &release); err != nil { - return nil, err - } - - return release, nil + err := request(s.url+releasesURI+strconv.Itoa(releaseID), params, &release) + return release, err } // ReleaseRating serves response for community release rating request @@ -84,11 +81,8 @@ type ReleaseRating struct { // ReleaseRating retruns community release rating func (s *DatabaseService) ReleaseRating(releaseID int) (*ReleaseRating, error) { var rating *ReleaseRating - if err := request(s.url+releasesURI+strconv.Itoa(releaseID)+"/rating", nil, &rating); err != nil { - return nil, err - } - - return rating, nil + err := request(s.url+releasesURI+strconv.Itoa(releaseID)+"/rating", nil, &rating) + return rating, err } // Artist ... @@ -108,10 +102,8 @@ type Artist struct { // Artist represents a person in the discogs database func (s *DatabaseService) Artist(artistID int) (*Artist, error) { var artist *Artist - if err := request(s.url+artistsURI+strconv.Itoa(artistID), nil, &artist); err != nil { - return nil, err - } - return artist, nil + err := request(s.url+artistsURI+strconv.Itoa(artistID), nil, &artist) + return artist, err } // ArtistReleases ... @@ -123,10 +115,8 @@ type ArtistReleases struct { // ArtistReleases returns a list of releases and masters associated with the artist. func (s *DatabaseService) ArtistReleases(artistID int, pagination *Pagination) (*ArtistReleases, error) { var releases *ArtistReleases - if err := request(s.url+artistsURI+strconv.Itoa(artistID)+"/releases", pagination.toParams(), &releases); err != nil { - return nil, err - } - return releases, nil + err := request(s.url+artistsURI+strconv.Itoa(artistID)+"/releases", pagination.params(), &releases) + return releases, err } // Label resource represents a label, company, recording studio, location, @@ -148,10 +138,8 @@ type Label struct { // Label returns a label. func (s *DatabaseService) Label(labelID int) (*Label, error) { var label *Label - if err := request(s.url+labelsURI+strconv.Itoa(labelID), nil, &label); err != nil { - return nil, err - } - return label, nil + err := request(s.url+labelsURI+strconv.Itoa(labelID), nil, &label) + return label, err } // LabelReleases is a list of Releases associated with the label. @@ -163,10 +151,8 @@ type LabelReleases struct { // LabelReleases returns a list of Releases associated with the label. func (s *DatabaseService) LabelReleases(labelID int, pagination *Pagination) (*LabelReleases, error) { var releases *LabelReleases - if err := request(s.url+labelsURI+strconv.Itoa(labelID)+"/releases", pagination.toParams(), &releases); err != nil { - return nil, err - } - return releases, nil + err := request(s.url+labelsURI+strconv.Itoa(labelID)+"/releases", pagination.params(), &releases) + return releases, err } // Master resource represents a set of similar releases. @@ -192,10 +178,8 @@ type Master struct { // Master returns a master release func (s *DatabaseService) Master(masterID int) (*Master, error) { var master *Master - if err := request(s.url+mastersURI+strconv.Itoa(masterID), nil, &master); err != nil { - return nil, err - } - return master, nil + err := request(s.url+mastersURI+strconv.Itoa(masterID), nil, &master) + return master, err } // MasterVersions retrieves a list of all releases that are versions of this master. @@ -207,8 +191,6 @@ type MasterVersions struct { // MasterVersions retrieves a list of all Releases that are versions of this master func (s *DatabaseService) MasterVersions(masterID int, pagination *Pagination) (*MasterVersions, error) { var versions *MasterVersions - if err := request(s.url+mastersURI+strconv.Itoa(masterID)+"/versions", pagination.toParams(), &versions); err != nil { - return nil, err - } - return versions, nil + err := request(s.url+mastersURI+strconv.Itoa(masterID)+"/versions", pagination.params(), &versions) + return versions, err } diff --git a/database_test.go b/database_test.go index 02ec9c3..c92797c 100644 --- a/database_test.go +++ b/database_test.go @@ -9,15 +9,16 @@ import ( func ReleaseServer(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"title":"Elephant Riddim"}`) + if _, err := io.WriteString(w, `{"title":"Elephant Riddim"}`); err != nil { + panic(err) + } } func TestReleaseServiceRelease(t *testing.T) { - expectedTitle := "Elephant Riddim" - ts := httptest.NewServer(http.HandlerFunc(ReleaseServer)) defer ts.Close() + expectedTitle := "Elephant Riddim" d := initDiscogsClient(t, &Options{URL: ts.URL}) release, err := d.Database.Release(8138518) if err != nil { diff --git a/discogs.go b/discogs.go index d05c3cb..0cde216 100644 --- a/discogs.go +++ b/discogs.go @@ -2,6 +2,7 @@ package discogs import ( "encoding/json" + "fmt" "io/ioutil" "net/http" "net/url" @@ -85,6 +86,15 @@ func request(path string, params url.Values, resp interface{}) error { } defer response.Body.Close() + if response.StatusCode != http.StatusOK { + switch response.StatusCode { + case http.StatusUnauthorized: + return ErrUnauthorized + default: + return fmt.Errorf("unknown error: %s", response.Status) + } + } + body, err := ioutil.ReadAll(response.Body) if err != nil { return err diff --git a/errors.go b/errors.go index d87b663..d4a415f 100644 --- a/errors.go +++ b/errors.go @@ -16,6 +16,7 @@ func (e *Error) Error() string { // APIErrors var ( + ErrUnauthorized = &Error{"authentication required"} ErrCurrencyNotSupported = &Error{"currency does not supported"} ErrUserAgentInvalid = &Error{"invalid user-agent"} ) diff --git a/examples/discogs_example.go b/examples/discogs_example.go index 1217163..b1f9381 100644 --- a/examples/discogs_example.go +++ b/examples/discogs_example.go @@ -9,7 +9,7 @@ import ( func main() { d, err := discogs.NewClient(&discogs.Options{ UserAgent: "TestDiscogsClient/0.0.1 +http://example.com", - Currency: "AAA", + Currency: "USD", Token: "", }) if err != nil { @@ -17,7 +17,7 @@ func main() { return } - release, err := d.Database.Release(12345) + release, err := d.Search.Search(discogs.SearchRequest{Q: "middle", PerPage: 3}) if err != nil { fmt.Println(err) return diff --git a/go.mod b/go.mod index 0e987ce..be4d84e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/irlndts/go-discogs -require github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 +go 1.12 diff --git a/go.sum b/go.sum index 35411ea..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= diff --git a/models.go b/models.go index 008680c..5d615ae 100644 --- a/models.go +++ b/models.go @@ -175,23 +175,15 @@ type Pagination struct { } // toParams converts pagaination params to request values -func (p *Pagination) toParams() url.Values { +func (p *Pagination) params() url.Values { if p == nil { return nil } params := url.Values{} - if p.Sort != "" { - params.Set("sort", p.Sort) - } - if p.SortOrder != "" { - params.Set("sort_order", p.SortOrder) - } - if p.Page != 0 { - params.Set("page", strconv.Itoa(p.Page)) - } - if p.PerPage != 0 { - params.Set("per_page", strconv.Itoa(p.PerPage)) - } + params.Set("sort", p.Sort) + params.Set("sort_order", p.SortOrder) + params.Set("page", strconv.Itoa(p.Page)) + params.Set("per_page", strconv.Itoa(p.PerPage)) return params } diff --git a/search.go b/search.go index 9d83dae..c7aa4ed 100644 --- a/search.go +++ b/search.go @@ -1,7 +1,8 @@ package discogs import ( - "github.com/google/go-querystring/query" + "net/url" + "strconv" ) // SearchService ... @@ -17,27 +18,56 @@ func newSearchService(url string) *SearchService { // SearchRequest describes search request type SearchRequest struct { - Q string `url:"q,omitempty"` // search query - Type string `url:"type,omitempty"` // one of release, master, artist, label - Title string `url:"title,omitempty"` // search by combined “Artist Name - Release Title” title field - ReleaseTitle string `url:"release_title,omitempty"` // search release titles - Credit string `url:"credit,omitempty"` // search release credits - Artist string `url:"artist,omitempty"` // search artist names - Anv string `url:"anv,omitempty"` // search artist ANV - Label string `url:"label,omitempty"` // search label names - Genre string `url:"genre,omitempty"` // search genres - Style string `url:"style,omitempty"` // search styles - Country string `url:"country,omitempty"` // search release country - Year string `url:"year,omitempty"` // search release year - Format string `url:"format,omitempty"` // search formats - Catno string `url:"catno,omitempty"` // search catalog number - Barcode string `url:"barcode,omitempty"` // search barcodes - Track string `url:"track,omitempty"` // search track titles - Submitter string `url:"submitter,omitempty"` // search submitter username - Contributor string `url:"contributor,omitempty"` // search contributor usernames + Q string // search query + Type string // one of release, master, artist, label + Title string // search by combined “Artist Name - Release Title” title field + ReleaseTitle string // search release titles + Credit string // search release credits + Artist string // search artist names + Anv string // search artist ANV + Label string // search label names + Genre string // search genres + Style string // search styles + Country string // search release country + Year string // search release year + Format string // search formats + Catno string // search catalog number + Barcode string // search barcodes + Track string // search track titles + Submitter string // search submitter username + Contributor string // search contributor usernames - Page int `url:"page,omitempty"` - PerPage int `url:"per_page,omitempty"` + Page int + PerPage int +} + +func (r *SearchRequest) params() url.Values { + if r == nil { + return nil + } + + params := url.Values{} + params.Set("q", r.Q) + params.Set("type", r.Type) + params.Set("title", r.Title) + params.Set("release_title", r.ReleaseTitle) + params.Set("credit", r.Credit) + params.Set("artist", r.Artist) + params.Set("anv", r.Anv) + params.Set("label", r.Label) + params.Set("genre", r.Genre) + params.Set("style", r.Style) + params.Set("country", r.Country) + params.Set("year", r.Year) + params.Set("format", r.Format) + params.Set("catno", r.Catno) + params.Set("barcode", r.Barcode) + params.Set("track", r.Track) + params.Set("submitter", r.Submitter) + params.Set("contributor", r.Contributor) + params.Set("page", strconv.Itoa(r.Page)) + params.Set("per_page", strconv.Itoa(r.PerPage)) + return params } // Search describes search response @@ -68,16 +98,8 @@ type Result struct { // 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 -// TODO(irlndts): improve params to pass func (s *SearchService) Search(req SearchRequest) (*Search, error) { - params, err := query.Values(req) - if err != nil { - return nil, err - } - var search *Search - if err := request(s.url, params, &search); err != nil { - return nil, err - } - return search, nil + err := request(s.url, req.params(), &search) + return search, err } diff --git a/vendor/github.com/google/go-querystring/LICENSE b/vendor/github.com/google/go-querystring/LICENSE deleted file mode 100644 index ae121a1..0000000 --- a/vendor/github.com/google/go-querystring/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 Google. 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. diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go deleted file mode 100644 index 37080b1..0000000 --- a/vendor/github.com/google/go-querystring/query/encode.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright 2013 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 query implements encoding of structs into URL query parameters. -// -// As a simple example: -// -// type Options struct { -// Query string `url:"q"` -// ShowAll bool `url:"all"` -// Page int `url:"page"` -// } -// -// opt := Options{ "foo", true, 2 } -// v, _ := query.Values(opt) -// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" -// -// The exact mapping between Go values and url.Values is described in the -// documentation for the Values() function. -package query - -import ( - "bytes" - "fmt" - "net/url" - "reflect" - "strconv" - "strings" - "time" -) - -var timeType = reflect.TypeOf(time.Time{}) - -var encoderType = reflect.TypeOf(new(Encoder)).Elem() - -// Encoder is an interface implemented by any type that wishes to encode -// itself into URL values in a non-standard way. -type Encoder interface { - EncodeValues(key string, v *url.Values) error -} - -// Values returns the url.Values encoding of v. -// -// Values expects to be passed a struct, and traverses it recursively using the -// following encoding rules. -// -// Each exported struct field is encoded as a URL parameter unless -// -// - the field's tag is "-", or -// - the field is empty and its tag specifies the "omitempty" option -// -// The empty values are false, 0, any nil pointer or interface value, any array -// slice, map, or string of length zero, and any time.Time that returns true -// for IsZero(). -// -// The URL parameter name defaults to the struct field name but can be -// specified in the struct field's tag value. The "url" key in the struct -// field's tag value is the key name, followed by an optional comma and -// options. For example: -// -// // Field is ignored by this package. -// Field int `url:"-"` -// -// // Field appears as URL parameter "myName". -// Field int `url:"myName"` -// -// // Field appears as URL parameter "myName" and the field is omitted if -// // its value is empty -// Field int `url:"myName,omitempty"` -// -// // Field appears as URL parameter "Field" (the default), but the field -// // is skipped if empty. Note the leading comma. -// Field int `url:",omitempty"` -// -// For encoding individual field values, the following type-dependent rules -// apply: -// -// Boolean values default to encoding as the strings "true" or "false". -// Including the "int" option signals that the field should be encoded as the -// strings "1" or "0". -// -// time.Time values default to encoding as RFC3339 timestamps. Including the -// "unix" option signals that the field should be encoded as a Unix time (see -// time.Unix()) -// -// Slice and Array values default to encoding as multiple URL values of the -// same name. Including the "comma" option signals that the field should be -// encoded as a single comma-delimited value. Including the "space" option -// similarly encodes the value as a single space-delimited string. Including -// the "semicolon" option will encode the value as a semicolon-delimited string. -// Including the "brackets" option signals that the multiple URL values should -// have "[]" appended to the value name. "numbered" will append a number to -// the end of each incidence of the value name, example: -// name0=value0&name1=value1, etc. -// -// Anonymous struct fields are usually encoded as if their inner exported -// fields were fields in the outer struct, subject to the standard Go -// visibility rules. An anonymous struct field with a name given in its URL -// tag is treated as having that name, rather than being anonymous. -// -// Non-nil pointer values are encoded as the value pointed to. -// -// Nested structs are encoded including parent fields in value names for -// scoping. e.g: -// -// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" -// -// All other values are encoded using their default string representation. -// -// Multiple fields that encode to the same URL parameter name will be included -// as multiple URL values of the same name. -func Values(v interface{}) (url.Values, error) { - values := make(url.Values) - val := reflect.ValueOf(v) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return values, nil - } - val = val.Elem() - } - - if v == nil { - return values, nil - } - - if val.Kind() != reflect.Struct { - return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) - } - - err := reflectValue(values, val, "") - return values, err -} - -// reflectValue populates the values parameter from the struct fields in val. -// Embedded structs are followed recursively (using the rules defined in the -// Values function documentation) breadth-first. -func reflectValue(values url.Values, val reflect.Value, scope string) error { - var embedded []reflect.Value - - typ := val.Type() - for i := 0; i < typ.NumField(); i++ { - sf := typ.Field(i) - if sf.PkgPath != "" && !sf.Anonymous { // unexported - continue - } - - sv := val.Field(i) - tag := sf.Tag.Get("url") - if tag == "-" { - continue - } - name, opts := parseTag(tag) - if name == "" { - if sf.Anonymous && sv.Kind() == reflect.Struct { - // save embedded struct for later processing - embedded = append(embedded, sv) - continue - } - - name = sf.Name - } - - if scope != "" { - name = scope + "[" + name + "]" - } - - if opts.Contains("omitempty") && isEmptyValue(sv) { - continue - } - - if sv.Type().Implements(encoderType) { - if !reflect.Indirect(sv).IsValid() { - sv = reflect.New(sv.Type().Elem()) - } - - m := sv.Interface().(Encoder) - if err := m.EncodeValues(name, &values); err != nil { - return err - } - continue - } - - if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { - var del byte - if opts.Contains("comma") { - del = ',' - } else if opts.Contains("space") { - del = ' ' - } else if opts.Contains("semicolon") { - del = ';' - } else if opts.Contains("brackets") { - name = name + "[]" - } - - if del != 0 { - s := new(bytes.Buffer) - first := true - for i := 0; i < sv.Len(); i++ { - if first { - first = false - } else { - s.WriteByte(del) - } - s.WriteString(valueString(sv.Index(i), opts)) - } - values.Add(name, s.String()) - } else { - for i := 0; i < sv.Len(); i++ { - k := name - if opts.Contains("numbered") { - k = fmt.Sprintf("%s%d", name, i) - } - values.Add(k, valueString(sv.Index(i), opts)) - } - } - continue - } - - for sv.Kind() == reflect.Ptr { - if sv.IsNil() { - break - } - sv = sv.Elem() - } - - if sv.Type() == timeType { - values.Add(name, valueString(sv, opts)) - continue - } - - if sv.Kind() == reflect.Struct { - reflectValue(values, sv, name) - continue - } - - values.Add(name, valueString(sv, opts)) - } - - for _, f := range embedded { - if err := reflectValue(values, f, scope); err != nil { - return err - } - } - - return nil -} - -// valueString returns the string representation of a value. -func valueString(v reflect.Value, opts tagOptions) string { - for v.Kind() == reflect.Ptr { - if v.IsNil() { - return "" - } - v = v.Elem() - } - - if v.Kind() == reflect.Bool && opts.Contains("int") { - if v.Bool() { - return "1" - } - return "0" - } - - if v.Type() == timeType { - t := v.Interface().(time.Time) - if opts.Contains("unix") { - return strconv.FormatInt(t.Unix(), 10) - } - return t.Format(time.RFC3339) - } - - return fmt.Sprint(v.Interface()) -} - -// isEmptyValue checks if a value should be considered empty for the purposes -// of omitting fields with the "omitempty" option. -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - } - - if v.Type() == timeType { - return v.Interface().(time.Time).IsZero() - } - - return false -} - -// tagOptions is the string following a comma in a struct field's "url" tag, or -// the empty string. It does not include the leading comma. -type tagOptions []string - -// parseTag splits a struct field's url tag into its name and comma-separated -// options. -func parseTag(tag string) (string, tagOptions) { - s := strings.Split(tag, ",") - return s[0], s[1:] -} - -// Contains checks whether the tagOptions contains the specified option. -func (o tagOptions) Contains(option string) bool { - for _, s := range o { - if s == option { - return true - } - } - return false -} diff --git a/vendor/modules.txt b/vendor/modules.txt deleted file mode 100644 index 648d508..0000000 --- a/vendor/modules.txt +++ /dev/null @@ -1,2 +0,0 @@ -# github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 -github.com/google/go-querystring/query