diff --git a/go.mod b/go.mod index 136093a..43660a8 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ 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.0 diff --git a/go.sum b/go.sum index a6ddb1d..7a664be 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/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= diff --git a/models.go b/models.go index 94ba473..bfc336e 100644 --- a/models.go +++ b/models.go @@ -22,7 +22,7 @@ type Series struct { ID int `json:"id"` Name string `json:"name"` ResourceURL string `json:"resource_url"` - ThumbnailURL string `json:"thumbnail_url"` + ThumbnailURL string `json:"thumbnail_url,omitempty"` } // ArtistSource ... @@ -68,7 +68,7 @@ type LabelSource struct { // Identifier ... type Identifier struct { - Description string `json:"description"` + Description string `json:"description,omitempty"` Type string `json:"type"` Value string `json:"value"` } diff --git a/testing_data.go b/testing_data.go index 6062f16..0e0ce97 100644 --- a/testing_data.go +++ b/testing_data.go @@ -4,6 +4,6 @@ package discogs const masterJson = `{"styles": ["Hardcore Hip-Hop", "Boom Bap"], "genres": ["Hip Hop"], "videos": [{"duration": 233, "embed": true, "title": "Eminem - It's Ok 1996", "description": "Eminem - It's Ok 1996", "uri": "https://www.youtube.com/watch?v=EKOPq3pDQBM"}, {"duration": 244, "embed": true, "title": "Eminem - Infinite [Official Audio 1996]", "description": "Eminem - Infinite [Official Audio 1996]", "uri": "https://www.youtube.com/watch?v=T8eA7SRTb7Y"}, {"duration": 4617, "embed": true, "title": "Infinite (Europe Reissue) by Eminem [Full Album]", "description": "Infinite (Europe Reissue) by Eminem [Full Album]", "uri": "https://www.youtube.com/watch?v=BzU-rw2t9FY"}], "num_for_sale": 4, "title": "Infinite", "most_recent_release": 10670860, "main_release": 3221262, "notes": "Infinite is the first Eminem album released on November 12, 1996, by Web Entertainment, on vinyl and cassette.", "main_release_url": "https://api.discogs.com/releases/3221262", "year": 1996, "uri": "https://www.discogs.com/Eminem-Infinite/master/718441", "versions_url": "https://api.discogs.com/masters/718441/versions", "tracklist": [{"duration": "4:03", "position": "A1", "type_": "track", "title": "Infinite"}, {"duration": "0:21", "position": "A2", "type_": "track", "extraartists": [{"join": "", "name": "DJ Head", "anv": "", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/181268", "id": 181268}, {"join": "", "name": "Proof (3)", "anv": "", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/181319", "id": 181319}], "title": "W.E.G.O."}, {"duration": "3:32", "position": "A3", "type_": "track", "extraartists": [{"join": "", "name": "Eye-Kyu", "anv": "Eiy-Kyu", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/181265", "id": 181265}], "title": "It's O.K."}, {"duration": "3:45", "position": "A4", "type_": "track", "extraartists": [{"join": "", "name": "DJ Butterfingers", "anv": "D.J. Butterfingers", "tracks": "", "role": "Scratches", "resource_url": "https://api.discogs.com/artists/553092", "id": 553092}], "title": "Tonite"}, {"duration": "4:13", "position": "A5", "type_": "track", "extraartists": [{"join": "", "name": "Eye-Kyu", "anv": "Eiy-Kyu", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/181265", "id": 181265}], "title": "313"}, {"duration": "3:57", "position": "A6", "type_": "track", "extraartists": [{"join": "", "name": "3", "anv": "Three", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/56120", "id": 56120}, {"join": "", "name": "Denaun Porter", "anv": "", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/176778", "id": 176778}], "title": "Maxine"}, {"duration": "4:03", "position": "B1", "type_": "track", "extraartists": [{"join": "", "name": "Thyme", "anv": "", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/181266", "id": 181266}, {"join": "", "name": "Denaun Porter", "anv": "", "tracks": "", "role": "Voice [Uncredited]", "resource_url": "https://api.discogs.com/artists/176778", "id": 176778}, {"join": "", "name": "Kuniva", "anv": "", "tracks": "", "role": "Voice [Uncredited]", "resource_url": "https://api.discogs.com/artists/333749", "id": 333749}], "title": "Open Mic"}, {"duration": "3:40", "position": "B2", "type_": "track", "extraartists": [{"join": "", "name": "Denaun Porter", "anv": "", "tracks": "", "role": "Voice [Uncredited]", "resource_url": "https://api.discogs.com/artists/176778", "id": 176778}], "title": "Never 2 Far"}, {"duration": "3:46", "position": "B3", "type_": "track", "extraartists": [{"join": "", "name": "Eye-Kyu", "anv": "Eiy-Kyu", "tracks": "", "role": "Featuring", "resource_url": "https://api.discogs.com/artists/181265", "id": 181265}, {"join": "", "name": "Angela Workman", "anv": "", "tracks": "", "role": "Vocals", "resource_url": "https://api.discogs.com/artists/189696", "id": 189696}], "title": "Searchin"}, {"duration": "3:26", "position": "B4", "type_": "track", "extraartists": [{"join": "", "name": "Proof (3)", "anv": "", "tracks": "", "role": "Vocals [Uncredited]", "resource_url": "https://api.discogs.com/artists/181319", "id": 181319}], "title": "Backstabber"}, {"duration": "3:23", "position": "B5", "type_": "track", "extraartists": [{"join": "", "name": "Denaun Porter", "anv": "", "tracks": "", "role": "Voice [Uncredited]", "resource_url": "https://api.discogs.com/artists/176778", "id": 176778}, {"join": "", "name": "Eye-Kyu", "anv": "Eiy-Kyu", "tracks": "", "role": "Voice [Uncredited]", "resource_url": "https://api.discogs.com/artists/181265", "id": 181265}, {"join": "", "name": "Proof (3)", "anv": "", "tracks": "", "role": "Voice [Uncredited]", "resource_url": "https://api.discogs.com/artists/181319", "id": 181319}], "title": "Jealousy Woes II"}], "most_recent_release_url": "https://api.discogs.com/releases/10670860", "artists": [{"join": "", "name": "Eminem", "anv": "", "tracks": "", "role": "", "resource_url": "https://api.discogs.com/artists/38661", "id": 38661}], "images": [{"uri": "", "height": 961, "width": 600, "resource_url": "", "type": "primary", "uri150": ""}, {"uri": "", "height": 840, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 306, "width": 382, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}], "resource_url": "https://api.discogs.com/masters/718441", "lowest_price": 23.16, "id": 718441, "data_quality": "Correct"}` -const releaseJson = `{"styles": ["Ska", "Reggae"], "videos": [{"duration": 301, "description": "ST.PETERSBURG SKA JAZZ REVIEW - WATER TAXI (BalconyTV)", "embed": true, "uri": "https://www.youtube.com/watch?v=i4_kwCTrTRs", "title": "ST.PETERSBURG SKA JAZZ REVIEW - WATER TAXI (BalconyTV)"}, {"duration": 292, "description": "St.Petersburg Ska-Jazz Review - Action Movie", "embed": true, "uri": "https://www.youtube.com/watch?v=IaQA8uiZUUc", "title": "St.Petersburg Ska-Jazz Review - Action Movie"}, {"duration": 320, "description": "St.Petersburg Ska-Jazz Review - Misterioso", "embed": true, "uri": "https://www.youtube.com/watch?v=2u5UtZNXugc", "title": "St.Petersburg Ska-Jazz Review - Misterioso"}, {"duration": 209, "description": "St.Petersburg Ska-Jazz Review - Perfidia", "embed": true, "uri": "https://www.youtube.com/watch?v=s3m6QY_JKnE", "title": "St.Petersburg Ska-Jazz Review - Perfidia"}, {"duration": 201, "description": "St.Petersburg Ska-Jazz Review - Volga River Boat Man", "embed": true, "uri": "https://www.youtube.com/watch?v=d-I-4O6JrMs", "title": "St.Petersburg Ska-Jazz Review - Volga River Boat Man"}], "series": [], "labels": [{"name": "Magnetic Loft Records", "entity_type": "1", "catno": "MLR-007", "resource_url": "https://api.discogs.com/labels/890477", "id": 890477, "entity_type_name": "Label"}], "year": 2016, "community": {"status": "Accepted", "rating": {"count": 11, "average": 4.91}, "have": 73, "contributors": [{"username": "magnetic-loft-music", "resource_url": "https://api.discogs.com/users/magnetic-loft-music"}, {"username": "Shveiker", "resource_url": "https://api.discogs.com/users/Shveiker"}], "want": 18, "submitter": {"username": "magnetic-loft-music", "resource_url": "https://api.discogs.com/users/magnetic-loft-music"}, "data_quality": "Needs Vote"}, "artists": [{"join": "", "name": "St. Petersburg Ska-Jazz Review", "anv": "SPB Ska-Jazz Review", "tracks": "", "role": "", "resource_url": "https://api.discogs.com/artists/794217", "id": 794217}], "images": [{"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "primary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}], "format_quantity": 1, "id": 8138518, "artists_sort": "St. Petersburg Ska-Jazz Review", "genres": ["Jazz", "Reggae"], "thumb": "", "num_for_sale": 8, "title": "Elephant Riddim", "date_changed": "2018-01-30T13:32:46-08:00", "master_id": 960657, "lowest_price": 10.0, "status": "Accepted", "released_formatted": "18 Feb 2016", "estimated_weight": 230, "master_url": "https://api.discogs.com/masters/960657", "released": "2016-02-18", "date_added": "2016-02-19T01:49:21-08:00", "tracklist": [{"duration": "", "position": "A1", "type_": "track", "title": "Action Movie"}, {"duration": "", "position": "A2", "type_": "track", "title": "Elephant Riddim"}, {"duration": "", "position": "A3", "type_": "track", "title": "Ceora"}, {"duration": "", "position": "A4", "type_": "track", "title": "Doop"}, {"duration": "", "position": "A5", "type_": "track", "title": "52d Street Theme"}, {"duration": "", "position": "B1", "type_": "track", "title": "Fly Away"}, {"duration": "", "position": "B2", "type_": "track", "title": "Water Taxi"}, {"duration": "", "position": "B3", "type_": "track", "title": "Misterioso"}, {"duration": "", "position": "B4", "type_": "track", "title": "Keep On Going"}, {"duration": "", "position": "B5", "type_": "track", "title": "Filho Maravilha / Taj Mahal"}], "extraartists": [{"join": "", "name": "Michael Gavrichkov", "anv": "", "tracks": "", "role": "Artwork By", "resource_url": "https://api.discogs.com/artists/4540627", "id": 4540627}, {"join": "", "name": "Stu Allotropia", "anv": "", "tracks": "", "role": "Design", "resource_url": "https://api.discogs.com/artists/4894261", "id": 4894261}], "country": "Russia", "identifiers": [{"type": "Matrix / Runout", "value": "134985E1/A"}, {"type": "Matrix / Runout", "value": "134985E2/A"}], "companies": [{"name": "GZ Media", "entity_type": "17", "catno": "134985E", "resource_url": "https://api.discogs.com/labels/430654", "id": 430654, "entity_type_name": "Pressed By"}], "uri": "https://www.discogs.com/SPB-Ska-Jazz-Review-Elephant-Riddim/release/8138518", "formats": [{"descriptions": ["LP", "Album", "Stereo"], "name": "Vinyl", "qty": "1"}], "resource_url": "https://api.discogs.com/releases/8138518", "data_quality": "Needs Vote"}` +const releaseJson = `{"styles": ["Ska", "Reggae"], "videos": [{"duration": 301, "description": "ST.PETERSBURG SKA JAZZ REVIEW - WATER TAXI (BalconyTV)", "embed": true, "uri": "https://www.youtube.com/watch?v=i4_kwCTrTRs", "title": "ST.PETERSBURG SKA JAZZ REVIEW - WATER TAXI (BalconyTV)"}, {"duration": 292, "description": "St.Petersburg Ska-Jazz Review - Action Movie", "embed": true, "uri": "https://www.youtube.com/watch?v=IaQA8uiZUUc", "title": "St.Petersburg Ska-Jazz Review - Action Movie"}, {"duration": 320, "description": "St.Petersburg Ska-Jazz Review - Misterioso", "embed": true, "uri": "https://www.youtube.com/watch?v=2u5UtZNXugc", "title": "St.Petersburg Ska-Jazz Review - Misterioso"}, {"duration": 209, "description": "St.Petersburg Ska-Jazz Review - Perfidia", "embed": true, "uri": "https://www.youtube.com/watch?v=s3m6QY_JKnE", "title": "St.Petersburg Ska-Jazz Review - Perfidia"}, {"duration": 201, "description": "St.Petersburg Ska-Jazz Review - Volga River Boat Man", "embed": true, "uri": "https://www.youtube.com/watch?v=d-I-4O6JrMs", "title": "St.Petersburg Ska-Jazz Review - Volga River Boat Man"}], "series": [{"name": "Original Jazz Classics", "entity_type": "2", "catno": "", "resource_url": "https://api.discogs.com/labels/34231", "id": 34231, "entity_type_name": "Series"}], "labels": [{"name": "Magnetic Loft Records", "entity_type": "1", "catno": "MLR-007", "resource_url": "https://api.discogs.com/labels/890477", "id": 890477, "entity_type_name": "Label"}], "year": 2016, "community": {"status": "Accepted", "rating": {"count": 11, "average": 4.91}, "have": 73, "contributors": [{"username": "magnetic-loft-music", "resource_url": "https://api.discogs.com/users/magnetic-loft-music"}, {"username": "Shveiker", "resource_url": "https://api.discogs.com/users/Shveiker"}], "want": 18, "submitter": {"username": "magnetic-loft-music", "resource_url": "https://api.discogs.com/users/magnetic-loft-music"}, "data_quality": "Needs Vote"}, "artists": [{"join": "", "name": "St. Petersburg Ska-Jazz Review", "anv": "SPB Ska-Jazz Review", "tracks": "", "role": "", "resource_url": "https://api.discogs.com/artists/794217", "id": 794217}], "images": [{"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "primary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}], "format_quantity": 1, "id": 8138518, "artists_sort": "St. Petersburg Ska-Jazz Review", "genres": ["Jazz", "Reggae"], "thumb": "", "num_for_sale": 8, "title": "Elephant Riddim", "date_changed": "2018-01-30T13:32:46-08:00", "master_id": 960657, "lowest_price": 10.0, "status": "Accepted", "released_formatted": "18 Feb 2016", "estimated_weight": 230, "master_url": "https://api.discogs.com/masters/960657", "released": "2016-02-18", "date_added": "2016-02-19T01:49:21-08:00", "tracklist": [{"duration": "", "position": "A1", "type_": "track", "title": "Action Movie"}, {"duration": "", "position": "A2", "type_": "track", "title": "Elephant Riddim"}, {"duration": "", "position": "A3", "type_": "track", "title": "Ceora"}, {"duration": "", "position": "A4", "type_": "track", "title": "Doop"}, {"duration": "", "position": "A5", "type_": "track", "title": "52d Street Theme"}, {"duration": "", "position": "B1", "type_": "track", "title": "Fly Away"}, {"duration": "", "position": "B2", "type_": "track", "title": "Water Taxi"}, {"duration": "", "position": "B3", "type_": "track", "title": "Misterioso"}, {"duration": "", "position": "B4", "type_": "track", "title": "Keep On Going"}, {"duration": "", "position": "B5", "type_": "track", "title": "Filho Maravilha / Taj Mahal"}], "extraartists": [{"join": "", "name": "Michael Gavrichkov", "anv": "", "tracks": "", "role": "Artwork By", "resource_url": "https://api.discogs.com/artists/4540627", "id": 4540627}, {"join": "", "name": "Stu Allotropia", "anv": "", "tracks": "", "role": "Design", "resource_url": "https://api.discogs.com/artists/4894261", "id": 4894261}], "country": "Russia", "identifiers": [{"type": "Matrix / Runout", "value": "134985E1/A", "description": "Side A - handwritten etched"}, {"type": "Matrix / Runout", "value": "134985E2/A"}], "companies": [{"name": "GZ Media", "entity_type": "17", "catno": "134985E", "resource_url": "https://api.discogs.com/labels/430654", "id": 430654, "entity_type_name": "Pressed By"}], "uri": "https://www.discogs.com/SPB-Ska-Jazz-Review-Elephant-Riddim/release/8138518", "formats": [{"descriptions": ["LP", "Album", "Stereo"], "name": "Vinyl", "qty": "1"}], "resource_url": "https://api.discogs.com/releases/8138518", "data_quality": "Needs Vote"}` const artistJson = `{"profile": "Marshall Bruce Mathers III (born October 17, 1972, St. Joseph, Missouri), known by his primary stage name Eminem, or by his alter ego Slim Shady, is an American rapper and record producer who grew up in Detroit, Michigan. He began his professional music career as a member of Soul Intent along with Proof in 1992. He also started his first record label with his group that same year called Mashin' Duck Records.", "realname": "Marshall Bruce Mathers III", "releases_url": "https://api.discogs.com/artists/38661/releases", "name": "Eminem", "uri": "https://www.discogs.com/artist/38661-Eminem", "urls": ["http://www.eminem.com", "http://www.instagram.com/eminem", "http://twitter.com/Eminem", "https://twitter.com/AskAboutREVIVAL", "http://www.facebook.com/eminem", "http://www.imdb.com/name/nm0004896", "http://www.myspace.com/eminem", "https://www.youtube.com/user/EminemMusic", "https://www.youtube.com/user/EminemVEVO", "https://www.filmo.gs/credit/16526-eminem", "https://www.bookogs.com/credit/229267-eminem", "http://eminem.tumblr.com", "http://en.wikipedia.org/wiki/Eminem", "http://equipboard.com/pros/eminem", "https://genius.com/eminem"], "images": [{"uri": "", "height": 607, "width": 600, "resource_url": "", "type": "primary", "uri150": ""}, {"uri": "", "height": 610, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 625, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 503, "width": 409, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 652, "width": 452, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 326, "width": 251, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 397, "width": 441, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 348, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 442, "width": 319, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 740, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 446, "width": 299, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 288, "width": 288, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 360, "width": 468, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 372, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 404, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 444, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 604, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 642, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 253, "width": 199, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 550, "width": 400, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 160, "width": 236, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 821, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 258, "width": 195, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 746, "width": 517, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 170, "width": 220, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 500, "width": 300, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 347, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 281, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 552, "width": 435, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 444, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 507, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 488, "width": 300, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 409, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 515, "width": 578, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 387, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 310, "width": 266, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 800, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 613, "width": 454, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 751, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 657, "width": 485, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 543, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 490, "width": 376, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 403, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 480, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 532, "width": 415, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 500, "width": 444, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 256, "width": 256, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 718, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 440, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 905, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 300, "width": 202, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 552, "width": 435, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 578, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}], "resource_url": "https://api.discogs.com/artists/38661", "aliases": [{"resource_url": "https://api.discogs.com/artists/108184", "id": 108184, "name": "Slim Shady"}, {"resource_url": "https://api.discogs.com/artists/644153", "id": 644153, "name": "Marshall Mathers"}, {"resource_url": "https://api.discogs.com/artists/787714", "id": 787714, "name": "Ken Kaniff"}], "id": 38661, "data_quality": "Needs Vote", "namevariations": ["E. Minem", "Em", "Emiem", "Emine", "EMINEM", "Eminem Show", "Eminen", "Enimen", "M & M", "M. Mathers", "M.N.M", "M&M", "MC Double M", "\u30a8\u30df\u30cd\u30e0"]}` diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go index 2133562..c9a63ce 100644 --- a/vendor/github.com/google/go-cmp/cmp/compare.go +++ b/vendor/github.com/google/go-cmp/cmp/compare.go @@ -22,8 +22,8 @@ // equality is determined by recursively comparing the primitive kinds on both // values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported // fields are not compared by default; they result in panics unless suppressed -// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared -// using the AllowUnexported option. +// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly +// compared using the Exporter option. package cmp import ( @@ -62,8 +62,8 @@ import ( // // Structs are equal if recursively calling Equal on all fields report equal. // If a struct contains unexported fields, Equal panics unless an Ignore option -// (e.g., cmpopts.IgnoreUnexported) ignores that field or the AllowUnexported -// option explicitly permits comparing the unexported field. +// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option +// explicitly permits comparing the unexported field. // // 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. @@ -80,6 +80,11 @@ import ( // Pointers and interfaces are equal if they are both nil or both non-nil, // where they have the same underlying concrete type and recursively // 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 { vx := reflect.ValueOf(x) vy := reflect.ValueOf(y) @@ -137,6 +142,7 @@ type state struct { // Calling statelessCompare must not result in observable changes to these. result diff.Result // The current result of comparison curPath Path // The current path in the value tree + curPtrs pointerPath // The current set of visited pointers reporters []reporter // Optional reporters // recChecker checks for infinite cycles applying the same set of @@ -148,13 +154,14 @@ type state struct { dynChecker dynChecker // These fields, once set by processOption, will not change. - exporters map[reflect.Type]bool // Set of structs with unexported field visibility - opts Options // List of all fundamental and filter options + exporters []exporter // List of exporters for structs with unexported fields + opts Options // List of all fundamental and filter options } func newState(opts []Option) *state { // Always ensure a validator option exists to validate the inputs. s := &state{opts: Options{validator{}}} + s.curPtrs.Init() s.processOption(Options(opts)) return s } @@ -174,13 +181,8 @@ func (s *state) processOption(opt Option) { panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt)) } s.opts = append(s.opts, opt) - case visibleStructs: - if s.exporters == nil { - s.exporters = make(map[reflect.Type]bool) - } - for t := range opt { - s.exporters[t] = true - } + case exporter: + s.exporters = append(s.exporters, opt) case reporter: s.reporters = append(s.reporters, opt) default: @@ -192,9 +194,9 @@ func (s *state) processOption(opt Option) { // This function is stateless in that it does not alter the current result, // or output to any registered reporters. func (s *state) statelessCompare(step PathStep) diff.Result { - // We do not save and restore the curPath because all of the compareX - // methods should properly push and pop from the path. - // It is an implementation bug if the contents of curPath differs from + // We do not save and restore curPath and curPtrs because all of the + // compareX methods should properly push and pop from them. + // It is an implementation bug if the contents of the paths differ from // when calling this function to when returning from it. oldResult, oldReporters := s.result, s.reporters @@ -216,9 +218,17 @@ func (s *state) compareAny(step PathStep) { } s.recChecker.Check(s.curPath) - // Obtain the current type and values. + // Cycle-detection for slice elements (see NOTE in compareSlice). t := step.Type() 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. if s.tryOptions(t, vx, vy) { @@ -354,6 +364,7 @@ func sanitizeValue(v reflect.Value, t reflect.Type) 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 mayForce, mayForceInit bool step := StructField{&structField{}} for i := 0; i < t.NumField(); i++ { step.typ = t.Field(i).Type @@ -375,7 +386,13 @@ func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { vax = makeAddressable(vx) 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.pvy = vay step.field = t.Field(i) @@ -391,9 +408,21 @@ func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) { 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 { if ix >= 0 { step.vx, step.xkey = vx.Index(ix), ix @@ -470,7 +499,12 @@ func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) { 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 // comparisons in a deterministic order. @@ -507,7 +541,12 @@ func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) { 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() s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}}) diff --git a/vendor/github.com/google/go-cmp/cmp/export_panic.go b/vendor/github.com/google/go-cmp/cmp/export_panic.go index abc3a1c..dd03235 100644 --- a/vendor/github.com/google/go-cmp/cmp/export_panic.go +++ b/vendor/github.com/google/go-cmp/cmp/export_panic.go @@ -8,8 +8,8 @@ package cmp import "reflect" -const supportAllowUnexported = false +const supportExporters = false func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value { - panic("retrieveUnexportedField is not implemented") + panic("no support for forcibly accessing unexported fields") } diff --git a/vendor/github.com/google/go-cmp/cmp/export_unsafe.go b/vendor/github.com/google/go-cmp/cmp/export_unsafe.go index 59d4ee9..57020e2 100644 --- a/vendor/github.com/google/go-cmp/cmp/export_unsafe.go +++ b/vendor/github.com/google/go-cmp/cmp/export_unsafe.go @@ -11,7 +11,7 @@ import ( "unsafe" ) -const supportAllowUnexported = true +const supportExporters = true // retrieveUnexportedField uses unsafe to forcibly retrieve any field from // 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 // describing the field to retrieve. 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() } diff --git a/vendor/github.com/google/go-cmp/cmp/options.go b/vendor/github.com/google/go-cmp/cmp/options.go index 7934481..abbd2a6 100644 --- a/vendor/github.com/google/go-cmp/cmp/options.go +++ b/vendor/github.com/google/go-cmp/cmp/options.go @@ -225,8 +225,20 @@ func (validator) apply(s *state, vx, vy reflect.Value) { // Unable to Interface implies unexported field without visibility access. 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" - panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help)) + 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" + 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") @@ -360,9 +372,8 @@ func (cm comparer) String() string { return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) } -// AllowUnexported returns an Option that forcibly allows operations on -// unexported fields in certain structs, which are specified by passing in a -// value of each struct type. +// Exporter returns an Option that specifies whether Equal is allowed to +// introspect into the unexported fields of certain struct types. // // Users of this option must understand that comparing on unexported fields // 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 // all unexported fields on specified struct types. -func AllowUnexported(types ...interface{}) Option { - if !supportAllowUnexported { - panic("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS") +func Exporter(f func(reflect.Type) bool) Option { + if !supportExporters { + 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) for _, typ := range types { t := reflect.TypeOf(typ) @@ -398,13 +423,7 @@ func AllowUnexported(types ...interface{}) Option { } m[t] = true } - return visibleStructs(m) -} - -type visibleStructs map[reflect.Type]bool - -func (visibleStructs) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { - panic("not implemented") + return exporter(func(t reflect.Type) bool { return m[t] }) } // Result represents the comparison result for a single node and @@ -436,6 +455,11 @@ func (r Result) ByFunc() bool { 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 const ( @@ -446,6 +470,7 @@ const ( reportByIgnore reportByMethod reportByFunc + reportByCycle ) // Reporter is an Option that can be passed to Equal. When Equal traverses diff --git a/vendor/github.com/google/go-cmp/cmp/path.go b/vendor/github.com/google/go-cmp/cmp/path.go index 96fffd2..509d6b8 100644 --- a/vendor/github.com/google/go-cmp/cmp/path.go +++ b/vendor/github.com/google/go-cmp/cmp/path.go @@ -10,6 +10,8 @@ import ( "strings" "unicode" "unicode/utf8" + + "github.com/google/go-cmp/cmp/internal/value" ) // 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: // • For StructField, both are not interface-able if the current field // 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 // either the x or y slice. // • For MapIndex, one may be invalid if an entry is missing from @@ -207,6 +209,7 @@ type SliceIndex struct{ *sliceIndex } type sliceIndex struct { pathStep xkey, ykey int + isSlice bool // False for reflect.Array } 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. 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. func isExported(id string) bool { r, _ := utf8.DecodeRuneInString(id) diff --git a/vendor/modules.txt b/vendor/modules.txt index 9b7ee59..d0236e4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,5 @@ -# github.com/google/go-cmp v0.3.1 +# github.com/google/go-cmp v0.4.0 +## explicit github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags