61 Commits

Author SHA1 Message Date
irlndts
618b882634 Use go mod 2018-12-11 16:47:31 +03:00
Artem Piskun
a4b6e4193a Merge pull request #27 from pluyckx/master
Add 'Artists' field to 'Track'
2018-04-30 21:27:27 +03:00
Philip Luyckx
e1e0446c29 Add 'Artists' field to 'Track'
Some tracks contain different artists than specified in the release. In
this case there is a field 'artists' in the JSON response. This field is
added to the 'Track' struct. When the field 'artists' is available in the
JSON response, the length of 'Artists' is not 0. Otherwise the 'Artists'
from the release should be used.

Signed-off-by: Philip Luyckx <philip.luyckx@gmail.com>
2018-04-30 10:37:04 +02:00
irlndts
dd501a3f65 fix 2018-04-10 15:39:54 +03:00
irlndts
110a37661a goreportcard 2018-03-28 20:18:24 +03:00
irlndts
3775bc39c7 travis pic 2018-03-28 20:13:31 +03:00
irlndts
e9ad282056 added travis-ci.yml 2018-03-28 20:11:47 +03:00
Artem Piskun
31a2e3b959 Merge pull request #26 from irlndts/APIErrors 2018-03-24 16:19:05 +03:00
irlndts
8e69f9334b Added several tests 2018-03-24 16:17:35 +03:00
irlndts
788518e3e2 Added several tests 2018-03-24 16:14:54 +03:00
irlndts
0a3a17b69d Lint errors fixed 2018-03-24 13:46:20 +03:00
Artem Piskun
ebfd052f67 Merge pull request #24 from irlndts/DSCGS-23
Move services to database
2018-03-21 21:18:54 +03:00
irlndts
c526ccd692 Move services to database 2018-03-21 21:17:40 +03:00
Artem Piskun
91d056b186 Merge pull request #22 from irlndts/DSCGS-20
Fix govet errors
2018-03-21 20:58:14 +03:00
irlndts
441de35813 Fix govet errors 2018-03-21 20:57:39 +03:00
Artem Piskun
872ff94673 Merge pull request #21 from irlndts/DSCGS-19
DSCGS-19 fix lint
2018-03-20 13:57:39 +03:00
irlndts
afd778f11c Cleanup 2018-03-20 13:56:17 +03:00
irlndts
dd29f70850 DSCGS-19 Fix golint errors 2018-03-17 23:12:25 +03:00
irlndts
d5ddddc09d Doc fixed 2018-03-17 22:41:43 +03:00
Artem Piskun
ede8271b88 Merge pull request #18 from irlndts/DSCGS-17 2018-03-17 22:01:09 +03:00
irlndts
b2674eb55e DSCGS-17 Use github.com/google/go-querystring/ 2018-03-17 21:58:56 +03:00
Artem Piskun
2bc2347afc Merge pull request #16 from irlndts/DSCGS-11 2018-03-17 21:00:22 +03:00
irlndts
62ba69198d DSCGS-11 Cleanup 2018-03-17 20:59:51 +03:00
irlndts
33699d6ad5 DSCGS-11 Cleanup 2018-03-17 20:57:41 +03:00
irlndts
4b824487ea DSCGS-11 Improve SearchService 2018-03-13 00:12:37 +03:00
irlndts
1091d9be02 DSCGS-11 Improve SearchService 2018-03-13 00:12:06 +03:00
Artem Piskun
b8ec73e2a0 Merge pull request #15 from irlndts/DSCGS-10 2018-03-12 23:33:10 +03:00
irlndts
7a214b4b15 DSCGS-10 Rewrite LabelService 2018-03-12 23:31:47 +03:00
Artem Piskun
fcb075f0f4 Merge pull request #13 from irlndts/DSCGS-9 2018-03-12 21:14:16 +03:00
irlndts
615c7561ad DSCGS-9 Rewrite MasterService 2018-03-12 21:13:09 +03:00
Artem Piskun
93ddab1b21 Merge pull request #12 from irlndts/DSCGS-7 2018-03-12 20:54:54 +03:00
irlndts
ad267389f6 DSCGS-7 Rewrite ArtistService like release service 2018-03-12 20:51:06 +03:00
Artem Piskun
8d359bb193 Merge pull request #6 from irlndts/Rewrite
Improvement and Rating
2018-03-12 20:00:30 +03:00
irlndts
4a80c3cc39 Rating 2018-03-12 19:51:37 +03:00
irlndts
69ef2a5d9a Release 2018-03-12 19:31:50 +03:00
irlndts
d1095bc769 Release improvement 2018-03-12 19:30:58 +03:00
irlndts
7d6af61b78 TEST 2018-02-20 20:00:24 +03:00
irlndts
6af125badd Improvements 2018-02-20 19:26:00 +03:00
irlndts
916e1f0bd6 Improvement token 2018-02-20 18:16:34 +03:00
irlndts
d0f617733a Some Improvements 2018-02-20 18:13:04 +03:00
irlndts
055007c459 improvement 2018-01-22 21:15:06 +03:00
irlndts
72a2e61894 Doc fixed 2018-01-22 21:03:06 +03:00
irlndts
87747a846f Doc fixed 2018-01-22 21:01:29 +03:00
irlndts
795cdb9f79 doc updated 2018-01-05 22:52:20 +03:00
irlndts
6a5e9d0a6b fix 2017-05-10 18:02:10 +03:00
irlndts
7536e26d28 merge 2017-05-09 18:54:15 +03:00
irlndts
5f77b45281 merge 2017-05-09 18:53:47 +03:00
irlndts
0f41f45197 minor comment fix 2017-05-09 18:52:58 +03:00
irlndts
efff71f46e minor comments 2017-05-09 17:07:11 +03:00
Artem Piskun
86244f6665 Merge pull request #5 from irlndts/DSCG-4
DSCGS-4
2017-04-26 16:02:07 +03:00
irlndts
78b3d0b0ad DSCGS-4 fix 2017-04-26 16:01:06 +03:00
irlndts
44db789a6b DSCGS-4 Logic improved 2017-04-26 15:57:03 +03:00
irlndts
dd4fa14f61 DSCGS-4 enough for today 2017-04-25 19:39:32 +03:00
irlndts
26dbacfe44 Minor fixings 2017-04-17 00:09:01 +03:00
irlndts
bce16b5ed1 Some comments edited 2017-04-16 23:49:50 +03:00
Artem Piskun
4e7c6c065a Merge pull request #3 from irlndts/DISCOGS-2
Discogs-2 Search Implementation
2017-02-14 22:05:07 +03:00
irlndts
d5292c30c9 DISCOGS-2 Search implementation 2017-02-14 22:01:50 +03:00
irlndts
20710e3f3e DISCOGS-2 Fix for the day 2017-02-13 19:40:27 +03:00
irlndts
cef1beeb81 test is fixed 2017-02-10 19:54:21 +03:00
Artem Piskun
83d3334321 Update README.md 2016-03-14 14:19:41 +03:00
Artem Piskun
c131693a53 Update README.md 2016-03-11 17:54:33 +03:00
24 changed files with 1119 additions and 575 deletions

3
.travis.yml Normal file
View File

@@ -0,0 +1,3 @@
language: go
go:
- "1.10"

View File

@@ -1,63 +1,89 @@
# REST API 2.0 Discogs.com client
go-discogs is a Go client library for the [Discogs API](https://www.discogs.com/developers/). Check the usage section or try the examples to see how to access the Discogs API.
[![Build Status](https://travis-ci.org/irlndts/go-discogs.svg?branch=master)](https://travis-ci.org/irlndts/go-discogs)[![Go Report Card](https://goreportcard.com/badge/github.com/irlndts/go-discogs)](https://goreportcard.com/report/github.com/irlndts/go-discogs)
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.
### Feauteres
* Database
* Releases
* [Releases](#releases)
* Release Rating
* Master Releases
* Release Versions
* Master Versions
* Artists
* Artist Releases
* Label
* All Label Releases
#### ToDo
- Search
* [Search](#search)
Install
--------
go get github.com/irlndts/go-discogs
go get -u github.com/irlndts/go-discogs
Usage
---------
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](https://www.discogs.com/developers/#page:home,header:home-general-information).
```go
package main
import (
"github.com/irlndts/go-discogs"
)
```
```go
client := discogs.NewClient().UserAgent("TestDiscogsClient/0.0.1 +example.com")
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).
This is token way example:
```go
client, err := discogs.NewClient(&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
})
```
#### Releases
```go
params := &discogs.ReleaseParams{Release_id: "8138518"}
release, _, err := d.Release.Release(params)
fmt.Println(fmt.Println(release.Artists[0].Name, " - ", release.Title)) // St. Petersburg Ska-Jazz Review - Elephant Riddim
release, _ := client.Database.Release(9893847)
fmt.Println(release.Artists[0].Name, " - ", release.Title)
// St. Petersburg Ska-Jazz Review - Elephant Riddim
```
#### Artists
#### Search
Issue a search query to discogs database. This endpoint accepts pagination parameters.
Authentication (as any user) is required.
Use `SearchRequest` struct to create a request.
```go
params := &discogs.LabelParams{Label_id: "890477", Page: 2, Per_page: 3}
label, _, err := d.Label.Releases(params)
type SearchRequest struct {
Q string // search query (optional)
Type string // one of release, master, artist, label (optional)
Title string // search by combined “Artist Name - Release Title” title field (optional)
ReleaseTitle string // search release titles (optional)
Credit string // search release credits (optional)
Artist string // search artist names (optional)
Anv string // search artist ANV (optional)
Label string // search label names (optional)
Genre string // search genres (optional)
Style string // search styles (optional)
Country string // search release country (optional)
Year string // search release year (optional)
Format string // search formats (optional)
Catno string // search catalog number (optional)
Barcode string // search barcodes (optional)
Track string // search track titles (optional)
Submitter string // search submitter username (optional)
Contributer string // search contributor usernames (optional)
for _, release := range label.Releases {
fmt.Println(release.Title)
Page int // optional
PerPage int // optional
}
```
Example
```go
request := discogs.SearchRequest{Artist: "reggaenauts", ReleaseTitle: "river rock", Page: 0, PerPage: 1}
search, _ := client.Search(request)
for _, r := range search.Results {
fmt.Println(r.Title)
}
/*
Someday / I Hate Everything About You
Spy Potion
Surf Attack From Russia
*/
```
etc.
More examples - soon

View File

@@ -1,58 +0,0 @@
package discogs
import (
"github.com/irlndts/go-apirequest"
"net/http"
)
type ArtistService struct {
api *apirequest.API
}
type ArtistParams struct {
Artist_id string
Sort string // year, title, format
Sort_order string // asc, desc
Page int
Per_page int
}
type Artist struct {
Namevariations []string `json:"namevariations"`
Profile string `json:"profile"`
Releases_url string `json:"releases_url"`
Resource_url string `json:"resource_url"`
Uri string `json:"uri"`
Urls []string `json:"urls"`
Data_quality string `json:"data_quality"`
Id int `json:"id"`
Images []Image `json:"images"`
Members []Member `json:"members"`
}
type ArtistReleases struct {
Paginastion Page `json:"pagination"`
Releases []ReleaseSource `json:"releases"`
}
func newArtistService(api *apirequest.API) *ArtistService {
return &ArtistService{
api: api.Path("artists/"),
}
}
func (self *ArtistService) Artist(params *ArtistParams) (*Artist, *http.Response, error) {
artist := new(Artist)
apiError := new(APIError)
resp, err := self.api.New().Get(params.Artist_id).Receive(artist, apiError)
return artist, resp, relevantError(err, *apiError)
}
func (self *ArtistService) Releases(params *ArtistParams) (*ArtistReleases, *http.Response, error) {
releases := new(ArtistReleases)
apiError := new(APIError)
resp, err := self.api.New().Get(params.Artist_id+"/releases").QueryStruct(params).Receive(releases, apiError)
return releases, resp, relevantError(err, *apiError)
}

View File

@@ -1,26 +0,0 @@
package discogs
import (
"fmt"
"testing"
)
func TestArtistService_Artist(t *testing.T) {
expectedId := 1000
d := NewClient().UserAgent("UnitTestClient/0.0.1 +https://github.com/irlndts/go-discogs")
artist, _, err := d.Artist.Artist(&ArtistParams{Artist_id: "1000"})
check(t, err)
assert(t, artist.Id == expectedId, fmt.Sprintf("Release.Title looked for %s, and received %s ", expectedId, artist.Id))
}
func TestArtistService_Releases(t *testing.T) {
expectedArtist := "Dave Clarke"
d := NewClient().UserAgent("UnitTestClient/0.0.1 +https://github.com/irlndts/go-discogs")
releases, _, err := d.Artist.Releases(&ArtistParams{Artist_id: "1000", Sort: "year", Sort_order: "desc"})
check(t, err)
assert(t, releases.Releases[0].Artist == expectedArtist, fmt.Sprintf("Releses.Artist looked for %s, and received %s ", expectedArtist, releases.Releases[0].Artist))
}

View File

@@ -1,143 +0,0 @@
package discogs
type Video struct {
Description string `json:"description"`
Duration int `json:"duration"`
Embed bool `json:"embed"`
Title string `json:"title"`
Uri string `json:"uri"`
}
type ArtistSource struct {
Anv string `json:"anv"`
Id int `json:"id"`
Join string `json:"join"`
Name string `json:"name:`
Resource_url string `json:"resource_url"`
Role string `json:"role"`
Tracks string `json:"tracks"`
}
type Image struct {
Height int `json:"height"`
Width int `json:"width"`
Resource_url string `json:"resource_url"`
Type string `json:"type"`
Uri string `json:"uri"`
Uri150 string `json:"uri150"`
}
type Track struct {
Duration string `json:"duration"`
Position string `json:"position"`
Title string `json:"title"`
Type string `json:"type_"`
Extraartists []ArtistSource `json:"extraartists"`
}
type LabelSource struct {
Catno string `json:"catno"`
Entity_type string `json:"entity_type"`
Id int `json:"id"`
Name string `json:"name"`
Resource_url string `json:"resource_url"`
}
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
type Format struct {
Descriptions []string `json:"descriptions"`
Name string `json:"name"`
Qty string `json:"qty"`
}
type Company struct {
Catno string `json:"catno"`
Entity_type string `json:"entity_type"`
Entity_type_name string `json:"entity_type_name"`
Id int `json:"id"`
Name string `json:"name"`
Resource_url string `json:"resource_url"`
}
type Community struct {
Contributors []Contributor `json:"contributors"`
Data_quality string `json:"string"`
Have int `json:"have"`
Rating Rating `json:"rating"`
Status string `json:"status"`
Submitter Submitter `json:"submitter"`
Want int `json:"want"`
}
type Submitter struct {
Resource_url string `json:"resource_url"`
Username string `json:"username"`
}
type Rating struct {
Average float32 `json:"average"`
Count int `json:"count"`
}
type Contributor struct {
Resource_url string `json:"resource_url"`
Username string `json:"username"`
}
type Page struct {
Per_page int `json:"per_page"`
Items int `json:"items"`
Page int `json:"page"`
Urls URLS `json:"urls"`
Pages int `json:"pages"`
}
type URLS struct {
Last string `json:"last"`
Next string `json:"next"`
}
type Version struct {
Catno string `json:"catno"`
Country string `json:"country"`
Format string `json:"format"`
Id int `json:"id"`
Label string `json:"label"`
Released string `json:"released"`
Resource_url string `json:"resource_url"`
Status string `json:"status"`
Thumb string `json:"thumb"`
Title string `json:"title"`
}
type Member struct {
Active bool `json:"active"`
Id int `json:"id"`
Name string `json:"name"`
Resource_url string `json:"resource_url"`
}
type Sublable struct {
Resource_url string `json:"url"`
Id int `json:"id"`
Name string `json:"name"`
}
type ReleaseSource struct {
Artist string `json:"artist"`
Catno string `json:"catno"`
Format string `json:"format"`
Id int `json:"id"`
Resource_url string `json:"resource_url"`
Status string `json:"status"`
Thumb string `json:"thumb"`
Title string `json:"title"`
Year int `json:"year"`
Main_release int `json:"main_release"`
Role string `json:"role"`
Type string `json:"type"`
}

214
database.go Normal file
View File

@@ -0,0 +1,214 @@
package discogs
import (
"net/url"
"strconv"
)
const (
releasesURI = "/releases/"
artistsURI = "/artists/"
labelsURI = "/labels/"
mastersURI = "/masters/"
)
// DatabaseService ...
type DatabaseService struct {
url string
currency string
}
func newDatabaseService(url string, currency string) *DatabaseService {
return &DatabaseService{
url: url,
currency: currency,
}
}
// Release serves relesase response from discogs
type Release struct {
Title string `json:"title"`
ID int `json:"id"`
Artists []ArtistSource `json:"artists"`
DataQuality string `json:"data_quality"`
Thumb string `json:"thumb"`
Community Community `json:"community"`
Companies []Company `json:"companies"`
Country string `json:"country"`
DateAdded string `json:"date_added"`
DateChanged string `json:"date_changed"`
EstimatedWeight int `json:"estimated_weight"`
ExtraArtists []ArtistSource `json:"extraartists"`
FormatQuantity int `json:"format_quantity"`
Formats []Format `json:"formats"`
Genres []string `json:"genres"`
Identifiers []Identifier `json:"identifiers"`
Images []Image `json:"images"`
Labels []LabelSource `json:"labels"`
LowestPrice float64 `json:"lowest_price"`
MasterID int `json:"master_id"`
MasterURL string `json:"master_url"`
Notes string `json:"notes,omitempty"`
NumForSale int `json:"num_for_sale,omitempty"`
Released string `json:"released"`
ReleasedFormatted string `json:"released_formatted"`
ResourceURL string `json:"resource_url"`
// Series
Status string `json:"status"`
Styles []string `json:"styles"`
Tracklist []Track `json:"tracklist"`
URI string `json:"uri"`
Videos []Video `json:"videos"`
Year int `json:"year"`
}
// Release returns release by release's ID
func (s *DatabaseService) Release(releaseID int) (*Release, error) {
params := url.Values{}
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
}
// ReleaseRating serves response for community release rating request
type ReleaseRating struct {
ID int `json:"release_id"`
Rating Rating `json:"rating"`
}
// 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
}
// Artist ...
type Artist struct {
Namevariations []string `json:"namevariations"`
Profile string `json:"profile"`
ReleasesURL string `json:"releases_url"`
ResourceURL string `json:"resource_url"`
URI string `json:"uri"`
URLs []string `json:"urls"`
DataQuality string `json:"data_quality"`
ID int `json:"id"`
Images []Image `json:"images"`
Members []Member `json:"members"`
}
// 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
}
// ArtistReleases ...
type ArtistReleases struct {
Pagination Page `json:"pagination"`
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) {
var releases *ArtistReleases
if err := request(s.url+artistsURI+strconv.Itoa(artistID)+"/releases", pagination.toParams(), &releases); err != nil {
return nil, err
}
return releases, nil
}
// Label resource represents a label, company, recording studio, location,
// or other entity involved with artists and releases.
type Label struct {
Profile string `json:"profile"`
ReleasesURL string `json:"releases_url"`
Name string `json:"name"`
ContactInfo string `json:"contact_info"`
URI string `json:"uri"`
Sublabels []Sublable `json:"sublabels"`
URLs []string `json:"urls"`
Images []Image `json:"images"`
ResourceURL string `json:"resource_url"`
ID int `json:"id"`
DataQuality string `json:"data_quality"`
}
// 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
}
// LabelReleases is a list of Releases associated with the label.
type LabelReleases struct {
Pagination Page `json:"pagination"`
Releases []ReleaseSource `json:"releases"`
}
// 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
}
// Master resource represents a set of similar releases.
// Masters (also known as `master releases`) have a `main release` which is often the chronologically earliest.
type Master struct {
Styles []string `json:"styles"`
Genres []string `json:"genres"`
Videos []Video `json:"videos"`
Title string `json:"title"`
MainRelease int `json:"main_release"`
MainReleaseURL string `json:"main_release_url"`
URI string `json:"uri"`
Artists []Artist `json:"artists"`
VersionURL string `json:"version_url"`
Year int `json:"year"`
Images []Image `json:"images"`
ResourceURL string `json:"resource_url"`
Tracklist []Track `json:"tracklist"`
ID int `json:"id"`
DataQuality string `json:"data_quality"`
}
// 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
}
// MasterVersions retrieves a list of all releases that are versions of this master.
type MasterVersions struct {
Pagination Page `json:"pagination"`
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) {
var versions *MasterVersions
if err := request(s.url+mastersURI+strconv.Itoa(masterID)+"/versions", pagination.toParams(), &versions); err != nil {
return nil, err
}
return versions, nil
}

30
database_test.go Normal file
View File

@@ -0,0 +1,30 @@
package discogs
import (
"io"
"net/http"
"net/http/httptest"
"testing"
)
func ReleaseServer(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
io.WriteString(w, `{"title":"Elephant Riddim"}`)
}
func TestReleaseServiceRelease(t *testing.T) {
expectedTitle := "Elephant Riddim"
ts := httptest.NewServer(http.HandlerFunc(ReleaseServer))
defer ts.Close()
d := initDiscogsClient(t, &Options{URL: ts.URL})
release, err := d.Database.Release(8138518)
if err != nil {
t.Fatalf("failed to get release: %s", err)
}
if release.Title != expectedTitle {
t.Fatalf("release title got=%s want=%s ", expectedTitle, release.Title)
}
}

View File

@@ -1,39 +1,94 @@
package discogs
import (
"github.com/irlndts/go-apirequest"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
)
const (
discogsAPI = "https://api.discogs.com/"
useragent = "Test UserAgent"
discogsAPI = "https://api.discogs.com"
)
// Options is a set of options to use discogs API client
type Options struct {
URL string
Currency string
UserAgent string
Token string
}
// Client is a Discogs client for making Discogs API requests.
type Client struct {
api *apirequest.API
Release *ReleaseService
Master *MasterService
Artist *ArtistService
Label *LabelService
Database *DatabaseService
Search *SearchService
}
var header *http.Header
// NewClient returns a new Client.
func NewClient() *Client {
base := apirequest.New().Client(&http.Client{}).Base(discogsAPI).Add("User-Agent", useragent)
func NewClient(o *Options) (*Client, error) {
header = &http.Header{}
if o == nil || o.UserAgent == "" {
return nil, ErrUserAgentInvalid
}
header.Add("User-Agent", o.UserAgent)
cur, err := currency(o.Currency)
if err != nil {
return nil, err
}
// set token, it's required for some queries like search
if o.Token != "" {
header.Add("Authorization", "Discogs token="+o.Token)
}
if o.URL == "" {
o.URL = discogsAPI
}
return &Client{
api: base,
Release: newReleaseService(base.New()),
Master: newMasterService(base.New()),
Artist: newArtistService(base.New()),
Label: newLabelService(base.New()),
Database: newDatabaseService(o.URL, cur),
Search: newSearchService(o.URL + "/database/search"),
}, nil
}
// currency validates currency for marketplace data.
// Defaults to the authenticated users currency. Must be one of the following:
// USD GBP EUR CAD AUD JPY CHF MXN BRL NZD SEK ZAR
func currency(c string) (string, error) {
switch c {
case "USD", "GBP", "EUR", "CAD", "AUD", "JPY", "CHF", "MXN", "BRL", "NZD", "SEK", "ZAR":
return c, nil
case "":
return "USD", nil
default:
return "", ErrCurrencyNotSupported
}
}
// discogs require specified user agent
func (c *Client) UserAgent(useragent string) *Client {
c.api.Set("User-Agent", useragent)
return c
func request(path string, params url.Values, resp interface{}) error {
r, err := http.NewRequest("GET", path+"?"+params.Encode(), nil)
if err != nil {
return err
}
r.Header = *header
client := &http.Client{}
response, err := client.Do(r)
if err != nil {
return err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
return json.Unmarshal(body, &resp)
}

View File

@@ -4,14 +4,89 @@ import (
"testing"
)
func check(t *testing.T, e error) {
if e != nil {
t.Error(e)
const (
testUserAgent = "UnitTestClient/0.0.2 +https://github.com/irlndts/go-discogs"
testToken = ""
)
func initDiscogsClient(t *testing.T, options *Options) *Client {
if options == nil {
options = &Options{
UserAgent: testUserAgent,
Currency: "USD",
Token: testToken,
}
}
if options.UserAgent == "" {
options.UserAgent = testUserAgent
}
client, err := NewClient(options)
if err != nil {
t.Fatalf("failed to create client: %s", err)
}
return client
}
func TestNewClient(t *testing.T) {
tests := map[string]struct {
options *Options
err error
}{
"normal": {&Options{
UserAgent: testUserAgent,
Currency: "USD",
Token: "some token",
}, nil},
"incorrect user-agent": {&Options{
UserAgent: "",
Currency: "USD",
}, ErrUserAgentInvalid},
"incorrect currency": {&Options{
UserAgent: testUserAgent,
Currency: "RUR",
}, ErrCurrencyNotSupported},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
if _, err := NewClient(tt.options); err != tt.err {
t.Errorf("err got=%s; want=%s", err, tt.err)
}
})
}
}
func assert(t *testing.T, condition bool, assertion string) {
if !condition {
t.Errorf("Assertion failed: %v", assertion)
func TestCurrency(t *testing.T) {
tests := []struct {
currency string
want string
err error
}{
{currency: "", want: "USD", err: nil},
{currency: "USD", want: "USD", err: nil},
{currency: "GBP", want: "GBP", err: nil},
{currency: "EUR", want: "EUR", err: nil},
{currency: "CAD", want: "CAD", err: nil},
{currency: "AUD", want: "AUD", err: nil},
{currency: "JPY", want: "JPY", err: nil},
{currency: "CHF", want: "CHF", err: nil},
{currency: "MXN", want: "MXN", err: nil},
{currency: "BRL", want: "BRL", err: nil},
{currency: "NZD", want: "NZD", err: nil},
{currency: "SEK", want: "SEK", err: nil},
{currency: "ZAR", want: "ZAR", err: nil},
{currency: "RUR", want: "", err: ErrCurrencyNotSupported},
}
for i, tt := range tests {
cur, err := currency(tt.currency)
if err != tt.err {
t.Errorf("#%d err got=%s; want=%s", i, err, tt.err)
}
if cur != tt.want {
t.Errorf("#%d currency got=%s; want=%s", i, cur, tt.want)
}
}
}

View File

@@ -2,38 +2,20 @@ package discogs
import (
"fmt"
"strings"
)
// APIError represents a Discogs API Error response
type APIError struct {
Message string `json:"message"`
// Error represents a Discogs API error
type Error struct {
Message string
}
func (e APIError) Error() string {
if e.Message != "" {
return fmt.Sprintf("discogs: %v", e.Message)
}
return ""
func (e *Error) Error() string {
return fmt.Sprintf("discogs error: %s", strings.ToLower(e.Message))
}
// Empty returns true if empty. Otherwise, at least 1 error message/code is
// present and false is returned.
func (e APIError) Empty() bool {
if e.Message == "" {
return true
}
return false
}
// relevantError returns any non-nil http-related error (creating the request,
// getting the response, decoding) if any. If the decoded apiError is non-zero
// the apiError is returned. Otherwise, no errors occurred, returns nil.
func relevantError(httpError error, apiError APIError) error {
if httpError != nil {
return httpError
}
if apiError.Empty() {
return nil
}
return apiError
}
// APIErrors
var (
ErrCurrencyNotSupported = &Error{"currency does not supported"}
ErrUserAgentInvalid = &Error{"invalid user-agent"}
)

View File

@@ -2,24 +2,25 @@ package main
import (
"fmt"
"github.com/irlndts/go-discogs"
)
func main() {
d := discogs.NewClient().UserAgent("TestDiscogsClient/0.0.1 +http://irlndts.moscow")
/*
params := &discogs.ReleaseParams{Release_id: "8138518"}
release, _, err := d.Release.Release(params)
*/
params := &discogs.LabelParams{Label_id: "890477", Page: 2, Per_page: 3}
label, _, err := d.Label.Releases(params)
d, err := discogs.NewClient(&discogs.Options{
UserAgent: "TestDiscogsClient/0.0.1 +http://example.com",
Currency: "AAA",
Token: "",
})
if err != nil {
fmt.Println(err)
} else {
for _, release := range label.Releases {
fmt.Println(release.Title)
}
return
}
release, err := d.Database.Release(12345)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", release)
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/irlndts/go-discogs
require github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
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=

View File

@@ -1,57 +0,0 @@
package discogs
import (
"github.com/irlndts/go-apirequest"
"net/http"
)
type LabelService struct {
api *apirequest.API
}
type LabelParams struct {
Label_id string
Page int
Per_page int
}
type Label struct {
Profile string `json:"profile"`
Releases_url string `json:"releases_url"`
Name string `json:"name"`
Contact_info string `json:"contact_info"`
Uri string `json:"uri"`
Sublabels []Sublable `json:"sublabels"`
Urls []string `json:"urls"`
Images []Image `json:"images"`
Resource_url string `json:"resource_url"`
Id int `json:"id"`
Data_quality string `json:"data_quality"`
}
type LabelReleases struct {
Pagination Page `json:"pagination"`
Releases []ReleaseSource `json:"releases"`
}
func newLabelService(api *apirequest.API) *LabelService {
return &LabelService{
api: api.Path("labels/"),
}
}
func (self *LabelService) Label(params *LabelParams) (*Label, *http.Response, error) {
label := new(Label)
apiError := new(APIError)
resp, err := self.api.New().Get(params.Label_id).Receive(label, apiError)
return label, resp, relevantError(err, *apiError)
}
func (self *LabelService) Releases(params *LabelParams) (*LabelReleases, *http.Response, error) {
releases := new(LabelReleases)
apiError := new(APIError)
resp, err := self.api.New().Get(params.Label_id+"/releases").QueryStruct(params).Receive(releases, apiError)
return releases, resp, relevantError(err, *apiError)
}

View File

@@ -1,26 +0,0 @@
package discogs
import (
"fmt"
"testing"
)
func TestLabelService_Label(t *testing.T) {
expectedId := 1000
d := NewClient().UserAgent("UnitTestClient/0.0.1 +https://github.com/irlndts/go-discogs")
label, _, err := d.Label.Label(&LabelParams{Label_id: "1000"})
check(t, err)
assert(t, label.Id == expectedId, fmt.Sprintf("Release.Title looked for %s, and received %s ", expectedId, label.Id))
}
func TestLabelService_Releases(t *testing.T) {
expectedId := "Good Time"
d := NewClient().UserAgent("UnitTestClient/0.0.1 +https://github.com/irlndts/go-discogs")
label, _, err := d.Label.Releases(&LabelParams{Label_id: "1000"})
check(t, err)
assert(t, label.Releases[0].Title == expectedId, fmt.Sprintf("Release.Title looked for %s, and received %s ", expectedId, label.Releases[0].Title))
}

View File

@@ -1,63 +0,0 @@
package discogs
import (
"github.com/irlndts/go-apirequest"
"net/http"
)
type MasterService struct {
api *apirequest.API
}
type MasterParams struct {
Master_id string
}
type MasterVersionParams struct {
Master_id string
Page int
Per_page int
}
type Master struct {
Styles []string `json:"styles"`
Genres []string `json:"genres"`
Videos []Video `json:"videos"`
Title string `json:"title"`
Main_release int `json:"main_release"`
Main_release_url string `json:"main_release_url"`
Uri string `json:"uri"`
Artists []Artist `json:"artists"`
Version_url string `json:"version_url"`
Year int `json:"year"`
Images []Image `json:"images"`
Resource_url string `json:"resource_url"`
Tracklist []Track `json:"tracklist"`
Id int `json:"id"`
Data_quality string `json:"data_quality"`
}
type MasterVersions struct {
Pagination Page `json:"pagination"`
Versions []Version `json:"versions"`
}
func newMasterService(api *apirequest.API) *MasterService {
return &MasterService{
api: api.Path("masters/"),
}
}
func (self *MasterService) Master(params *MasterParams) (*Master, *http.Response, error) {
master := new(Master)
apiError := new(APIError)
resp, err := self.api.New().Get(params.Master_id).Receive(master, apiError)
return master, resp, relevantError(err, *apiError)
}
func (self *MasterService) Versions(params *MasterVersionParams) (*MasterVersions, *http.Response, error) {
versions := new(MasterVersions)
apiError := new(APIError)
resp, err := self.api.New().Get(params.Master_id+"/versions").QueryStruct(params).Receive(versions, apiError)
return versions, resp, relevantError(err, *apiError)
}

View File

@@ -1,26 +0,0 @@
package discogs
import (
"fmt"
"testing"
)
func TestMasterService_Master(t *testing.T) {
expectedTitle := "Elephant Riddim"
d := NewClient().UserAgent("UnitTestClient/0.0.1 +https://github.com/irlndts/go-discogs")
master, _, err := d.Master.Master(&MasterParams{Master_id: "960657"})
check(t, err)
assert(t, master.Title == expectedTitle, fmt.Sprintf("master.Title looked for %s, and received %s ", expectedTitle, master.Title))
}
func TestMasterService_Versions(t *testing.T) {
expectedTitle := "Stardiver"
d := NewClient().UserAgent("UnitTestClient/0.0.1 +https://github.com/irlndts/go-discogs")
versions, _, err := d.Master.Versions(&MasterVersionParams{Master_id: "1000", Page: 1, Per_page: 1})
check(t, err)
assert(t, versions.Versions[0].Title == expectedTitle, fmt.Sprintf("master.Title looked for %s, and received %s ", expectedTitle, versions.Versions[0].Title))
}

197
models.go Normal file
View File

@@ -0,0 +1,197 @@
package discogs
import (
"net/url"
"strconv"
)
// Video ...
type Video struct {
Description string `json:"description"`
Duration int `json:"duration"`
Embed bool `json:"embed"`
Title string `json:"title"`
URI string `json:"uri"`
}
// ArtistSource ...
type ArtistSource struct {
Anv string `json:"anv"`
ID int `json:"id"`
Join string `json:"join"`
Name string `json:"name"`
ResourceURL string `json:"resource_url"`
Role string `json:"role"`
Tracks string `json:"tracks"`
}
// Image ...
type Image struct {
Height int `json:"height"`
Width int `json:"width"`
ResourceURL string `json:"resource_url"`
Type string `json:"type"`
URI string `json:"uri"`
URI150 string `json:"uri150"`
}
// Track ...
type Track struct {
Duration string `json:"duration"`
Position string `json:"position"`
Title string `json:"title"`
Type string `json:"type_"`
Extraartists []ArtistSource `json:"extraartists"`
Artists []ArtistSource `json:"artists"`
}
// LabelSource ...
type LabelSource struct {
Catno string `json:"catno"`
EntityType string `json:"entity_type"`
ID int `json:"id"`
Name string `json:"name"`
ResourceURL string `json:"resource_url"`
}
// Identifier ...
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
// Format ...
type Format struct {
Descriptions []string `json:"descriptions"`
Name string `json:"name"`
Qty string `json:"qty"`
}
// Company ...
type Company 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"`
}
// Community ...
type Community struct {
Contributors []Contributor `json:"contributors"`
DataQuality string `json:"string"`
Have int `json:"have"`
Rating Rating `json:"rating"`
Status string `json:"status"`
Submitter Submitter `json:"submitter"`
Want int `json:"want"`
}
// Submitter ...
type Submitter struct {
ResourceURL string `json:"resource_url"`
Username string `json:"username"`
}
// Rating ...
type Rating struct {
Average float32 `json:"average"`
Count int `json:"count"`
}
// Contributor ...
type Contributor struct {
ResourceURL string `json:"resource_url"`
Username string `json:"username"`
}
// Page ...
type Page struct {
PerPage int `json:"per_page"`
Items int `json:"items"`
Page int `json:"page"`
URLs URLsList `json:"urls"`
Pages int `json:"pages"`
}
// URLsList ...
type URLsList struct {
Last string `json:"last"`
Next string `json:"next"`
}
// Version ...
type Version struct {
Catno string `json:"catno"`
Country string `json:"country"`
Format string `json:"format"`
ID int `json:"id"`
Label string `json:"label"`
Released string `json:"released"`
ResourceURL string `json:"resource_url"`
Status string `json:"status"`
Thumb string `json:"thumb"`
Title string `json:"title"`
}
// Member ...
type Member struct {
Active bool `json:"active"`
ID int `json:"id"`
Name string `json:"name"`
ResourceURL string `json:"resource_url"`
}
// Sublable ...
type Sublable struct {
ResourceURL string `json:"url"`
ID int `json:"id"`
Name string `json:"name"`
}
// ReleaseSource ...
type ReleaseSource struct {
Artist string `json:"artist"`
Catno string `json:"catno"`
Format string `json:"format"`
ID int `json:"id"`
ResourceURL string `json:"resource_url"`
Status string `json:"status"`
Thumb string `json:"thumb"`
Title string `json:"title"`
Year int `json:"year"`
MainRelease int `json:"main_release"`
Role string `json:"role"`
Type string `json:"type"`
}
// Pagination ...
type Pagination struct {
Sort string // year, title, format
SortOrder string // asc, desc
Page int
PerPage int
}
// toParams converts pagaination params to request values
func (p *Pagination) toParams() 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))
}
return params
}

View File

@@ -1,61 +0,0 @@
package discogs
import (
"github.com/irlndts/go-apirequest"
"net/http"
)
type ReleaseService struct {
api *apirequest.API
}
type ReleaseParams struct {
Release_id string
}
type Release struct {
Title string `json:"title"`
Id int `json:"id"`
Artists []ArtistSource `json:"artists"`
Data_quality string `json:"data_quality"`
Thumb string `json:"thumb"`
Community Community `json:"community"`
Companies []Company `json:"companies"`
Country string `json:"country"`
Date_added string `json:"date_added"`
Date_changed string `json:"date_changed"`
Estimated_weight int `json:"estimated_weight"`
Extraartists []ArtistSource `json:"extraartists"`
Format_quantity int `json:"format_quantity"`
Formats []Format `json:"formats"`
Genres []string `json:"genres"`
Identifiers []Identifier `json:"identifiers"`
Images []Image `json:"images"`
Labels []LabelSource `json:"labels"`
Master_id int `json:"master_id"`
Master_url string `json:"master_url"`
Notes string `josn:"notes"`
Released string `json:"released"`
Released_formatted string `json:"released_formatted"`
Resource_url string `json:"resource_url"`
Status string `json:"status"`
Styles []string `json:"styles"`
Tracklist []Track `json:"tracklist"`
Uri string `json:"uri"`
Videos []Video `json:"videos"`
Year int `json:"year"`
}
func newReleaseService(api *apirequest.API) *ReleaseService {
return &ReleaseService{
api: api.Path("releases/"),
}
}
func (self *ReleaseService) Release(params *ReleaseParams) (*Release, *http.Response, error) {
release := new(Release)
apiError := new(APIError)
resp, err := self.api.New().Get(params.Release_id).Receive(release, apiError)
return release, resp, relevantError(err, *apiError)
}

View File

@@ -1,16 +0,0 @@
package discogs
import (
"fmt"
"testing"
)
func TestReleaseService_Release(t *testing.T) {
expectedTitle := "Elephant Riddim"
d := NewClient().UserAgent("UnitTestClient/0.0.1 +https://github.com/irlndts/go-discogs")
release, _, err := d.Release.Release(&ReleaseParams{Release_id: "8138518"})
check(t, err)
assert(t, release.Title == expectedTitle, fmt.Sprintf("Release.Title looked for %s, and received %s ", expectedTitle, release.Title))
}

83
search.go Normal file
View File

@@ -0,0 +1,83 @@
package discogs
import (
"github.com/google/go-querystring/query"
)
// SearchService ...
type SearchService struct {
url string
}
func newSearchService(url string) *SearchService {
return &SearchService{
url: url,
}
}
// 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
Page int `url:"page,omitempty"`
PerPage int `url:"per_page,omitempty"`
}
// Search describes search response
type Search struct {
Pagination Page `json:"pagination"`
Results []Result `json:"results,omitempty"`
}
// Result describes a part of search result
type Result struct {
Style []string `json:"style,omitempty"`
Thumb string `json:"thumb,omitempty"`
Title string `json:"title,omitempty"`
Country string `json:"country,omitempty"`
Format []string `json:"format,omitempty"`
URI string `json:"uri,omitempty"`
Community Community `json:"community,omitempty"`
Label []string `json:"label,omitempty"`
Catno string `json:"catno,omitempty"`
Year string `json:"year,omitempty"`
Genre []string `json:"genre,omitempty"`
ResourceURL string `json:"resource_url,omitempty"`
Type string `json:"type,omitempty"`
ID int `json:"id,omitempty"`
}
// Search makes search request to discogs.
// 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
}

27
vendor/github.com/google/go-querystring/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
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.

320
vendor/github.com/google/go-querystring/query/encode.go generated vendored Normal file
View File

@@ -0,0 +1,320 @@
// 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
}

2
vendor/modules.txt vendored Normal file
View File

@@ -0,0 +1,2 @@
# github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135
github.com/google/go-querystring/query